Repository navigation
Conversation
…ance gate Must-close 6: non-zero assurance binds the exact committed program for its CSD tier before funding, and that program must release on what the tier promises. The v3 composition commitment (#327) pins a verificationProgramHash supplied by whoever builds the plan; nothing resolved it from the CSD tier or checked the program against the tier. This is the resolver half of composition's LO-CO-3. - The committed-stage shape the oracle hashes and runs (string predicates over raw event types), hashed through computeVerificationProgramHash's preimage. The document-print-and-mail v4 programs are ported from the evidence golden on #270 (not merged) and reproduce their pins byte-exact (independence 0xd229c8da..., honest-asymmetry 0x6e00cad1...); the same formula reproduces the oracle-confirmed v3 pin 0xe1cac435.... - checkCommittedProgramForTier: every positive stage names an event the tier binds through a non-supporting primitive and that proves an outcome level; some stage proves device_reported or stronger; not-simulated is present; execution_completed is paired with execution_failed absent. The v3 program, which released on printer_job_verified, fails three of these. Tier evidence no stage requires is returned as unenforcedTierEvidence. - resolveAcceptedProgram: one committed program per (CSD, non-zero tier) from COMMITTED_PROGRAM_REGISTRY; none or two fails closed. Only print-and-mail tier2 has one today; tier1 and tier3 have no committed program. - assertAcceptedProgramForTier, the gate: the committed hash must equal the resolved program's hash exactly (tier upgrade or downgrade without its own program is refused), the registry entry must hash to its program, and the program must pass the tier check. Tier 0 takes no program. Finding: against the real CSD, tier2 promises printer_log_captured, photo_captured and photo_anti_spoof_check, and the v4 program requires none of them. They are enforced only if the oracle verifies those primitives itself; that is not visible from public PCC. Tests: 23 new; spec 840/840; tsc clean. Nine mutants (exact hash, supporting evidence, no-level events, not-simulated, tier-zero program, registry hash, tier check at the gate, ambiguous registry, failure pairing) each turn a test red. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS
This was referenced Sep 24, 2026
Merged
… 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
This was referenced Sep 24, 2026
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
…nce review) Evidence's review of #406: fundability is #349's assertAcceptedProgramForTier (committed program hash + tiers 0..T eligible with implemented verifiers), not resolveAcceptedProgram. Comment only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy
This was referenced Sep 24, 2026
…ust seal
Composition's accepted deal pins each registry a funded program's
verifiers check keys against (bus #3391, registry-pins-note.md). Its
compiler needs the (primitiveId, registryId) set server-side, so the gate
that accepts the (CSD, tier) now returns it: assertAcceptedProgramForTier
succeeds with { ok: true, registryPins }.
requiredRegistryPins(evidence, k) walks the funded CSD's tiers 0..k,
the same server-resolved map the gate checks (committed programs hold
stage predicates, not primitives). It takes every registry-backed
primitive (today ident.registered_key) with the registry its params
name. Pins are deduped on (primitiveId, registryId) and sorted by
primitiveId then registryId in code-unit order. A registry-backed
primitive that names no registry cannot be pinned, so the gate refuses
it as registry-not-named, before funding rather than at /settle.
Today's print-and-mail programs name no registry, so they return
registryPins []. The test fixture's fixed tiers now name
pcc.registry.kernel-signing-keys.v1 on ident.registered_key.
committed-program 33 (+2), spec 850, tsc clean. Five mutants are each
killed: a missing registry tolerated, no dedupe, no sort, only tier k
walked, and non-registry primitives counted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Composition's accepted-deal v3 seals each registry pin under the same id rule as its other ids (ID_PATTERN: 1 to 128 printable ASCII characters). requiredRegistryPins accepted any non-empty string, so a CSD tier naming, for example, "pcc.registry.<U+212A>.v1" (the Kelvin sign) passed the gate and would only fail later, at compile. It is now refused at the gate as registry-not-named, before funding (evidence's v3 gate-pin ruling, #4568). Reproduced first: the new test failed at 48a9587 (1 failed | 33 passed: the Kelvin-sign id was accepted). Now 34/34; spec 851/851; tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…ontradictions and the gateway-stamp rule #349 was cut from #345 before cb81284 (deriveContradictions, J4) and ba31ea6 (gateway-stamped events prove no level). Merged, not rebased, so nothing is force-pushed. No conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…try or primitive-index override (E8 F4) E8 F4 (MEDIUM): assertAcceptedProgramForTier accepted a registry and a primitive index as 2nd and 3rd arguments, and the N19 leg only reads verifierStatus. A caller of the public export could pass an index that marks every verifier "live" and fund print-and-mail tier2. Reproduced at c56b0d7 through both the evidence index and the package root. assertAcceptedProgramForTier(input) now takes one argument and always resolves against COMMITTED_PROGRAM_REGISTRY and the built-in primitive vocabulary. The override-taking form is assertAcceptedProgramForTierWith(input, registry, options): exported from committed-program.ts for tests, and not from evidence/index.ts, which now lists its committed-program exports explicitly instead of using export *. The package.json exports map exposes only ".", "./schemas", "./identity" and "./tool-manifests", so a consumer cannot deep-import the module; the doc says so. Tests: the production gate has one parameter and ignores a smuggled registry and a smuggled all-live index; neither the evidence index nor the package root exposes the override form; the explicit export list loses no other export of the module. Existing gate tests move to the test-only form. The pinned programs, their hashes and COMMITTED_PROGRAM_REGISTRY are untouched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…so the checks and the pins cannot see different CSDs (E8 F3) E8 F3 (HIGH): the gate read input.evidence once for the tier lookup, once for eligibility and once for the pins. A getter or proxy could answer three times with three maps: one that passes the program check, one the eligibility lint accepts, and a last one with no registry-backed primitive. Reproduced at c56b0d7 with such a getter: 3 reads, and the gate returned {ok:true, registryPins:[]} for a CSD whose eligible map names the kernel registry, so the deal would have sealed no registry pin. The gate now reads input.csd, input.tierKey, input.committedProgramHash and input.evidence exactly once each, at entry. evidence is materialized with JSON.parse(JSON.stringify(evidence)) inside try/catch and must come out as a plain non-null, non-array object. Tier lookup, eligibility, the program check and pin extraction all use that one snapshot. Input that is not plain JSON data (a throwing getter, a cycle, a BigInt, a non-object evidence, a non-object input, a csd or tierKey that is not a string) returns the new refusal code invalid-input instead of throwing. csd and tierKey must be primitive strings because an object with a stateful toString would be a second channel for the same race (it could pass as tier0 on one coercion and tier2 on the next). Tests: all four fields are read exactly once on every path through the gate (funded, tier0, no program, wrong hash); the three-map getter is evaluated on its first answer only and equals the first map evaluated alone, pins included; a nested getter, a proxy and a toJSON are each consulted once; the table of non-JSON evidence and malformed inputs is refused as invalid-input; null- prototype and class-instance evidence, which are plain data, still fund. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…fore it funds tier k (E8 F2) E8 F2 (HIGH): computeCsdEligibility starts eligibleTier at 0 even when tier0 is absent (original design in 1c475e9: tier 0 is the always-listable floor), then advances through tier1 and tier2. The gate trusted that result, so a CSD with no tier0 could be funded at tier2. Reproduced at c56b0d7: delete tiers.tier0, gate tier2, and the gate returned ok:true with the kernel registry pin. The same happened with tier0 present but null, an array, a string or a number. In the gate, after the program hash check and before eligibility, require the exact keys tier0 ... tier<k> as own keys of the snapshot, each a non-null, non-array object. A missing key returns {ok:false, code:"tier-not-in-csd", reasons:[...]}, one reason per missing tier, in order. The funded key must also be exactly tier<k> (a key like tier02, which tierNumber reads as 2, would otherwise make the program check and the eligibility check look at different tiers). eligibility.ts is not touched: its other callers are reported in the triage file. Tier 0 itself still needs no declaration to be funded. It is the permissionless floor and a CSD with no evidence block is tier-0 only by the eligibility rule, so requiring tier0 there would close the on-ramp. This is a decision for the lane to confirm; a test pins it. Tests: delete tier0 (the reviewer's repro) with the lint's own eligibleTier as a positive control; tier0 and tier1 as null, array, string and number; several missing tiers named in order; an inherited tier0 through Object.prototype is not declared; a non-exact funded key is refused; only tiers 0..k are required, so tier3 may be absent. Two existing tests change: a missing tier1 is now tier-not-in-csd with its reason instead of tier-not-eligible, and the missing funded tier gains its reason. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…ted deal's digest grammar (E8 F5)
E8 F5 (MEDIUM): the gate lowercased the whole committed hash before comparing, so
HASH.toUpperCase() ("0XD229...") passed the gate, while the accepted deal's
parser (DIGEST_PATTERN in csd/composition-commitment.ts: a literal lowercase 0x
and 64 hex digits) refuses it. Reproduced at c56b0d7. It did not create two
funded commitments, because the deal refuses 0X, but it made pre-funding
acceptance inconsistent with what the deal would accept.
DIGEST_PATTERN is exported, so the gate imports it instead of mirroring it: the
committed hash must match it first, and only then is it compared after
lowercasing the hex digits alone. A hash that does not match the grammar is a
program-hash-mismatch, the same code as any other wrong hash.
Tests: a table of 16 inputs (the pinned hash, uppercase and mixed-case digits,
0X with lowercase and uppercase digits, no prefix, 63 and 65 digits, a non-hex
digit, a fullwidth letter, leading and trailing space, a trailing newline, an
embedded NUL, the empty string, another valid digest) where the gate accepts
exactly when the accepted deal's own validatePlan accepts the hash and
deriveCapabilityContractRoot canonicalizes it to the pinned hash's root; the
reviewer's HASH.toUpperCase() row; non-string hashes, including objects that
coerce to the hash, are a mismatch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…dependent provenance (E8 F1) E8 F1 (HIGH): the tier check never looked at allowedProvenance. A program whose "event-present-independent" mail stage allowed only ["operator_self_report"], registered for tier2 under its own hash, produced no violations and the gate funded it. The word "independent" was established by a predicate label and an unchecked string allowlist the program's author controls. Reproduced at c56b0d7 for the reviewer's repro and for seven variants (absent, empty, duplicate, superset, and stray provenance on an event-present, an event-absent and a not-simulated stage): every one gave no violation and a funded tier2. New evidence-owned data: INDEPENDENT_PROVENANCE, a frozen null-prototype map holding exactly { courier_pickup_confirmed: ["independent_carrier_scan"] }, the only independent provenance the pinned programs use. A label counts as independent only if the evidence lane lists it there; the doc says the oracle must still authenticate the label's source when it evaluates the stage. checkCommittedProgramForTier now adds: - independence-not-established (stageId, eventType) unless an event-present-independent stage has an allowedProvenance that is a non-empty array of distinct strings, each listed in INDEPENDENT_PROVENANCE for the stage's event type (own-property lookup; an indexed loop so a sparse array's hole is not a label); - provenance-on-dependent-stage (stageId) for any other predicate that carries allowedProvenance, since the field means nothing there. An unknown predicate, or an independent stage with no usable event type, keeps its existing single violation. INDEPENDENT_PROVENANCE is exported from evidence/index.ts. The pinned data is untouched: PRINT_AND_MAIL_INDEPENDENCE_PROGRAM and PRINT_AND_MAIL_HONEST_ASYMMETRY_PROGRAM, their hashes (0xd229c8da..., 0x6e00cad1...) and COMMITTED_PROGRAM_REGISTRY. Both programs still pass the tier check against the real tier2; a test pins the literal hashes and the registry. Tests: the reviewer's repro through the check and through the gate (registered via the test-only registry override); twelve attack variants, each refused with exactly one violation by the check and by the gate; event-specificity (the carrier label does not make execution_completed independent); stray provenance on three predicates, including empty and null, and on the honest-asymmetry mail stage; an explicit undefined is not stray; violations keep stage order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
…he tier-zero path Found while reviewing fixer-lima's E8 round: tierNumber used /^tier(\d+)$/, so "tier00" parsed as tier 0. resolveAcceptedProgram then returned the tier-zero result, and the gate funded it with tier0's pins before its exact-key check ran. tierNumber now accepts 0 or a decimal without a leading zero; a non-canonical key finds no committed program and is refused. Same class as E8 F5 (one accepted spelling per input). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qb6kQhDYRwUDd6AVes3Fwx
This branch has not been 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.
Update 2026-09-24 @074e3231: the gate refuses tiers that cannot be verified end to end (board N19)
assertAcceptedProgramForTiernow takes the funded CSD's wholeevidencemap in place of onetier.tier-not-eligible, plus reasons, unless tiers 0..T are eligible undercomputeCsdEligibilitywithrequireImplementedVerifier. In that mode a stub verifier caps its tier.tier-not-in-csd.Consequence: as authored, document-print-and-mail is eligible only at tier 0:
ident.registered_key, whichreceipt.kernel_signedandmachine.execution_logdepend on;The tier2 program
0xd229c8da…stays pinned but cannot be funded until the CSD and its verifiers are fixed. That is N19's "or those tiers made unfundable", derived instead of hand-listed.API change for composition (feat(spec): accepted-plan compiler — composition half of MS-01 (R12) #351/feat(gateway): accept seam for externally authored plans + end-to-end trace (R9 pure part) #356): pass
evidence: csd.evidencein place oftier: csd.evidence[tierKey].Tests: committed-program 31/31 (8 new); spec 848/848; tsc clean; 7 mutants killed.
The companion ladder data is in feat(spec): per-(CSD, tier) evidence ladders compiled from the CSD (N19, for N33) #383.
What this closes (technical pack §3, must-close 6; the resolver half of LO-CO-3)
Non-zero assurance must bind the exact committed program for its CSD tier before funding, and that program must release on what the tier promises. Today the v3 composition commitment (#327) pins a
verificationProgramHashsupplied by whoever builds the plan. Nothing resolves it from the CSD tier or checks the program against the tier.Contents
Committed-stage shape. This is the shape the oracle hashes and runs: string predicates over raw event types. It is hashed with
computeVerificationProgramHash's preimage.document-print-and-mailv4 programs are ported from the evidence golden on feat(gateway): v3 evidence-signing Step-6 — async split + final-package (audited; gate CLOSED; HOLD for auth-P0) #270, which is not merged.0xd229c8da…, honest-asymmetry0x6e00cad1….0xe1cac435….checkCommittedProgramForTier(program, csdTier)requires:role: supporting, and that proves an outcome level (per feat(spec): evidence levels -- submitted / device_reported / inspected_output (must-close 5) #345);device_reportedor stronger;not-simulatedis present;execution_completedis paired with event-absentexecution_failed.The v3 program that released on
printer_job_verifiedfails three of these. The function also returnsunenforcedTierEvidence.resolveAcceptedProgram(csd, tierKey)gives one committed program per (CSD, non-zero tier). Finding none, or two, fails closed. Only print-and-mailtier2has one;tier1andtier3have no committed program.assertAcceptedProgramForTieris the pre-funding gate:Finding for evidence, oracle and composition
Against the real CSD,
tier2promisesprinter_log_captured,photo_capturedandphoto_anti_spoof_check, but the v4 program requires none of them. They are enforced only if the oracle verifies those primitives itself, which public PCC can't see. The question has been put to the oracle. Separately, the public vocabulary marks every tier-1+ print-and-mail primitive exceptreceipt.kernel_signedasverifierStatus: "stub".Not in this PR
Tests
tscclean.Stacked on #345.
🤖 Generated with Claude Code
https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS