# V11 network simulator API execution contract

The browser planner chooses candidate paths; real HTTP calls below perform shared admission, fulfilment and charging. One Durable Object per network run owns all three operators' accounts, 24 windows, resource/demand reservations and outcomes. No optimizer runs on the server. This is a bounded mock, not distributed production admission or TMF conformance.

`POST /mock-api/networkRun` JSON `{scenario:<validated simulator scenario>,policy:"joint",execution:{buyerBudgetCents:100000,sellerCreditCents:100000,ranRateCents:1,backhaulRateCents:1,deliveryOutcome:"success"}}` returns 201 `{runId,state}`. All subsequent calls require `X-Network-Run: <uuid>`. Every mutation requires `Idempotency-Key`, including run creation (use a cryptographically random, unguessable creation key (a bearer capability, not a friendly identifier); it deterministically identifies an opaque run UUID, so same-key creation retries recover the same run; changed payload with that key conflicts). Same run/key/request replays original response; changed request conflicts. TTL 24 actual hours, no wall-clock quote expiry. 648 path purchases/own allocations and 12000 mutations maximum. Rates are integer cents per Mbps-hour, windows two hours, quantity 10..2000 Mbps in multiples of ten. Budget/credit 0..100000000 cents; rates 0..1000. Delivery enum success/failure/unknown.

TMF-shaped subset uses these exact paths:

* `GET /tmf-api/productCatalogManagement/v5/productOffering[/id]`: `network-path` offering plus six actual resource-owner offerings `ran-0..2`, `backhaul-0..2`.
* `POST /tmf-api/quoteManagement/v5/quote` body `{"@type":"Quote","@baseType":"Entity","quoteItem":[{"@type":"QuoteItem","id":"1","action":"add","productOffering":{"@type":"ProductOfferingRef","id":"network-path"}}],"demoProfile":{"windowIndex":0,"buyer":0,"host":1,"route":2,"quantityMbps":20}}` returns approved Quote. Quote demoProfile contains authoritative path, `transactionId`, `totalCents`, `lines`, `locationId`, `inputHash`, `policy`; IDs are server generated. Own resource lines cost zero but use capacity. Quote does not reserve anything. All-own paths use networkAllocation below.
* `PATCH /tmf-api/quoteManagement/v5/quote/{id}` body `{state:"accepted"}` buyer assent (duplicate no-op).
* `POST /tmf-api/productOrderingManagement/v4/productOrder` body `{quote:[{id:<quoteId>}],productOrderItem:[{id:"1",action:"add",productOffering:{id:"network-path"}}]}` returns accepted admission in acknowledged ProductOrder, extension `demoProfile.transactionId`. Atomically rechecks live demand, RAN/transport capacity, outages, access/reachability, independent-policy restrictions, buyer budget and each actual seller's directional credit. Quote may become stale due to another path's admission. Admission rejects without partial holds.
* `POST /tmf-api/serviceOrdering/v4/serviceOrder` body `{demoProfile:{productOrderId:<id>},serviceOrderItem:[{id:"1",action:"add",service:{name:"Network path",serviceSpecification:{id:"network-path-spec"}}}]}` returns ServiceOrder inProgress. Standard GET list/id supported for quote, productOrder, serviceOrder.
* `POST /mock-api/networkControl` body `{action:"fulfil",serviceOrderId:<id>}`: configured outcome. Success verifies BOTH components before delivered Mbps increases. Failure releases capacity/demand and budget/credit holds, earns zero charges. Unknown has zero charges/served and retains all reservations. `{action:"resolve",serviceOrderId:<id>,outcome:"success"|"failure"}` explicitly resolves unknown. Returns state.
* `POST /mock-api/networkAllocation` body `{windowIndex,buyer,host:buyer,route:buyer,quantityMbps}`: shared authoritative admission for own-only path, then verifies/delivers it without external quote/order/charge. Returns 201 `{allocation,state}`. Rejected own path cannot silently count as served.
* `GET /tmf-api/serviceInventory/v4/service[/id]` and `GET /tmf-api/agreementManagement/v4/agreement[/id]` expose the actual three-group inventory/framework. `GET /mock-api/networkState` returns state.

Proposed mock IODSP base `/tmf-api/interOperatorSettlement/v0.3`:

* `POST /commitmentRecord` body `{transactionId,evidenceRefs:[...definitive transaction evidence ids...]}`. Obtain refs from `state.evidence.filter(e=>e.transactionId===id&&e.billable).map(e=>e.id)`. Both successful component usage records or the failure record are required. Server prices actual path quantity/window and actual external resource owners, captures once, releases residual holds. Own lines have no financial charge. Unknown rejects. Returns 201 commitment with totalCents. No client amount accepted.
* `POST /bilateralStatement` body `{transactionId}` only after commitment, derives a separate bilateral record per actual creditor, no payment confirmation. Response `{transactionId,totalCents,statements:[...]}`. Zero totals settle without payment. Positive receivables remain outstanding. GET list/id supported for acceptedTerms, preAuthorisationPool, commitmentRecord, chargeRecord, bilateralStatement, nettingRegister, disputeResolution.

