Skip to content

feat(gateway): PlanPresentation preview completeness, evidence strength, expiry, honest unknowns (product item 6) - #434

Merged
LamaSu merged 10 commits into
masterfrom
feat/plan-preview-completeness
Oct 6, 2026
Merged

LamaSu merged 10 commits into
masterfrom
feat/plan-preview-completeness

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 29, 2026

Copy link
Copy Markdown
Owner

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 stays pcc.plan-presentation.v1.

  • Evidence strength per node: nodes[].assurance = { tier, program, evidence: [{evidenceTypeId, tier}] }.
    • Only for compiled and sealed nodes.
    • Taken ONLY from the intact, bound plan's sealed canonicalPlan.assurance, never from the proposal. A forged assurance-shaped field in a submission has no effect (tested).
  • Expiry: expiry = { reservationExpiresAt (ISO 8601), reclaimAt? }, from an optional server-truth reservation: { reservationId, expiresAt }.
    • It must name THIS submission's reservation.
    • A mismatched, malformed or unrenderable window makes the presentation invalid with plan-binding: mismatched server inputs never mix.
  • Honest unknowns: 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. With external-plan-trace: 73/73. One existing whole-object assertion gained unknowns. tsc is clean.
  • Mutation checks: 7, all killed.

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

LamaSu and others added 9 commits September 28, 2026 19:45
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
@LamaSu
LamaSu changed the base branch from feat/plan-presentation to master October 4, 2026 02:21
…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
LamaSu marked this pull request as ready for review October 4, 2026 13:09
@LamaSu
LamaSu merged commit d7ffcf7 into master Oct 6, 2026
16 of 20 checks passed

This branch had an error being deployed

1 failed deployment
trusted-checks — b9cfeb79 Deployed Oct 4, 2026 by LamaSu via post-verdicts #128
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