Repository navigation
feat(gateway): PlanPresentation preview completeness, evidence strength, expiry, honest unknowns (product item 6) - #434
Merged
Conversation
Adds three additive facts PlanPresentation was missing, per the product pack's "never silently substitute mock data" invariant: - PlanNodePresentation.assurance (tier, program, evidence type+tier), present only for compiled/sealed nodes and taken ONLY from the intact, bound plan's canonicalPlan.assurance -- never the proposal, so a caller cannot inject evidence strength it did not earn. requirementId is dropped (non-semantic). - PresentPlanArgs.reservation + PlanPresentation.expiry: an optional reservation window (reservationId, expiresAt unix seconds). It must name the submission's own reservation; a mismatch or malformed window is invalid/"plan-binding", following the file's existing invalid() path. reservationExpiresAt renders as ISO 8601; reclaimAt mirrors preview.reclaimAt once compiled. - PlanPresentation.unknowns: string[], always present (today exactly ["time-estimate"]), so a UI renders "unknown" instead of inventing a duration. Schema stays "pcc.plan-presentation.v1"; all new fields are additive and optional except `unknowns`, which is required but new, so one existing full- object equality in external-plan-trace.test.ts (the malformed-submission case) was updated to include it -- no other existing assertion changed. New tests in plan-presentation-preview.test.ts build real presentations with presentPlan over the real seam (acceptExternalPlan), covering: assurance equals canonicalPlan.assurance exactly incl. evidence order; proposed/stale nodes carry no assurance; a forged submission cannot inject assurance; expiry appears only with a matching reservation; mismatched or malformed (-1, 1.5, "x") expiresAt is invalid/plan-binding with no nodes shown; unknowns is ["time-estimate"] in every state. implementer-echo (lane pcc-composition, feat/plan-preview-completeness, base 1b5e77e / #357). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…and a merge-proof pin This is the owning lane's review of b1a2bf4 (implementer-echo). - expiresAt was bounded only by MAX_SAFE_INTEGER, but a Date renders at most 8.64e15 ms. A larger "valid" expiry threw a RangeError inside toISOString and fell into the generic catch, where it was reported as malformed-outcome. It is now bounded by what a Date can render, so an unrenderable window is a plan-binding refusal like any malformed one. The test has 2 new cases. - The pinned PRINT_ASSURANCE constants are compared order-insensitively. This branch predates #351's 9d7686a, which sorts evidence, so an order-pinned literal would break when the stack merges up. Presentation order is still pinned to the canonicalPlan's by the structural test. Tests: 15 preview tests, and 73 with external-plan-trace. tsc is clean. 7 mutation checks, all killed: evidence reordered; the tier or program not the sealed one; another reservation accepted; the Date bound removed; unknowns missing; reclaimAt never attached. pcc-composition 8a0f4de0, goal pcc-reconciliation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…view-completeness #434 is based on #357. This brings in 3475654 (M1: the execution contract is bound to its planHash, binding and unit), 22fe64d (M2: verdicts are an exact cover of the nodes and a refusal's two lists agree) and 2a6a71a (their mutation-driven test additions). The merge is automatic; the two branches' hunks in plan-presentation.ts and in external-plan-trace.test.ts do not overlap. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…ed (#434, MEDIUM: duplicate case) #434 (astra r1, MEDIUM), first half: replacing both verdicts of an accepted outcome with the same current verdict passed validation and stayed Layer B/sealed at 6248cf9 (the reviewer's second case; reproduced there). The merge-up of #357's M2 (22fe64d, an exact one-to-one cover of the nodes) already refuses it as a malformed outcome, so no source change is needed here. This test pins it on this branch. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…wn (#434, MEDIUM) #434 (astra r1, MEDIUM), second half: verdict validation checked membership, count and status, but never compared the acceptance-time live terms with the accepted deal's node, so a verdict could say a live price of 0.01 beside a sealed deal of 6.50 (reproduced at 6248cf9: the presentation stayed Layer B/sealed; no digest recomputation was needed). For every accepted node, the node's current verdict must now agree with the deal, else the whole presentation is invalid (plan-binding, Layer C, no nodes): - price, in exact base units: the shown decimal parses (no float; no over-precision, no leading zero) to exactly the resolved amount, and the resolved amount is the unit's gross; when an economics agreement grossed the unit up (royalties on top) it may not exceed the gross, since the seam refuses a gross below the quote and the plan does not record the quote; - currency; operator (lowercased); tier (the resolved tier, and the offered tiers shown include it); - the capability's type, csd and matched digest (digest lowercased), the same view's other terms. The verdict reader now keeps R10's exact resolved tier and gross for current verdicts; a verdict missing either, or with the wrong type, is a malformed outcome. Beyond the spec's four named terms, the capability-identity trio and the offered tiers are checked too: they are shown beside the same deal, and honest seam output satisfies all of them. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…completeness Merges 384e097 (a test pinning that M1 keeps presenting an economics-agreement deal). Tests only on the #357 side; no conflicts. On this branch the same deal also passes the live-terms check. Agent: implementer-india Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…mitted quote (#434, confirmation round) astra's confirmation of 902c981 left one MEDIUM open. For a deal with an economics agreement, the plan keeps only the unit's gross, not the operator's quote. The live-terms check could then only require quote <= gross, so a consistent downward rewrite of a verdict's quote was still shown beside the sealed deal. Reproduced first: the reviewer's case, presented sealed, still showed the live terms with the rewritten quote. A sealed presentation now shows only the terms the deal commits, so a sealed agreement-backed node carries no live view. A compiled (unsealed) agreement deal still shows the live quote, and a deal with no agreement keeps exact price equality. The <= bound stays as a refusal of a verdict that cannot be the accepted one. The earlier agreement test now expects the live quote only on the compiled presentation. Tests: preview, trace and revalidation 148/148. Mutations: 3/3 killed. Full gateway suite: 3075 passed, 6 skipped. The capture and capture-3d files fail to load locally; that is pre-existing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0171FftEAfqjwbJHowVTqAbz
…e-checks again (#434) 77f4934 made a current verdict on a sealed agreement-backed deal skip the live view, but in the same if/else chain: the current-but-skipped case fell through to the final branch and read v.reason, which a current verdict does not have. tsc refused it (plan-presentation.ts:707, TS2339), so the gateway does not build at 77f4934. As a stacked PR, #434 ran dashboard CI only, so no CI caught it; the lane's merge-order fold build did. The current case now has its own branch. The behaviour is unchanged except that no reason key (undefined) is attached to a current node; the test pins that, and fails on 77f4934. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
…ain merge, no edits; #357 landed, retarget to master) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
…completeness (plain merge, no edits; fresh CI) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSBxBEX3sn9DPSqihyjuZE
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 06:45 — with
GitHub Actions
Failure
LamaSu
marked this pull request as ready for review
October 4, 2026 13:09
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 13:10 — with
GitHub Actions
Failure
This branch had an error being deployed
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.
Draft, stacked on #357 (PlanPresentation). Product pack section 5, item 6: "Preview of spend, time, evidence strength, licensing/payout obligations and expiry."
The preview already shows spend (gross, fee, net, payouts by recipient), tier per node, reclaimAt, and the licensing commitments (
deal.agreementHash, economic and rights terms hashes). This PR adds what was missing, additively. The schema stayspcc.plan-presentation.v1.nodes[].assurance = { tier, program, evidence: [{evidenceTypeId, tier}] }.canonicalPlan.assurance, never from the proposal. A forged assurance-shaped field in a submission has no effect (tested).expiry = { reservationExpiresAt (ISO 8601), reclaimAt? }, from an optional server-truthreservation: { reservationId, expiresAt }.invalidwithplan-binding: mismatched server inputs never mix.unknowns: ["time-estimate"]in every state. PCC has no trusted source for a duration, so a UI renders "unknown" and never invents one (product invariant 3).The route that passes the store's reservation window comes with the R9 route family (#391) after its first verdict.
Tests
plan-presentation-preview.test.ts: 15 tests. Withexternal-plan-trace: 73/73. One existing whole-object assertion gainedunknowns. tsc is clean.Written by a sonnet subagent (implementer-echo, b1a2bf4) to the lane's spec. The lane's review (6248cf9) bounded expiresAt to what a Date can render, and made the pinned test survive #351's evidence sort at merge-up.
pcc-composition 8a0f4de0, goal pcc-reconciliation.
🤖 Generated with Claude Code