State `{runId,schemaVersion,inputHash,locationId,policy,execution,phase,quotes:[],orders:[],serviceOrders:[],transactions:[],allocations:[],evidence:[],charges:[],commitments:[],statements:[],accounts:{buyers:[{operator,budgetLimitCents,budgetHeldCents,budgetSpentCents}],credit:[{seller,buyer,creditLimitCents,creditHeldCents,receivableCents}]},windows:[{windowIndex,hour,demandMbps:[3],deliveredMbps:[3],demandHeldMbps:[3],ranCapacityMbps:[3],backhaulCapacityMbps:[3],ranUsedMbps:[3],backhaulUsedMbps:[3],hostUp:[3],routeUp:[3]}]}`. `transactions[].status`: quoted, buyerAccepted, accepted, activating, unknown, fulfilled, failed, rated, statement, settled. Top phase is executing. Standard resources/records carry `demoProfile:{mock:true,profile:"network-execution-v11",...}`. Evidence records name synthetic external fulfilment/assurance and authoritative input/window/path. No network delivery is inferred from the optimizer's assignments.

Errors `{ "@type":"Error",code,reason,status:"409" }`; malformed 400, missing/unknown run 400/404, insufficient budget/credit/capacity/demand/path 409, mutation bounds429. No global shared fallback. API traffic is the source for delivered state, financial projections and execution outcome.

Transactions expose `{id,transactionId,windowIndex,buyer,host,route,quantityMbps,quoteId,productOrderId,serviceOrderId,status,delivered,outcome,totalCents,lines:[{id,owner,external,rateCents,amountCents}]}`. Own allocations expose `{id,windowIndex,buyer,host,route,quantityMbps,delivered:true,outcome:"success"}`. Successful window bookings remain consumed; a fresh operation cannot reuse their demand/capacity. An unknown transaction blocks any later-window path using its RAN or transport resource (`unknown_resource_hold`) until explicit reconciliation. Timestamps are deterministic two-hour windows from synthetic epoch 2026-01-01T00:00Z, not real-time scheduling.

Window admission is monotonic (`lastAdmittedWindow` starts at -1). Quotes do not move the cursor, but successful ProductOrder/own admission does. Older-window admission rejects `window_order_conflict`. Any earlier window still accepted, activating or unknown blocks advancing windows (`pending_window_hold` / `unknown_resource_hold`), including paths using different resources, until physical fulfilment has a definitive outcome. This conservative whole-run gate avoids reinterpreting historical bookings. Network state schema is version 4; interim-version runs require a fresh run.

Validation: `node --test tests/cloudflare-network-api.mjs` uses the actual exported edge/DO handler with clone-on-read durable storage doubles, concurrency and actor reconstruction. `NETWORK_API_TEST_ORIGIN=http://127.0.0.1:8879 node --test tests/cloudflare-network-api-http.mjs` uses a real separately running Wrangler/workerd server, including 24 network windows, cross-operator purchases, own allocations, shared limits and all delivery outcomes.

Admission rejections are audited without any resource or financial mutation: `admissionAttempts[]` retains each accepted/rejected ProductOrder attempt, its transaction, window and original code/reason. A rejected transaction has `status:"admissionRejected"`, `admissionStatus:"rejected"` and `lastAdmissionRejection`. Buyer quote assent remains valid; a fresh operation key can retry admission if prerequisites change. Repeating the original key replays its original rejected response without adding an audit row. Successful later admission retains earlier attempts and changes operational status to accepted.

External failure evidence uses neutral `pathFailure`/`pathFailed`; this aggregate mock supplies no per-line fault attribution and does not assign seller blame. Own-resource allocation enforces authoritative demand, availability, access and capacity admission, then assumes own fulfilment (`verification:"admitted-own-fulfilment-assumed"`). The configurable success/failure/unknown injector covers external path orders only. Own delivery is not a second independent assurance measurement. The creation key and derived run UUID must be kept private; users sharing a creation key and payload intentionally address the same run namespace.

Exact cached mutation replies have a hard aggregate 64 MiB UTF-8 byte limit per run, tracked durably in the run root along with the request counter. The creation reply counts toward this limit. A new operation whose reply would exceed it returns `429 run_reply_storage_limit` before any snapshot, admission audit, resource or financial change is committed. GET current state and already-cached exact replays remain available after the cap; new work requires a fresh run. Replies are chunked but not compressed. This bounds retained replay storage in addition to 648 path records / 12000 mutations / 24-hour expiry; it does not eliminate full-snapshot rewrite cost.

Unknown or unfulfilled commitment requests return `409 outcome_unresolved` before checking their evidence array. ProductOrder relatedParty includes only its actual buyer and external resource sellers. The proposed `nettingRegister` contains bilateral payable instructions (`recordKind:"bilateralPayableInstruction"`, `multilateralOffsetApplied:false`, `offsetAmountCents:0`); it performs no multilateral offset and does not reduce gross credit exposure.
