Skip to content

feat(gateway): MC 9 bounded child authority — a composite operator carves a child reservation from one sealed unit (503 until the sealed deal is stored) - #415

Draft
LamaSu wants to merge 8 commits into
feat/r13-issue-routefrom
feat/mc9-child-reservations
Draft

LamaSu wants to merge 8 commits into
feat/r13-issue-routefrom
feat/mc9-child-reservations

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 (H1, H2, H3, M4, M5, M6). This merge carries #410's round-2 route (bbe03e3) into this branch and updates the child route for #402's changed store API — that's H1 over HTTP:

  • The route now names only { reservationId, unit }. The STORE derives the unit's operator, n and reclaimAt from the parent's sealed deal, inside its own 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 (feat(gateway): R13 reservation ISSUE + READ routes — server terms only, exact money; 503 until store, exact ceiling and payer binding exist #410).
  • 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 now apply: its ceiling (it was 0 before this commit) and its floor. The child's floor is max(the request's, the body's, the 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. New tests cover: 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.

Round-2 pack: 45-composition-r13-402-410-415-r2-ac3db658.md (covers #402, #410 and #415).


Draft, stacked on #410 (R13 issue and read routes) → #402 (the R13 store). MUST-CLOSE 9: bounded child authority (#2301), over HTTP.

What it does

POST /api/settlement/reservations/:parentId/children lets a composite operator subcontract part of a unit it was paid for. The child reservation is carved from ONE unit of a SEALED parent deal. The body is {unit: "<jobId>#<milestoneIndex>", requestId, currency, maxAmountBaseUnits, purpose, expiresInSec, minTier?}.

Authority, all enforced by the store in one immediate transaction (#402), with the route answering first where that avoids an oracle:

  • Only the parent unit's operator may carve a child. Anyone else gets exactly the missing-parent 404, checked BEFORE any other answer. A test caught an earlier version that leaked request-not-found for a real parent.
  • The child is funded by the operator's own wallet (payerFor), never the parent payer's.
  • All children of a unit fit its net n, and none outlives its reclaimAt.
  • The child's floor is max(the request's, the body's, the parent's) (round 2). The child's own request ceiling now applies too — it was 0 before ac3db65.
  • The child's own request must be the operator's own, in the same currency.
  • The parent must be sealed (consumed); an unsealed parent now gets the SAME 404 as a missing parent (round 2, H1 over HTTP) — it was a 409 before ac3db65.

A failed child cannot mark the parent fulfilled. The parent unit releases only on its own tier evidence, and VCR's requires (#2674, emitted by #391) gates release order.

Where the sealed deal lives (resolved in round 2)

The store keeps the sealed deal. budget_reservations.consumed_deal_json holds the compiled plan as acceptedDealDigest's own canonical preimage, written in the same consume transaction (amendment #3231, #402 450b575/@af14e2ad; steward condition #3235). The route-side ParentUnitResolver this section used to describe is gone: services/sealed-deal-parent-units.ts and its parentUnits option are deleted, in this PR (ac3db65). The STORE itself now derives the parent unit's terms — operator, n, reclaimAt — from that stored deal, inside its own immediate transaction, after checking the stored bytes against the sealed digest (#402 @45d0cde3, H1). Production's 503 now depends only on the issue wiring (#410), not on any resolver here.

Tests (reservations-route.test.ts, +3; 7 in total)

  • End to end on one durable store:

    1. the payer's deal is issued and sealed;
    2. the print unit's operator carves a child with its own wallet (201);
    3. a subcontracted child plan (the mail drop, another operator) is accepted against the child (200), with the operator's wallet as the payer.
  • Bounds: n exact to one base unit, across children; reclaimAt exact (at it is fine, one second over is 422); the parent payer's wallet is refused; an unsealed parent is the same 404 as a missing one (round 2; was 409 before ac3db65); the child request must be the operator's own and in the reservation's currency.

  • No oracle: another principal, a missing parent and an unknown unit give the byte-identical 404. Malformed unit references are 400. Production is 503.

  • Mutation checks, 6: all killed. The C8 survivor led to the request-currency case.

    (Round 1 counts. Round 2's own numbers — gateway R13 139/139, reservations-route 10/10, 5 mutation checks all killed — are in the Round 2 section above.)

pcc-composition 8a0f4de0, goal pcc-reconciliation.

🤖 Generated with Claude Code

https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz

LamaSu and others added 6 commits September 24, 2026 16:16
…erator carves a child reservation from a sealed unit

POST /api/settlement/reservations/:parentId/children lets the operator of
ONE unit of a SEALED parent deal subcontract part of it (#2301). The store
(#402) enforces every bound in one immediate transaction; the route
answers first where that closes an oracle.
- Only the parent unit's operator may carve a child. Anyone else gets the
  byte-identical missing-parent 404, BEFORE any other answer. A test caught
  an earlier version that leaked request-not-found for a real parent.
- The child is funded by the operator's own wallet, never the parent
  payer's. All children of a unit fit its net n and none outlives its
  reclaimAt. The floor is max(parent's, own). The child's own request must
  be the operator's, in the reservation's currency. An unsealed parent is
  409.
- The parent unit's terms come from an injected ParentUnitResolver.
  Nothing persists the sealed deal today, so production answers 503. The
  proposed consumed_deal_json amendment to #2240 is in the PR body.

Tests: 3 new (7 in the file), including end to end on one durable store:
payer's deal sealed, the operator carves a child, a subcontracted child
plan is accepted against it. Full gateway suite: 3061 passed, 6 skipped;
only the known capture suites fail to load. 6 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
…rable store (PCC_HTTP_TRACE_OUT)

When PCC_HTTP_TRACE_OUT is set, the MC 9 end-to-end test writes the whole
exchange as JSON:
1. the payer issues;
2. the parent deal is sealed;
3. the operator carves a child;
4. the child plan is accepted (digest, payer, obligation, deal bindings,
   seal, presentation state);
5. both reservations are read back.
The composition return cites it as the HTTP-level end-to-end trace, next
to the pure-level one. Nothing changes when the variable is unset.

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
…l (amendment #3231)

services/sealed-deal-parent-units.ts derives a parent unit's terms from
the bytes the R13 store keeps. Those bytes are the accepted deal's
canonical preimage, which hash to the sealed digest. The terms are the
job's operator, the unit's exact net n and its reclaimAt. Nothing comes
from the caller: the reservation id and unit reference are lookup keys. An
unknown unit, an unsealed parent, unreadable bytes or a non-canonical
amount are null (fail closed; BigInt alone would accept hex and padded
digits).

The MC 9 tests now use this REAL resolver instead of a stand-in. An
unsealed parent therefore has no sealed deal and gets the same 404 as a
missing one; the store's own not-consumed check remains as defence in
depth.

Tests: route file 8/8 (1 new resolver test). Full gateway suite: 3062
passed, 6 skipped; only the known capture suites fail to load. db 208/208.
4 mutation checks: 3 killed, 1 equivalent (reading an unsealed
reservation's absent bytes as an empty deal is still null; the store's
state filter is what guards it, and the db tests pin that).

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
…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
…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>
LamaSu and others added 2 commits September 29, 2026 14:46
…nto feat/mc9-child-reservations

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…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 — b0e72194 Deployed Oct 7, 2026 by LamaSu via post-verdicts #206
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