# Level 2: build a Store order-to-inventory integration

This original BEAT IT project extends the Level 1 health/lookup starter with one small write integration. A Mule `POST /orders` validates input, maps it with DataWeave, calls a local synthetic inventory HTTP service and returns a bounded response. The backend owns stock changes and operation-key replay. Mule does not implement a database transaction or supply an exactly-once guarantee.

**Checked here:** ten Python tests make real HTTP requests over loopback to the included stateful backend; they include concurrent duplicate calls. XML/JSON parsing and structural checks inspect the Mule project. **Not executed here:** Maven resolution, Mule compilation, the twelve MUnit examples, Studio/Code Builder deployment, or HTTP through Mule. Expected Mule results below are acceptance criteria, not recorded successful runs. No cloud deployment, credentials, paid model, vendor project or customer data is included.

## 1. Follow the files and ownership

| File | Purpose | What it does not establish |
| --- | --- | --- |
| `backend/inventory.py` | Real local HTTP server with stock and ledger under one lock | Durable production storage or authentication |
| `backend/test_inventory.py` | Real HTTP baseline, conflict, failure and concurrency tests | That Mule calls it correctly |
| `store-api/src/main/mule/store-api.xml` | Listener, validation, connector, response/error handling | Executed Mule behavior |
| `store-api/src/main/resources/reservation.dwl` | Original boundary mapping | Mapping execution until run in Mule/MUnit |
| `store-api/src/test/munit/store-api-test.xml` | Twelve example tests, including forbidden calls and direct mapping | Passing tests merely because XML exists |
| `clients/store.http` | Ordered acceptance requests | Observed results until executed |

Use Python 3.10+ for the backend. It binds only `127.0.0.1`; do not expose this unauthenticated teaching server to other machines. It keeps state in memory and resets when restarted. `/state` is a local teaching inspection endpoint, not a production administrative API.

## 2. Run the independent backend checks

From this folder:

```bash
python3 -m unittest discover -s backend -p 'test_*.py' -v
```

Expected: ten tests pass. Each test starts a fresh server on an available loopback port, sends actual HTTP requests and inspects actual responses and resulting state. The concurrent test sends twelve requests with one key and expects one `201`, eleven `200` replays, one reservation ID and one stock decrement. It tests this single-process locking contract, not distributed persistence.

Inspect `test_restart_loses_memory_is_explicit`: a new server has stock five and no operation history. Replaying a previous request after restarting this server can create a new effect. This limitation is deliberate and blocks any claim of durable duplicate prevention.

## 3. Start a fresh inventory service

```bash
python3 backend/inventory.py --port 8091
```

Leave it running in a separate terminal. In another terminal:

```bash
curl -i http://127.0.0.1:8091/state
```

Fresh expected body:

```json
{"stock":{"0012":5},"effects":0,"operations":0}
```

Stop and restart to deliberately reset the exercise. Do not reset during a test sequence and then interpret the cleared ledger as evidence that a previous effect never happened. The backend's known stock is fictional. `0012` is a string identifier, not numeric twelve.

## 4. Understand the two contracts before coding

The Mule request requires exactly these fields:

```json
{"orderId":"O100","sku":"0012","quantity":2}
```

`orderId` accepts 1–64 ASCII letters, digits, `_` or `-`; `sku` accepts the same alphabet with length 1–32. Quantity must be numeric, integral and between 1 and 100. Strings, booleans, missing/null fields, extra fields and zero are rejected. `Idempotency-Key` is required with the order-ID alphabet/length. Reuse it only for the same intended operation and unchanged body. A new key means a new operation under this contract; it does not mean the stock service deduplicates all repeated order IDs.

The outbound mapping is:

```json
{"order_id":"O100","product":{"sku":"0012"},"units":2}
```

Read `reservation.dwl`: the order identifier changes field name, SKU becomes nested, and quantity becomes units. Input private fields are not forwarded; the strict request schema rejects extras. The backend validates its own contract independently because a caller can bypass Mule. Integer-valued JSON numbers such as `2.0` normalize to integer units in the backend before fingerprinting.

Predict the mapping for quantity three before running anything. Expected: identical IDs and nested shape, `units=3`; it is a different payload, so reuse of an already committed key conflicts.

## 5. Inspect and configure the Mule project

Open `store-api` as a Mule Maven project in your compatible authorized Studio/Code Builder environment. The standalone POM uses the same approved pins as the Level 1 template: Mule Maven Plugin `4.10.3`, MUnit `3.7.5`, HTTP Connector `1.12.1`, Java 17 and an artifact minimum Mule version of `4.9.0`. The minimum is not an exact runtime selection. Verify compatibility with the current release documentation and your actual available runtime.

Replace `REPLACE_WITH_EXACT_SUPPORTED_RUNTIME` in the POM with that exact supported runtime, or pass the property explicitly with your actual version. Do not type a fabricated runtime or copy an obsolete documentation sample. Then run from `store-api`:

```bash
mvn clean test
```

This step can resolve dependencies over the network and requires your own compatible development setup. Record the actual command, Java/Maven/runtime versions, MUnit reports and failures. If dependencies, licensing or runtime are unavailable, leave this step **not executed** and retain the backend evidence separately. No `mule:deploy`, Exchange publication or cloud profile is present in this project.

`application.properties` binds Mule at loopback port `8082` and inventory at loopback port `8091`. Both processes must share the same machine/network namespace. A container's `127.0.0.1` is not the host's loopback; use an explicitly reviewed local arrangement before changing addresses. This project does not configure container networking.

## 6. Review what the MUnit examples test

The twelve definitions cover quantity zero, string quantity, unexpected field, valid mapped response, idempotency conflict, malformed backend success, connectivity failure and the DataWeave mapping itself. Four additional negative-success cases reject negative stock, fractional stock, empty reservation IDs and IDs outside the backend R-plus-positive-integer pattern. Successful dependency responses require nonnegative integral remaining stock and a reservation ID matching `R[1-9][0-9]{0,18}` (at most twenty characters). Invalid input asserts **zero** `http:request` calls. Valid processing verifies one call. Dependency mocks return controlled responses; they do not exercise real network or stock state. The direct mapping test executes `reservation.dwl` instead of merely accepting a canned backend response as proof of mapping.

Run the suite, inspect actual reports and deliberately change the mapping in a disposable copy: convert SKU to a number or map quantity to a wrong value. The corresponding assertion should fail. Restore the mapping and rerun. This mutation check is an assigned local activity; it has not been executed while authoring the package.

## 7. Start Mule and execute the complete path

After compilation/tests succeed in your environment, run the application locally using your configured IDE/runtime. Keep the Python backend running. Execute request 1 in `clients/store.http`, or:

```bash
curl -i -X POST http://127.0.0.1:8082/orders \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: K100' \
  --data '{"orderId":"O100","sku":"0012","quantity":2}'
```

Expected first result: HTTP `201`, `orderId=O100`, `sku="0012"`, `quantity=2`, `remaining=3`, `replayed=false` and a generated trace ID. Inspect `/state`: expected effects one and stock three. Save actual input, output and state. A response alone does not show that state changed exactly as expected.

The flow generates `vars.safeCorrelation` using `uuid()` rather than trusting a caller-supplied trace string. Mule's own correlation ID can inherit a source header; therefore this lab uses its separate safe ID in its response, outbound header and minimal completion log. Trace identity is not authentication or authorization.

## 8. Repeat, conflict and reject without hidden effects

Repeat the same K100 request unchanged. Expected HTTP `200`, the same reservation ID and `replayed=true`; stock stays three and effects stays one. Next change quantity to one while retaining K100. Expected `409/idempotency_conflict`; stock remains three. Do not change the key just to make a conflicting request pass. Decide whether the changed request is truly a newly authorized operation.

Send quantity zero with a fresh key: expected `400/invalid_order` and no inventory effect. Send unknown SKU with a valid shape: expected `404/unknown_sku`. Ask for six units on a fresh backend: expected `409/insufficient_stock`. The outbound response validator deliberately admits HTTP status codes for application inspection; that does not turn those error responses into business success. Unrecognized or malformed dependency responses become a safe `502` rather than forwarding arbitrary backend content.

