Repository navigation
feat(gateway): accept seam for externally authored plans + end-to-end trace (R9 pure part) - #356
Merged
Merged
Conversation
… trace (reconciliation R9, pure part)
acceptExternalPlan(submission, { principal, tenantId }, deps) chains
R10 (live re-read) -> R11 (server-resolved programs + evidence's gate) ->
R12 (compileAcceptedPlan) with every read injected, so the accept decision
is a pure function of (submission, principal, live state). The agent
supplies only the plan's shape and what it believes each node costs; every
settlement term is the server's: operator and payout, gross, currency,
program, evidence requirements, payer (from the reservation), fee and
reclaim time (policy), and the plan id itself (derived from the reservation,
so a caller cannot pick job ids that collide with another plan's).
Refusals are typed by stage: submission, reservation (not-found / not-issued
/ expired / wrong-principal / wrong-request), revalidation (the non-current
verdicts, stale ones carrying the re-quote), currency, program (claimed
mismatch), evidence (no contract for the tier), compile.
external-plan-trace.test.ts is the return contract's end-to-end trace:
agent DAG (listed mail-first) -> accepted deal (2 V-next jobs, one per
operator, canonical order, bigint economics, sealed digest) -> reservation
consumed exactly once (a labelled R13 STAND-IN implementing the consume
protocol: re-check, RECOMPUTE the digest, consume once, seal) -> V-next
units ready (conservation, step ids, root, reclaimAt). Plus the charter's
negatives through the whole seam: forged cheaper snapshot, unapproved
program, expired reservation, principal/request A used for B, obligation
over reservation, double consume (exactly one), duplicate node, a plan
altered after acceptance (digest-mismatch at consume), caller-chosen
planId/payout ignored, currency and evidence refusals, malformed input.
PCC_TRACE_OUT=<path> writes the happy-path trace as JSON.
13 tests; 7 mutation checks (principal, expiry, node currency, program
cross-check, caller planId, missing evidence, request) each caught. Scoped
tsc clean. Stand-ins: in-memory live rows, program registry + gate (#349
draft), CSD tier -> evidence map (evidence owns it), R13 store (#2240).
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
… agent's plan (R9)
Product pack section 5 ("SERVER COMPOSER") and ledger R9: the server
composer is one optional planner, never canonical. The new
submissionFromComposeResponse() only restates a ComposeResponse as the
claims an agent would submit (nodes step-<index>, dependsOn -> edges, the
composer's operator/kernel/type/tier, and its float estimatedPriceUSD
verbatim). R10 then re-reads and compares those claims like anyone's, so a
composer that drifted from the live rows gets `stale` + a re-quote (or
`invalid-claim` for an exponent-form float), never a deal on its own say.
Non-proposed or expired proposals, dangling dependencies and duplicate
step indices are refused before the seam.
Tests: 3 new in external-plan-trace.test.ts (16 total there): accepted
through the ordinary seam; float drift -> stale; 1e-7 -> malformed claim;
expired / over_budget / dangling dep / duplicate index refused. 4 mutation
checks (expiry, status, dangling dep, duplicate index) each caught. Scoped
tsc clean.
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
Astra's cross-family review of #356: SHIP-WITH-FIXES. It confirmed the authority chain: payer from the reservation; operator and payout from the live kernel; gross, currency and program from the server; the reservationId is only a lookup key, which satisfies the charter's "no caller reservation id as authority". Fixed: - Tier: the tier is the principal's purchase choice, constrained by the live offer. A reservation may now carry `minTier`, a floor the payer set (invariant 11). A delegate spending it cannot downgrade assurance (new refusal stage `tier`). The header says so instead of "every term is the server's". - Compose adapter: JSON-shaped values whose coercion throws (status, expiresAt or assuranceTier with a null toString) crashed it. Every field's type is now checked before any coercion, and a string tier "2" no longer becomes "tier2". The validation boundary is explicit: structure here, meaning in R10. - Seam: an empty or missing principal matches nothing. The clock is floored (a `Date.now()/1000` clock made BigInt throw). A non-finite clock throws rather than compare as "not expired". - R13 stand-in consume contract, which was overstated: it now re-checks plan id and EACH job's payer, and DERIVES the obligation from the units. It documents that a recomputed digest proves integrity, not authority (a resealed plan with another payer is refused). Tests: 23 in external-plan-trace.test.ts (7 new). The fix-driven ones fail against f2d74b3. New negatives: consume-time expiry / principal / resealed payer / understated total / foreign plan id; a resolved program the gate rejects; tier below the reservation minimum; nested malformed nodes and edges; adapter coercion crashes. Mutation checks: seam 10/10 caught, adapter 6/6 caught. Scoped tsc clean. 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
… outcomes carry verdicts and a submission digest The pattern that closed #351 and #355 under cross-family review, now applied to the seam (#356) before its confirmation round: - The submission is copied once (snapshotSubmission: requestId, reservationId, each node's fields, edges; lengths read once, lists capped, non-primitives become an owned sentinel). The program cross-check, R10 and the compile use only that copy. A callback that mutates the caller's submission changes nothing. - The reservation record is read once. A malformed record (non-numeric expiresAt, or minTier outside 0..3) is a typed refusal, "malformed-reservation". - Dependencies and policy are read once, before any input. An unreadable or non-function dependency is a defined wiring fault (TypeError). Each dependency is called with deps as its receiver, including the gate the compiler calls. The principal and tenant are read once. - Every outcome after the snapshot carries `submissionDigest`, a type-tagged sha256 of the submission exactly as evaluated, so a read model can prove the submission it shows belongs to this outcome. It also carries `verdicts`, all of R10's verdicts whenever R10 ran. That field moves down from #357, where it belongs to the seam. Tests: 30 in external-plan-trace.test.ts (7 new). 17 mutation checks each caught: 7 new (fields read twice, compile edges taken from the caller, reservation read twice, receiver dropped, unguarded wiring, unguarded submission read, digest ignoring edges) plus the 10 earlier seam mutations re-anchored. Scoped tsc clean. 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
# Conflicts: # packages/gateway/src/__tests__/external-plan-trace.test.ts # packages/gateway/src/services/external-plan-seam.ts
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
… be verified end to end (N19) The oracle reads a funded tier's evidence two ways. One is event presence under the CSD's ladder (#383). The other is a registered verifier per listed primitive, and a stub verifier fails closed. A tier whose primitives nobody can verify was still fundable here, as long as its committed program fit. That sold assurance the oracle cannot check, and the tier evidence no stage enforces weakened the promise without anyone seeing it. assertAcceptedProgramForTier now takes the funded CSD's whole evidence map in place of one tier. It refuses with tier-not-eligible, and the reasons, unless tiers 0..T are eligible under computeCsdEligibility's oracle-enforcing mode (requireImplementedVerifier). A funded tier absent from the CSD is tier-not-in-csd. The program check runs against the CSD's own tier. The primitive index is injectable, for tests and for when verifiers go live. As authored, document-print-and-mail is eligible only at tier 0: - tiers 1-3 lack ident.registered_key, which receipt.kernel_signed and machine.execution_log depend on; - tier 2 has no Family-G primitive; - six of its verifiers are stubs. So the tier2 program (0xd229c8da) stays pinned but cannot be funded until the CSD and its verifiers are fixed. This is N19's "or those tiers made unfundable", derived instead of hand-listed. API change for composition (#351/#356): pass evidence: csd.evidence in place of tier: csd.evidence[tierKey]. Tests: committed-program.test.ts 31/31, including 8 new eligibility cases. The existing program-leg negatives now fund a fixed CSD with live verifiers. Spec suite green; tsc clean. 7 mutants killed: leg removed, report-only, reasons scope, off-by-one, tier-in-CSD, missing-tier reason, caller index. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS
…o feat/external-plan-seam
…nto the sealed deal (N25) An agent's plan may now say WHAT each node runs on: `inputs` (e.g. the document hash and page count) and `constraints` (e.g. a deadline), as plain JSON. That is content, never authority. The compiler (#351, merged in) seals it through each node's canonicalPlan and planHash. - snapshotSubmission reads each node's inputs and constraints ONCE through @pcc/spec's copyPlanJson. The result is absent, an owned frozen copy, or the refusal reason. - submissionDigest v2 encodes execution JSON by its canonical form. Absent, {} and invalid all differ, and invalid evaluates to its reason, just as a non-primitive evaluates to NOT_DATA. - Invalid execution JSON is refused as { stage: "submission", reason: "invalid-execution-json", fields }. It names EVERY bad field in (nodeId, field) order, and it comes before any reservation read. - The compiler receives the owned copies; absent means {}. Tests: 5 new in the trace, 35/35 there and R10 47/47; the files typecheck. 8 mutation checks, all killed: not refused, dropped before the compiler, the digest ignoring it, a double read, unsorted problems, a reservation read before the refusal, absent colliding with {}, and constraints dropped. 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
…hows each unit's sealed execution (N25) This merges #356, which brings #351's agreementHash seal and the per-node execution contract. PlanPresentation's owned plan copy now includes every field the deal digest commits to, so its re-derived digest matches: - agreementHash; - each binding's planHash; - a field-by-field copy of its canonicalPlan, with inputs and constraints read once through copyPlanJson. Non-JSON is invalid (malformed-outcome) even inside a resealed plan. Additionally: - The binding check requires each canonicalPlan to name its own plan, node, job and unit (plan-binding). - Compiled and sealed nodes show `execution: {planHash, inputs, constraints}`, taken only from the intact, bound plan, never from the proposal. The deal shows agreementHash. - The seam's invalid-execution-json refusal renders as refused, with one code per bad field ("<field>:<reason>@<nodeId>"). - The trace test's tail conflict (both sides appended a describe block) is resolved by keeping both. Tests: trace 53/53 (4 new) and the files typecheck. 9 mutation checks, all killed: planHash or agreementHash not copied, the contract not bound (and only its node unbound), execution not shown, exec-JSON codes not rendered, execution JSON not validated, and the deal hiding agreementHash. That last one survived until a resealed-agreement test was added. 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
…act) into feat/agent-plans-route
…on b: royalties on top)
SeamDeps.economics is optional and present only when an agreement applies.
It is { unitGross, splitNet }: economics' agreementUnitGross and
netSplitterFor, bound per request by the route to the agreement and the
server's facts.
- It is read once with every other dependency. Anything else is the
defined wiring TypeError. Both functions are called with the binding as
their receiver.
- The agreement's gross map is read once into owned data; bigints only.
- For each node, the gross must exist and must cover the operator's live
quote from R10. Otherwise it is a typed refusal at the new "economics"
stage: agreement-refused (with economics' code), agreement-unreadable,
unit-gross-missing or quote-not-covered, all before the compile.
- The compiler receives gross = the agreement's, quoteBaseUnits = the live
quote, and splitNet. So the reservation must cover the GROSSED-UP total,
and economics can hold the operator's legs to its quote floor.
- Without an agreement nothing changes: one leg, gross = the quote.
Tests: 4 new, trace 39/39, and the files typecheck. 8 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
added a commit
that referenced
this pull request
Sep 24, 2026
…ntation shows the economics refusal stage This merges #356's R15 binding, which also brings #351's quote to the splitter. Two presentation changes: - The refusal whitelist gains the seam's new "economics" stage. A missing unit gross, a gross below the quote, and an agreement refusal render as refused, Layer C, never a re-quote. The node is named, and economics' code is shown as a code. - The `execution` doc now says its inputs are the CALLER's content, sealed as what was agreed, not facts PCC verified: render them as data. This is the doc nuance noted after the self-review of #357. The trace's tail conflict (both sides appended a describe block) is resolved by keeping both. Tests: trace 58/58 (1 new) and the files typecheck. 2 mutation checks, both killed: the stage not whitelisted, the code not shown. 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
…ing) into feat/agent-plans-route
LamaSu
added a commit
that referenced
this pull request
Sep 29, 2026
… rows, one CSD mapping (ChatGPT review of c48af89) The operator's cross-family review (ChatGPT, 2026-09-28) of c48af89 was DO-NOT-SHIP with four blocking findings. All four are fixed here, each with a test that fails without it. - Kernel status is an ALLOWLIST, and only "online" is available. The kernels table's status model is online, offline, maintenance and suspended, and the sweeper can set expired. - offline, maintenance and expired are unavailable/kernel-not-online; - suspended stays operator-suspended; - any other string ("retired", "", "ONLINE") is malformed-live-row. - Tenants. - The tenant option must be a well-formed id; otherwise the caller sees public rows only. An empty string is never a tenant. - A row whose tenant is not a well-formed id is visible to nobody. - Duplicate live rows. A REQUESTED id that appears twice in either loader answer makes that answer malformed. This fails closed, in either order, exactly like any other malformed answer. - A broken second copy counts too: its id is reported even when the rest of the row cannot be read. - Unrequested rows are still ignored. - One call, one mapping. csdForType is called at most once per capability type, so two nodes of one type cannot resolve against two CSDs. - The verdict's resource note: a decimal string over 100 characters is malformed before any BigInt work. Tests: 6 new, R10 53/53, and tsc is clean for the service and its tests. 12 mutation checks: 10 killed. T1 and T2 SURVIVE, and they are an equivalent pair: - validating the tenant option and validating the row tenant each close the tenant finding alone, because a visible scoped row must equal a validated option; - removing both is killed. Both are kept as defense in depth. Out of scope for this PR, and tracked: the verdict's section B/C5 asks that acceptance bind to row versions within one snapshot. That is the accept path (#356/#391), so it follows their first-round verdicts. pcc-composition 8a0f4de0, goal pcc-reconciliation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…before reading the submission (astra, round 2 of #356) astra's review of a16095a (pack B-composition-356-r2b) returned DO-NOT-SHIP under the in-process getter and callback threat model. A (authority): only the REFERENCE to deps.revalidation was captured before the submission was read. A submission getter or a reservation callback could then swap loadCapabilities, loadKernels or csdForType, and R10 would authenticate the caller's own terms against fabricated rows. The three callables are now captured and validated with every other dependency, before any submission property is read. R10 gets only a frozen object of pinned wrappers that call them with their original receiver. C (authority): ctx.principal and ctx.tenantId were copied AFTER the submission was read, so a getter could make the caller the reservation's owner, or switch its tenant. The context is now read once, before the submission. The checks, and the order of refusals, are unchanged. B (digest): tag() encoded a symbol and NOT_DATA alike, and JSON encoded -0 as 0. leaf() now turns a symbol into NOT_DATA and -0 into 0, so the snapshot never keeps two values that encode alike. Both are semantics-preserving. Tests: 7 new. - A: a submission getter, and a reservation callback, swapping all three R10 dependencies both give the baseline outcome. - A: a non-function R10 callable is a wiring fault raised before any submission property is read. - A: method-style (class) loaders keep their receiver. - C: a getter making the caller the owner is still wrong-principal. - C: a getter switching the tenant gives the same refusal as the original tenant (with a control that the tenant matters). - B: symbols and objects snapshot to one NOT_DATA, -0 to 0, and distinct values keep distinct digests. Full gateway suite: 3020 passed, 6 skipped. The capture suites do not load without a built @pcc/verifier dist. Mutations: 6 of 6 killed. S6 (callables not validated) first survived; that exposed a missing test, which was then added. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…code (#356, review round 3) The seam now takes an owned copy of the submission before reading any field: only plain objects and real arrays are walked, through their own data properties, with the Proxy check (which invokes no trap) first. A Proxy or an accessor anywhere refuses the submission as malformed; an object or array with another prototype is never read. The authenticated context's principal and tenant are read the same way. The copy is bounded in depth, value count and list length. The header states the trust boundary: caller inputs are data, the dependencies are trusted server wiring. Tests: a submission getter that rewrites a method-style loader's state, a Proxy submission, getters in node fields, execution inputs and arrays, a class instance, a context accessor, the three bounds and a non-plain array. The earlier read-once tests now assert that the getter never runs. 11 of 11 mutants killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…recondition (#356, review round 4) astra's round-4 review of 8990a11 (SHIP-WITH-FIXES) found one MEDIUM: the no-code copy skipped symbol keys without counting them, and it bounds nothing about an object's width before Reflect.ownKeys enumerates it. That was reproduced first. At 8990a11, a submission with a symbol-keyed input, or with more than 1,000,000 symbol keys, was accepted (2 failing tests). A symbol key now refuses the submission, like an accessor or a Proxy: JSON cannot produce one, and a skipped key goes uncounted. The header now states the size bound as the seam's precondition. The submission is parsed from an HTTP body the gateway caps at 1 MiB (server.ts bodyLimit), and JavaScript cannot count an object's own keys without materializing them, so the width bound lives with the caller. A test also pins the string-key variant, which the value bound refuses. Tests: external-plan-trace 60/60. Mutation: symbol keys skipped again is killed (2 tests fail). 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
Oct 3, 2026
…ion (#357) Pack 133 passed as #356's confirmation (steward #5847), which releases the held merge-up of the accepted-plan stack. A merge-tree simulation of the whole stack over master found one conflict: this one. The conflict was in packages/gateway/src/__tests__/external-plan-trace.test.ts: #357 and #356 each appended a describe block at the end of the file. Both are kept, #357's block first, closed explicitly, then #356's. The seam source (services/external-plan-seam.ts) merged without conflict. Results: external-plan-trace 93/93. Full gateway suite: 3121 passed, 6 skipped. tsc is clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
LamaSu
marked this pull request as ready for review
October 3, 2026 22:39
merged; plain merge, no edits) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
…after #355 merged; plain merge, no edits) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
…am (plain merge, no edits; fresh CI on the green master) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
LamaSu
added a commit
that referenced
this pull request
Oct 4, 2026
…resentation (plain merge, no edits; fresh CI on the green master) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Round 3 (2026-09-29): astra's findings on a16095a (pack B-356-r2b, DO-NOT-SHIP) fixed at 184a0e3
70-composition-356-r3-184a0e39.md.Reconciliation row R9, pure part (goal
pcc-reconciliation; ledger item 27, "no path for an externally authored plan"): the accept seam that chains R10 → R11 → R12. It also carries the composition return contract's end-to-end trace.Stacked on #355 (R10), which is stacked on #351 (R12).
What it does
acceptExternalPlan(submission, { principal, tenantId }, deps)injects every read, so the accept decision is a pure function of (submission, principal, live state).What the agent supplies: only the plan's shape and what it believes each node costs.
What the server decides: every settlement term.
Refusals are typed by stage:
submissionreservation: not-found, not-issued, expired, wrong-principal, wrong-requestrevalidation: the non-currentverdicts. Astaleverdict carries the re-quote.currencyprogram: the claimed program doesn't matchevidence: no evidence contract for the tiercompileThe trace (
external-plan-trace.test.ts)PCC_TRACE_OUT=<path>writes the happy path as JSON. The current run is at/mnt/sparkbulk/pcc-reconciliation/returns/pcc-composition-trace.json.Charter negatives, through the whole seam:
digest-mismatchat consume/api/composeis one planner among many (f2d74b3)submissionFromComposeResponse()restates aComposeResponseas the claims an agent would submit:step-<index>dependsOnbecomes edgesThose claims then go through the same seam, so the composer gets no shortcut:
stalewith a re-quote.1e-7is a malformed claim.proposedproposalsTests: 3 new. 4 mutation checks, each caught by a failing test.
Cross-family review (astra): SHIP-WITH-FIXES, fixed in 190bf9a
Confirmed:
Fixed:
minTierfloor, and a delegate cannot downgrade it.Tests: 23, with 7 new; the fix-driven tests fail against f2d74b3. Mutation checks: seam 10/10 caught, adapter 6/6 caught.
Read-once seam (70f9582), confirmation queued
This applies the pattern that closed #351 and #355.
snapshotSubmission. The program cross-check, R10 and the compile use only that copy.malformed-reservation.submissionDigest, a type-tagged sha256 of the submission exactly as evaluated, andverdicts. The presentation (feat(gateway): PlanPresentation read model for caller-authored plans (product section 5) #357) uses the digest to bind what it displays.N25: execution inputs reach the sealed deal (4f30bb1)
inputsandconstraints: plain JSON, the caller's content (e.g. which document and how many pages), never authority.@pcc/spec'scopyPlanJson, as absent, an owned frozen copy, or the refusal reason.submissionDigestv2 encodes them. Absent,{}and invalid all differ, and invalid evaluates to its reason.{stage:"submission", reason:"invalid-execution-json", fields}. It names EVERY bad field in (nodeId, field) order, before any reservation read.R15 economics binding, option (b) royalties on top (a16095a)
SeamDeps.economics = { unitGross, splitNet }: economics'agreementUnitGrossandnetSplitterFor, bound per request by the route to the agreement and the server's facts. It is read once, and both functions are called with the binding as receiver.economicsstage before the compile: agreement-refused (with economics' code), agreement-unreadable, unit-gross-missing or quote-not-covered.quoteBaseUnits= the live quote, andsplitNet, so the reservation must cover the grossed-up total.Cross-family status: round 2 (the read-once seam + N25 + economics) is queued for the 19:43 PDT codex window at a16095a (#3177).
Stand-ins (labelled in the test)
Not in this PR
Tests
tscis clean.🤖 Generated with Claude Code
https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz