Skip to content

feat(gateway): R13 reservation ISSUE + READ routes — server terms only, exact money; 503 until store, exact ceiling and payer binding exist - #410

Draft
LamaSu wants to merge 7 commits into
feat/r13-budget-reservationsfrom
feat/r13-issue-route
Draft

LamaSu wants to merge 7 commits into
feat/r13-budget-reservationsfrom
feat/r13-issue-route

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Round 3 (2026-09-29): merges #402's fixes for astra's pack-45 findings

This head merges #402 @638bc3dc (A, B, C, D, each reproduced first). The R13 delta pack is 92-composition-r13-r3-6dadaf8b.md, and the full tests pass at #415's head 6dadaf8.


Round 2 (the operator's cross-family review, 2026-09-28)

The operator's ChatGPT review of #402/#410/#415 at 0b8adda9 was DO-NOT-SHIP. This is the #410 half of the round-2 fixes (bbe03e3); the store half is #402 @45d0cde3, merged in e7a49b6.

  • M4 — the assurance floor. requestCeiling becomes requestTerms(requestId, principal) -> { currency, ceilingBaseUnits, minTier }: the request's own server-held terms, neither with a default. The body's minTier can only RAISE the request's floor, never lower it. A malformed server floor is a wiring fault, answered with a generic 500, never a default. Production stays 503 until gateway provides exact request terms, floor included — the requests table stores no floor today, and the 503's missing list says so.
  • H2 — no route-side time. The route passes no time; the store's own clock stamps issue time and the bounded expiry.

Tests: gateway R13 134/134 (+1: the floor comes from the request, the body only raises it, a malformed server floor is a 500). tsc is clean apart from two pre-existing unrelated errors. 4 mutation checks, all killed: the body lowers the floor; the request floor is ignored (the old code); a malformed server floor is accepted; the ceiling is not the request's.


Draft, stacked on #402 (R13 store). R13's other half over HTTP. #402 lets the accept route CONSUME a reservation; this lets a principal ISSUE and READ one. It cannot merge before #402 (operator decision #2240, queue item 26), WP-A (R28) and the two decisions below.

What it does

Route Behaviour
POST /api/settlement/reservations Issues a reservation for the authenticated principal. The body is {requestId, currency, maxAmountBaseUnits: "<canonical base units>", purpose, expiresInSec, minTier?}. Returns 201 with the reservation (amounts as exact decimal strings).
GET /api/settlement/reservations/:id Reads one of the principal's OWN reservations.

Authority. Every term that grants money authority is the server's:

  • principal: the API gate's identity (authenticatedPrincipal).
  • payer: payerFor(principal), the wallet bound to that principal. A request's requesterWallet is caller data and is never used (invariant 10).
  • ceiling and floor: requestTerms(requestId, principal) (round 2, M4) — the principal's OWN request's server-held terms: its authorized ceiling in EXACT base units, and its assurance floor. Neither has a default. It must be the principal's own request; another principal's request is a 404, exactly like a missing one. The body's minTier can only RAISE the request's floor, never lower it.
  • id: server-generated. Issue time and expiry: the store's own clock plus the bounded lifetime (round 2, H2) — the route passes no time.

A body reservationId, principal, payer or ceiling is ignored (tested).

⚡ Decisions this needs (not mine)

  1. Exact request terms — ceiling AND assurance floor (gateway, fix(gateway): honest operator-relay evidence, claim authentication, immutable authorized ceiling (LO-GW-4/3a, R-06) #335 R-06). Today authorizedCeiling = body.budget ?? 1000: a JS number with a DEFAULT, and the requests table stores no floor at all. For money authority both must be:

    Production stays 503 until gateway provides both (round 2, M4).

  2. The payer-wallet binding (gateway N1/N21). Which wallet pays for a principal's reservation? It is never a body field.

Tests (src/__tests__/reservations-route.test.ts, 4)

  • End to end on ONE durable store: issue (201, server terms, body authority ignored) → accept (200, sealed) → read (consumed, the same digest) → accept again (409).

  • Authority: 401; the 404 existence-oracle closure for requests and reservations; no bound wallet is 409.

  • Exactness and bounds: eight malformed amounts; the TTL and floor bounds; currency mismatch; the ceiling exact to one base unit.

  • Production and faults: 503 on both endpoints; a thrown resolver is a generic 500.

  • 9 mutation checks, all killed:

    (Round 1 counts. Round 2 — M4 and the store clock — adds its own test and 4 mutation checks; see the Round 2 section above.)

pcc-composition 8a0f4de0, goal pcc-reconciliation.

🤖 Generated with Claude Code

https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz

…ly, exact money; 503 until wired)

This is R13's other half over HTTP. #402 lets accept CONSUME a reservation;
this lets a principal ISSUE one and READ its own.

- POST /api/settlement/reservations issues for the authenticated
  principal. The payer is payerFor(principal) and the ceiling is
  requestCeiling(requestId, principal) in EXACT base units; the id,
  issue time and expiry are the server's. Body authority fields
  (reservationId, principal, payer, ceiling) are ignored.
- GET /api/settlement/reservations/:id reads only the principal's own.
  Another principal's reservation or request gets the same 404 as a
  missing one.
- Amounts are canonical base-unit STRINGS; a JSON number is a float and is
  refused. TTL 60 s to 7 days; floor 0..3; the currency must be the
  request's. The ceiling check is #402's exact one.
- It is a money path, so the scope checker default-denies it. It answers
  503 issue-not-wired until the store (#2240), an exact request ceiling
  and the payer-wallet binding exist. #335's authorizedCeiling is a JS
  number defaulting to 1000 and is not usable as money authority.

Tests: 4, end to end on one durable store: issue, accept, read consumed,
second accept 409. Full gateway suite: 3058 passed, 6 skipped (only the
known capture suites fail to load). 9 mutation checks, all killed.

pcc-composition 8a0f4de0, goal pcc-reconciliation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
LamaSu and others added 2 commits September 24, 2026 16:24
…r13-issue-route

pcc-composition 8a0f4de0, goal pcc-reconciliation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
LamaSu added a commit that referenced this pull request Sep 24, 2026
LamaSu added a commit that referenced this pull request Sep 24, 2026
…mc9-child-reservations

pcc-composition 8a0f4de0, goal pcc-reconciliation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
LamaSu and others added 2 commits September 28, 2026 16:26
pcc-composition 8a0f4de0, goal pcc-reconciliation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… time is the store's (ChatGPT review of 0b8adda)

This is the #410 half of the round-2 fixes. The store half is 45d0cde, merged in e7a49b6.

- M4: the assurance floor.
  - `requestCeiling` becomes `requestTerms(requestId, principal) -> { currency, ceilingBaseUnits, minTier }`: the request's own server-held terms, neither with a default.
  - The body's `minTier` can only RAISE the request's floor, never lower it.
  - A malformed server floor is a wiring fault, answered with a generic 500, never a default.
  - Production stays 503 until gateway provides exact request terms, floor included. The requests table stores no floor today, and the 503's missing list says so.
- H2: the route passes no time. The store's own clock stamps issue time and expiry (the bounded lifetime), so the `now` wiring is gone.

Tests: gateway R13 134/134 (+1: the floor comes from the request, the body only raises it, a malformed server floor is a 500). tsc is clean apart from the two pre-existing unrelated errors.

4 mutation checks, all killed: the body lowers the floor; the request floor is ignored (the old code); a malformed server floor is accepted; the ceiling is not the request's.

pcc-composition 8a0f4de0, goal pcc-reconciliation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…h the child route on the new store API (H1)

This merge carries the R13 round-2 store (45d0cde) and issue route (bbe03e3). The store's API changed, so the MC 9 child route changes with it, here in the merge. That change is the ChatGPT review's H1 over HTTP.

- The route names only `{ reservationId, unit }`. The STORE derives the unit's operator, n and reclaimAt from the parent's sealed deal, inside its immediate transaction, after checking the stored bytes against the sealed digest.
  - The route-side resolver (services/sealed-deal-parent-units.ts) is deleted, and so is its `parentUnits` option. Production's 503 now depends only on the issue wiring.
- No oracle.
  - The whole not-found family is the SAME 404: no parent, parent not sealed, no such unit, not the unit's operator, and a corrupt stored deal (which is also logged as an integrity fault).
  - An unsealed parent was a 409; it is now that same 404.
  - The caller's OWN request is checked first. It says nothing about the parent.
- The child's own request terms apply: its ceiling (it was 0) and its floor. The child's floor is max(request's, body's, parent's).
- The conflict resolution in reservations-route.test.ts: the request table takes the new terms shape (ceiling and floor), with the operator's subcontract requests; the wiring keeps the operator's own payer wallet.

Tests: gateway R13 139/139, reservations-route 10/10. There are new tests for: another operator's unit; forged body terms ignored; the child's own ceiling; the child's floor; a corrupt stored deal giving the same 404.

5 route mutation checks, all killed: a corrupt deal answers 500; an unsealed parent is 409; the child ceiling is 0; the child skips its own ceiling; the child floor comes from the body only.

pcc-composition 8a0f4de0, goal pcc-reconciliation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3-issue-route

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…nto feat/mc9-child-reservations

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sue-route (plain merge, no edits)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZpG7S6AyDzTEJBQH8up44
LamaSu added a commit that referenced this pull request Oct 7, 2026
…at/mc9-child-reservations (plain merge, no edits)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZpG7S6AyDzTEJBQH8up44

This branch had an error being deployed

1 failed deployment
trusted-checks — 982040b2 Deployed Oct 7, 2026 by LamaSu via post-verdicts #205
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant