Repository navigation
fix(verifier): neither an event's unsigned id nor the events' order decides EvidenceVerifier's verdict; docs: they are uncommitted metadata (E11b/E11c, stacked on #341) - #560
Merged
LamaSu merged 18 commits intoOct 6, 2026
Conversation
…ata of a verified bundle (E11b LOW) E11b (astra on #341 @f249eb48, SHIP) left one non-blocking LOW. The verifier hashes only type, timestamp, source and payload, but returns each event's unsigned `id`, in input order. So the same signed bundleHash yields differently identified or ordered results and archives. It cannot create a false binding or a wrong settlement, because neither participates in any acceptance decision. The review offered two follow-ups: document both, or drop `id`. This takes the first, which changes no behaviour for consumers that archive or display ids: - the header gains "WHAT THE RESULT DOES NOT COMMIT": the bundle commits the SET of event hashes, and id and order are not committed, so identify an event by its hash and order by committed fields; - the result type's doc points to it; - a test pins the property: relabelled ids plus a reversed order still verify against the same bundleHash, and come back exactly as given. Stacked on #341 (feat/evidence-subject-binding @f249eb48). spec 50 files, 1212 tests; tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…ecides EvidenceVerifier's verdict (E11c HIGH) E11c (astra on #560 @4ad48f4f): DO-NOT-SHIP. The documentation of uncommitted metadata was correct, but a known consumer relied on both uncommitted properties. Both were REPRODUCED at 4ad48f4 with real hashes (hashEvent, hashBundle), so the bundle and event integrity checks pass. 1. Step completeness fell back from the committed payload.stepId to the unsigned event id. A workflow_step_completed event without payload.stepId: verified with id "unrelated", the verdict is INVALID (the declared step has no trace); relabelling ONLY the id to "step-1" gives VALID. Same bundleHash, same event hashes. Fix: a trace comes only from a committed string payload.stepId; the id never counts. 2. Lifecycle selection used Array.prototype.find, so the first match by array position won. A completion before the start plus one after it gave INVALID or VALID depending on the order. The power summary checked depended on order too. Fix: selectByCommittedOrder picks by committed fields (timestamp, then hash): the LATEST start and the EARLIEST completion, so a positive duration means every completion follows every start, in any order; and the earliest power summary. An unparseable timestamp sorts where the duration check fails closed. With one start and one completion, the behaviour is exactly as before. The LOW: "SET" misstated the commitment, because hashBundle keeps duplicates. subject-binding.ts now says "sorted MULTISET", and its test asserts that a duplicated event changes the bundleHash. New evidence-verifier-uncommitted.test.ts: relabel invariance with a VALID positive control on a committed stepId, three orders of a pre/post-start completion, and two orders of power summaries. All three tests fail at 4ad48f4. verifier 30 files, 595 tests, tsc clean. spec 1212. gateway 3734 passed, plus the known completion-real-tier load flake (passes alone). gateway tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
A completion between two starts fails closed in every order, and two power summaries with the same timestamp are told apart by hash, not position. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…nding itself (E11c) Its invalid result also came from tier 1's missing power summary, so mutant M7 (the latest completion counts) survived. It now asserts execution_duration_positive fails. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…ed state, never a worker's bundle or proof (E11d HIGH, N118) E11d (astra on #560 @eac51ba5): DO-NOT-SHIP. The id and order routes are CLOSED, but the bundle's unsigned assuranceTier still chose the evidence required. REPRODUCED at eac51ba: a tier-1 bundle without a power summary is INVALID, and the same signed bundle relabelled assuranceTier 0 is VALID (same bundleHash). The steward asked to fix the PROPERTY: the verifier reads nothing unsigned. What EvidenceVerifier consumes, enumerated: - bundleHash, recomputed from the events: committed; - each event's type, timestamp, source and payload, via the verified event hash: committed; - event.id: labels findings only, display (E11c already removed it from decisions); - events' order: no longer decides anything (E11c); - bundle.assuranceTier: NOT committed, so it now chooses nothing; - id, jobId, stepId, kernelId, createdAt, kernelSignature: never read. Fix: - EvidenceVerifier: the tier's required evidence comes from options.acceptedTier, which the caller takes from authenticated state (the accepted plan or program, or its own task record). - A bundle that claims another tier is rejected (critical "assurance_tier_accepted"). - Without an accepted tier, or with one that is not 0-3, the verdict fails closed, and no requirement is chosen at all. - TMPValidatorBridge: validate(envelope, context) takes the caller's ValidationContext { acceptedTier }. - sensor_evidence passes it to the verifier. - The bittensor and oracle routes used the worker's proof.requiredTier, the same class. Now they use the accepted tier and refuse a proof that is missing one or claims another. - tmp-tasks route: a TMP task records its acceptedTier at creation, from the poster's assuranceTier (validated 0-3), and the proof route passes the task's own value. MilestoneProcurement gains acceptedTier?: AssuranceTier. Tests: - ONE generic test mutates every uncommitted bundle field (plus an injected unknown one), every uncommitted event field, and the order, on a VALID and an INVALID sealed bundle (over 40 variants each). The verdict never changes. - Without an accepted tier: invalid, and identical for every claimed tier. A mismatching claim is rejected, and a non-tier acceptedTier is refused. - E11d's reproduction now holds. - Bridge: missing or mismatching tiers are refused on every route. - Existing bridge and E11c tests pass the accepted tier their scenarios imply. verifier 30 files, 607 tests. spec 1212. gateway 207 files, 3735 tests. tsc clean for gateway, verifier and spec. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…ted tier's requirements (N118) Mutant M8 (the requirements follow the bundle's own tier) survived, because the mismatch rejection already made the result invalid. The test now pins that the tier_requirement findings equal the accepted tier's, whatever tier the bundle claims. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…-bound task state; every pipeline enforces the tier or refuses; the verifier reads no unsigned field (E11e, N118) E11e (astra on #560 @cde080d3): DO-NOT-SHIP, with 2 HIGH and 1 MEDIUM, all REPRODUCED at cde080d: - MEDIUM: the verifier still read the unsigned bundle.assuranceTier, in the mismatch check. With assuranceTier in the generic mutation loop, a VALID bundle became INVALID. - HIGH 1: the worker chose the pipeline, and an empty-path Merkle proof passed. merkle_commitment with merkleRoot === leaf, path [] and indices [] validated TRUE against a tier-3 task requiring sensor_evidence. - HIGH 2: the task tier was not owner-bound. Two creations for one milestone, at tier 3 then tier 0, both returned 201, and the task read tier 0. The steward restated N118 end to end (#6161): everything that decides a verdict comes from authoritative, owner-bound task state, every pipeline enforces the tier or refuses, and the verifier reads no unsigned field. Fix: - EvidenceVerifier: bundle.assuranceTier is not read at all. The required evidence is the accepted tier's only, and a missing or invalid accepted tier fails closed. - TMPValidatorBridge: ValidationContext gains proofType, the task's pipeline. A submission without it, or on another pipeline, is refused (task_pipeline) before anything is verified. zk_proof and merkle_commitment now REFUSE (tier_unenforceable): every tier 0-3 requires evidence events those proofs cannot show. That also ends the empty-path Merkle accept. The two unreachable private validators are removed, and the constructor is unchanged. - tmp-tasks route: task state is authoritative and owner-bound. - An injected TmpTaskRouteOptions.milestoneOwner(milestoneId) names the milestone's poster. Creation needs an authenticated caller (401) whom the resolver names as the owner (403 otherwise; a resolver that throws or answers null is no owner). Creation is write-once (409) and requires a tier (400). - The store is per app instance. Validation passes the task's own tier and pipeline. - WITHOUT a resolver, which is the live gateway today, creation returns 503: the routes FAIL CLOSED until a milestone-to-poster resolver is wired (decision request #6182). Tests: - the generic test covers EVERY bundle field, assuranceTier included; - the bundle's claimed tier never changes a verdict; - bridge tests carry the task's own context; ZK and Merkle are refused, even with genuine proofs; - pipeline mismatch and a missing pipeline are refused; - tmp-tasks-authority.test.ts: fail-closed without a resolver, unauthenticated, owner-only and write-once creation, the tier required, and a Merkle submission against a sensor task refused at the route. verifier 30 files, 609 tests. spec 1212. gateway 208 files, 3740 tests. tsc clean for gateway, verifier and spec. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…rifier at the task's tier (E11e) Positive control for the owner-bound route: a sensor_evidence submission against a tier-2 sensor_evidence task passes the pipeline check and is judged on tier 2's requirements (its camera requirement), not on the bundle's claimed tier 0. It kills the mutant that drops the task's tier from the validation context. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
… what E11e made true The header listed zk_proof and merkle_commitment as validating through their services; both now refuse (tier_unenforceable), and the pipeline is the task's own. The sensor path's comment still said the verifier rejects a bundle claiming another tier; it no longer reads the bundle's tier at all. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…in creates a TMP task (#6182) The steward's ruling on #6182 (the N55 precedent): fail closed with an admin exception. Without an owner resolver, the route refuses every caller (403 admin_only) except one its injected isAdmin answers exactly true for; a throwing or non-true answer is no admin. With a resolver wired, only the owner path applies. Write-once, per-app state and validation from the task's own tier and pipeline are unchanged. The gateway wires isAdmin to hasAdminScope: an API key listing the literal "admin" scope. The wildcard does not count, because self-service sign-up mints every key with ["*"]. The resolver's doc says to wire it only with a proven caller principal (WP-A), since an API key's operatorId is self-typed today. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…s with WP-A #326 in either order The tests appended to scope-checker-money-path.test.ts conflicted with #326's edits to that file. In their own file they assert only what holds under both scope parsers: master's getCallerScopes and #326's parseScopeColumn, which refuses CSV (so no CSV case here). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…itted data; no worker tier is read (E11f HIGH 2, MEDIUM 3) E11f HIGH 2, reproduced at a121fe1: one bundleHash with fabricated events (fake hashes) passed both network pipelines, and empty events failed. The mock oracle and MockMiner only compared the JSON's own bundleHash with the supplied string. Now, before any network: - bundleData must parse to an evidence bundle that EvidenceVerifier accepts at the task's tier (event and bundle hashes recomputed, the tier's required events, the consistency checks); - the supplied bundleHash must be that bundle's hash; - the network receives only what the hash commits, as canonical JSON: the bundle hash, and each event's type, timestamp, source, payload and hash, ordered by timestamp then hash. No bundle field, event id, array order or payload key order reaches it, and its answer can only add a refusal. The boundary: committed means hash-committed; no kernel signature is checked, as on sensor_evidence. E11f MEDIUM 3, reproduced: requiredTier 0 on a tier-2 task was refused. acceptedTierOf no longer reads proof.requiredTier at all. TIER_ENFORCING_PIPELINES names the pipelines that can enforce a tier, for task creation. The oracle tests use sealed bundles; their old fixture is the reproduction. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…the schema, optional fields included (E11f MEDIUM 4) E11f MEDIUM 4, reproduced at a121fe1: a verifier that raised a critical finding whenever sessionKeyAuthorization was present kept the generic test green, because the fixture never carried the optional field. The test now enumerates EvidenceBundleSchema.shape and EvidenceEventSchema.shape, tries sessionKeyAuthorization with a well-formed delegation too, and reorders each payload's keys (committed only through its canonical form). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…olume; creation refuses pipelines that can't enforce a tier (E11f HIGH 1, MEDIUM 5) E11f HIGH 1, reproduced at a121fe1: two app instances both created m-1 (tier 3, then tier 0), because the store was a per-app Map, and a restart forgot every task. The steward's ruling on #6309, option (d): - FsTmpTaskStore keeps one file per milestone on the gateway's volume, beside pcc.db, named by the sha256 hex of the milestone id (no path traversal). The record is written to a temporary file opened with O_EXCL, synced, then hard-linked to its name; link() fails with EEXIST, so a second creation is refused across restarts and no reader sees a partial record. A record that isn't its milestone's is refused on read. - The store is injected: the gateway wires FsTmpTaskStore (server.ts, source-pinned), and tests use MemoryTmpTaskStore. With no store, or one that fails, the routes fail closed (503). - Lifecycle status (claimed, completed) is this process's view only and decides nothing. - The boundary: one gateway on one volume, as for pcc.db itself. E11f MEDIUM 5, reproduced: a zk_proof task was created (201) and every proof then refused. Creating a benchmark task now needs a pipeline in TIER_ENFORCING_PIPELINES (400 pipeline_unenforceable otherwise). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…y move money on it before N124 The steward's ruling on #6358/#6360: a hash commitment proves the bundle's integrity, not who produced it. Origin (each pipeline verifies the bundle's signature against the registered key of the kernel or device bound to the task's job) is row N124, after #560. No money or reputation path consumes a TMP verdict today (#6369), and the route now says any future consumer must wait for N124. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…, whoever asks (S1 survivor) The route mutant that dropped the store check survived: the try/catch around createOnce still answered 503 for an admin's valid request, so the property held, but a non-admin then got 403 and a bad body 400. The test now pins the documented order: a missing store is named first, for any caller and any body. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
…fresh full CI run (steward #6167) A plain merge: its tree equals git merge-tree of e881935 (E11g SHIP) and master. The fold #560 + ae69fa6 + #574 was built and tested first (verifier 627/627, gateway 4595 passed, 0 failed); this tree differs from it only by #571's one kernel test file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 03:24 — with
GitHub Actions
Failure
…rent master (steward wake-up, 23:2x) A plain merge, no edits: its tree equals git merge-tree of bf97fc1 and master. CI that ran on an older master isn't evidence: many PRs merged since. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 07:02 — with
GitHub Actions
Failure
LamaSu
marked this pull request as ready for review
October 4, 2026 07:30
LamaSu
had a problem deploying
to
trusted-checks
October 4, 2026 07:30 — 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.
Based on master (#341 has merged). DRAFT. The operator makes every merge.
This PR fixes N118 (board row; the steward states the property in bus #6161). Everything that decides a verdict comes from authoritative, owner-bound task state: the tier, the proof pipeline and the requirements. Nothing comes from the worker's submission or from another caller's task creation. Every pipeline either enforces the tier or refuses. The verifier reads no unsigned field.
It began as the follow-up for E11b's one LOW on #341: an event's
idand the order ofeventsare not committed. Cross-family review then found HIGHs in the consumers, and each round fixed them: E11c, E11d, E11e and E11f were all DO-NOT-SHIP. E11g, a full delta round, is in review.EvidenceVerifier (packages/verifier)
payload.stepIdcovers a step, never the unsignedevent.id(E11c).options.acceptedTier, the tier the caller knows from authenticated state. Without it, the verdict fails closed. The bundle's ownassuranceTieris not signed, so it is never read, not even to compare (E11d, E11e).TMPValidatorBridge (packages/verifier)
validate(envelope, context): the context (acceptedTier,proofType) is the task's own record, never the worker's envelope.task_pipeline).zk_proofandmerkle_commitmentrefuse (tier_unenforceable), because neither evidences the events that every tier requires. This also ends the empty-path Merkle accept.proof.requiredTieris never read.The TMP task route (packages/gateway)
milestoneOwnerresolver, only the milestone's owner creates its task.admin_only:adminscope (hasAdminScope).Documentation
subject-binding.tsgains a section, WHAT THE RESULT DOES NOT COMMIT. The bundle commits the sorted MULTISET of event hashes; neitheridnor order is committed.Tests and evidence (at e881935)
hasAdminScopehas its own tests.🤖 Generated with Claude Code
https://claude.ai/code/session_01VGNHoFFhbAdeNBc4BWigst