## 9. Exercise transient failure and bounded retry

On a fresh unused key K-retry, send the request in `clients/store.http` with `X-Lab-Failure: before-once`. The local backend's first attempt returns `503` before making an effect. Repeat the **same key and same body**: expected `201`. Repeat once more: `200` replay. The test header is an explicit local teaching mechanism; remove it when adapting to a real service. There is no automatic retry loop in this Mule project.

A caller may make a bounded retry under this backend contract. Record attempts including the initial call. Do not treat every production `503` as proof nothing committed: the special fake service knows its pre-commit branch, whereas a timeout or lost response can leave the caller uncertain. Mule maps HTTP connectivity/timeout to `503` with `outcome=unknown`, never to zero stock or confirmed no effect.

For an uncertainty tabletop, imagine the backend committed but its response was lost. Repeating the same key/body while this process and ledger survive returns the existing reservation. Restarting the backend destroys that evidence. A real design needs durable atomic state, retention, operation-status reconciliation and a verified downstream contract; an HTTP header or in-memory dictionary does not provide those guarantees by itself.

## 10. Review failures without guessing success

Stop the backend while Mule remains available, then send a valid fresh request. Expected dependency-unavailable behavior; retain the actual connector error category without exposing stack traces or request payloads to the client. Restart the backend deliberately and note that its inventory/ledger reset. Restore a known test baseline before continuing.

If Mule returns `502` unexpectedly, inspect the boundary: response MIME/shape, field association, status code and server reachability. Do not loosen success checks or disable transport controls merely to obtain a green result. HTTP here is confined to local synthetic processes; this is not a template for sending production data without appropriate TLS and access controls.

## 11. Handoff and acceptance record

Deliver:

1. Python test output from this package and your actual local run date.
2. Exact Mule tool/runtime versions and actual MUnit reports, or an explicit unexecuted blocker.
3. Mapping prediction and mutation-test evidence if executed.
4. Full-path first create, unchanged replay, changed-body conflict, invalid input and unavailable-dependency traces, each with `/state` observations where meaningful.
5. A short ownership statement: Mule validates/maps/routes; the backend lock and ledger own this process's stock/replay decision; neither is durable across restart.
6. Remaining gaps: authentication, authorization, TLS deployment, durable storage, distributed concurrency and actual cloud operations are outside this small reference project.

A reviewer should check output **and forbidden effects**, retain a positive control and change one input at a time. A passing Python suite does not establish the MUnit or Mule integration checks. A successful local Mule run would not establish a production architecture, official certification or cloud readiness.

## Public primary references checked

- [HTTP Connector 1.12 reference](https://docs.mulesoft.com/http-connector/latest/http-documentation): request, timeout, headers and response validator.
- [HTTP request streaming troubleshooting](https://docs.mulesoft.com/http-connector/latest/http-troubleshooting): `requestStreamingMode="NEVER"` is used for this small Content-Length backend contract; it is not a large-payload streaming design.
- [Raise Error](https://docs.mulesoft.com/mule-runtime/latest/raise-error-component-reference) and [error handling](https://docs.mulesoft.com/mule-runtime/latest/error-handling).
- [MUnit mocking](https://docs.mulesoft.com/munit/3.6/mock-event-processor) and [verify calls](https://docs.mulesoft.com/munit/3.6/verify-event-processor).
- [Correlation IDs](https://docs.mulesoft.com/mule-runtime/4.9/correlation-id): source IDs may be inherited; the lab generates a separate safe trace reference.
- [Mule Maven Plugin 4.10.3](https://docs.mulesoft.com/release-notes/mule-maven-plugin/mule-maven-plugin-4.10.3-release-notes), [MUnit 3.7.5](https://docs.mulesoft.com/release-notes/munit/munit-3.7.5-release-notes), [HTTP releases](https://docs.mulesoft.com/release-notes/connector/connector-http).

These references describe product behavior. The files remain an original synthetic exercise; no vendor manual text or solutions are redistributed.
