Skip to content

feat(spec): per-(CSD, tier) evidence ladders compiled from the CSD (N19, for N33) - #383

Draft
LamaSu wants to merge 5 commits into
masterfrom
feat/evidence-tier-ladders
Draft

LamaSu wants to merge 5 commits into
masterfrom
feat/evidence-tier-ladders

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Update 2026-09-24 @9cfdafd8

  • The oracle mirrors the ladder byte-for-byte (0xe9cf2a2f…0560) and grants a tier only when every listed primitive is verified. It accepts the empty tier 0 and mirrors the tier-adds-no-event-type and tier-drops-requirement refusals.
  • EvidenceVerifier takes the CSD's ladder through verify(bundle, { tierRequirements }), so a non-FDM bundle is no longer judged by the FDM default. A claimed tier the ladder does not define fails closed as tier_requirement_defined; before, it passed silently. 4 new tests, 2 mutants killed; verifier 589/589.
  • The eligibility text says what it does. A bad reference in tier k caps the CSD below k, not at 0 (adk feat(spec): planProvenance, the deterministic provenance planner (ADK D2) #406). There is a new pinning test, and evidence-primitives passes 42/42.

Summary

Board N19, which N33 depends on. The oracle's /settle now recomputes the achieved tier from a kernel-signed bundle's authenticated event types, under the funded capability's ladder (private 88ea27b). The only public ladder was DEFAULT_TIER_REQUIREMENTS, which is FDM-shaped. A print-and-mail bundle has no gcode_hash_verified, so it failed even tier 0. This PR publishes each CSD's own ladder, compiled deterministically from its evidence tiers. The oracle mirrors it by value and checks its service-pinned ladder against the digest.

  • compileTierLadder(resolvedCsd) → TierLadderV1, one entry per tier:
    • requiredEventTypes: AND of OR-groups, the DEFAULT_TIER_REQUIREMENTS shape, so every existing consumer accepts it.
    • nonEventRequirements: required fields that no event can prove.
    • primitives: the list the oracle runs registered verifiers over. Every listed primitive is verified, supporting ones included.
  • The ladder carries the CSD's capabilityContractDigest. It is committed with computeTierLadderDigest: 0x + sha256 of the canonical ladder, with schemaHash: "tier-ladder/v1".
  • achievedTierFromEventTypes: the reference monotone closure. Tier T counts only if tiers 0..T are all met.
  • Pinned: document.print-and-mail → 0xe9cf2a2f9f6dcc355a5c71cd503b7598c0cbf1b58429e452adfafe6cf0fe0560. The full ladder is in tier-ladder.vectors.json, and the CSD digest is sha256:35560b16…c331.
tier required event types
0 none (self-attested)
1 execution_completed, printer_job_verified, printer_log_captured
2 tier 1 + photo_captured, photo_anti_spoof_check, courier_pickup_confirmed
3 tier 2 + courier_delivery_confirmed

Fails closed

Compilation throws TierLadderError on:

  • tiers that are not contiguous from tier0, or an unknown evidence key;
  • a primitive bound to evidence its tier does not require;
  • a tier that drops a lower tier's requirement;
  • a tier that adds no event type over the tier below. An events-only recompute could not tell those two tiers apart.

Without the last rule, the eight legacy built-in CSDs compiled to empty sets that any bundle would meet at tier 3. Their required[] lists field names, not event types. They now get no ladder above tier 0, which matches computeCsdEligibility. A role: "supporting" flag never removes a bind, so relabelling evidence cannot weaken a tier (N19's done-when).

What this does NOT change

  • Nothing about funding. Separately: under the public eligibility rule, computeCsdEligibility caps print-and-mail at tier 0 as authored, even in report-only mode. The reasons:
    • tiers 1–3 miss the ident.registered_key dependency of receipt.kernel_signed and machine.execution_log;
    • tier 2 has no Family-G (human-attestation) primitive;
    • tier 3 lists four primitives whose tierSupport excludes tier 3.
  • In oracle-enforcing mode, six tier-2 verifiers are also still stubs: machine.execution_log, artifact.hash, capture.photo_nonced, fresh.challenge_bound, confirm.target_system, and ident.registered_key by dependency.
  • photo_anti_spoof_check has no producer in the repo.

The funding-gate consequence is proposed separately, on #349.

Test plan

  • tier-ladder.test.ts 18/18. It covers the pinned vector and digest, the tier-2 promise, supporting-flag immutability, closure on a non-cumulative ladder, every fail-closed code, and legacy CSDs.
  • spec 815/815; tsc --noEmit clean.
  • 11 mutants, all killed: each compile rule, closure, any-group, digest canonicality, malformed entries, and the committed supporting flag.
  • Secret scan of the range: 0 findings.

🤖 Generated with Claude Code

https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS

…he tier sets /settle recomputes from (N19)

The oracle's /settle recomputes the achieved tier from a kernel-signed
bundle's authenticated event types under the funded capability's ladder
(N33). The only public ladder was DEFAULT_TIER_REQUIREMENTS, which is
FDM-shaped, so a print-and-mail bundle (no gcode_hash_verified) failed even
tier 0.

compileTierLadder turns a CSD's own evidence tiers into that ladder,
deterministically:
- requiredEventTypes: AND of OR-groups, the DEFAULT_TIER_REQUIREMENTS shape.
- nonEventRequirements: required fields that events cannot prove.
- primitives: the list the oracle verifies.
The ladder carries the CSD's capabilityContractDigest and is committed with
computeTierLadderDigest (0x sha256, schemaHash tier-ladder/v1).

The document.print-and-mail ladder is pinned in tier-ladder.vectors.json and
PINNED_TIER_LADDER_DIGESTS as 0xe9cf2a2f...0560. Its tier 2 requires
photo_captured, photo_anti_spoof_check, printer_log_captured and
courier_pickup_confirmed as events. A role:"supporting" flag never removes a
bind, so relabelling cannot weaken a tier.

Compilation fails closed on:
- non-contiguous tiers or an unknown evidence key;
- a primitive bound to evidence its tier does not require;
- a tier that drops a lower tier's requirement;
- a tier that adds no event type over the tier below, which the recompute
  could not tell apart.
The last rule is why the eight legacy built-in CSDs, whose required[] lists
field names, get no ladder above tier 0. Without it they compiled to empty
sets that any bundle would meet at tier 3.

achievedTierFromEventTypes gives the reference monotone-closure semantics.

Tests: tier-ladder.test.ts 18/18; spec suite green; tsc clean. 11 mutants,
one per rule plus closure, any-group, digest canonicality, malformed entries
and the committed supporting flag, all killed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS
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
…ladder, and an undefined tier fails closed (N19)

The public EvidenceVerifier always used DEFAULT_TIER_REQUIREMENTS, which is
FDM-shaped: gcode_hash_verified at every tier. So a print-and-mail or
pipetting bundle failed every tier (refvertical's lab.md Q1).

- verify(bundle, { tierRequirements }) now takes the funded capability's
  ladder, compileTierLadder(csd).tiers from @pcc/spec (#383). Without it the
  FDM default applies exactly as before.
- A claimed tier the requirements in force do not define was silently
  skipped. It is now a critical tier_requirement_defined finding.

Tests: evidence-verifier-ladder 4/4, on a real print-and-mail bundle:
- the default fails tier 1 on gcode_hash_verified;
- the CSD ladder meets all 3 tier-1 requirements;
- tier 2 without the photo fails on photo_captured and
  photo_anti_spoof_check;
- an undefined tier fails closed.
Verifier 589/589; tsc clean. 2 mutants killed (ladder ignored, undefined
tier passing).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS
… caps the CSD below k, not at 0

adk's planner (#406) found that the lint's reason text and header rule (b)
said an unknown or reserved reference, or a free-text-only tier, "caps at
tier 0". The aggregate has always capped below the tier that holds it
(eligibleTier = k-1): tiers below do not depend on it, and the ascent is
monotone. The aggregate is the rule (my answer #3174), so the header and the
three reason texts now say "caps the CSD below tier k".

The phrases callers match on are kept ("unknown primitive ...", "legacy
free-text required[]"), so #406's and the existing tests are unaffected. A
new test pins the rule: an unknown primitive at tier 2 gives eligibleTier 1,
with the exact reason.

Tests: evidence-primitives 42/42; spec suite green; tsc clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117ows6894R3n6YQXBCRahS
LamaSu and others added 2 commits October 12, 2026 18:20
Conflicts resolved by gpt-6.1-sol (codex, reasoning ultra); reviewed by Claude Opus 5.5 (reviewer-alpha).

- packages/spec/src/evidence/index.ts: master's barrel kept whole (measurement-profile, the principal-id comment and export) plus #383's tier-ladder comment and export; nothing dropped from either side.
- packages/verifier/src/evidence-verifier.ts: step 3 keeps master's accepted-tier rule and both assurance_tier_accepted findings verbatim (N118/E11e: the bundle's unsigned assuranceTier is never read) and looks #383's ladder, options.tierRequirements ?? DEFAULT_TIER_REQUIREMENTS, up by the ACCEPTED tier; an accepted tier the ladder does not define is the critical tier_requirement_defined finding.
- packages/verifier/src/__tests__/evidence-verifier-ladder.test.ts (#383's test, adapted): the four original tests pass acceptedTier instead of the bundle's tier, and regression tests (a)-(d) pin accepted-tier selection, the missing-accepted-tier failure, the undefined-tier failure and the default-table behaviour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116fiVRS9gDE1HrCECyjVR9

This branch had an error being deployed

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