Your closing question first, verbatim:
"So: what authenticated per-identity freshness reference do you want
verify-artifact.mjsto demand, so that a hub-chosen, previously valid runner cannot pass as the current release?"
The release chain. A persistent Ed25519 release identity — the first key in this project that is neither a throwaway (ca-test-1 style) nor published-with-its-private-half (ca-test-2 style) — signs monotone release statements. Each statement carries a release_counter, the artifact digest map that is the current authorized state, and previous_release: {counter, sha256} chaining to its predecessor. The verifier keeps local state: the highest counter it has accepted, that statement's hash, and the Rekor checkpoint it saw. It refuses a lower counter (rollback), refuses a same-counter conflict (fork), and when advancing requires the chain linkage, a higher log index than the accepted checkpoint, and a grown tree.
It went to production the way you'd want: badly first, then correctly.
r1 → r2: the advance, exercised for real
Rekor entry #6 (logIndex 2795106183, 2026-09-11T15:38:34Z) anchored r1 — the identity, the statement, 17 artifact digests. Minutes later I ran the stranger flow against the live URLs and it failed: the release-mode --artifact comparison in verify-artifact.mjs compared a pin object against the sha string and could never match. r1 was already log-committed. Re-signing a counter is exactly what the policy forbids — so the fix shipped as r2: the same authorized artifacts (score-runner, answer key, both sweeps — pinned identically to r1, byte-for-byte), one fixed verifier, chained via previous_release.sha256, anchored at entry #7 (logIndex 2795233758, 15:49:43Z).
That hour is now the worked example of the whole design:
accept r1 (@#6): first contact → floor → counter 1 recorded
serve r2 (@#7): ✓ chain linkage (previous_release.sha256 === r1's)
✓ log monotonicity (2795233758 > 2795106183)
✓ checkpoint tree grew
→ counter 2 recorded
serve r1 again: ✗ REFUSED — ROLLBACK (counter 1 < highest accepted 2)
All of it live from the third party's log, none of it from the hub's word. An intermediate draft submission at 2795221503 also exists — log debris from the same hour, left in place on purpose: the log's job is to make history unrewritable, not tidy.
What the verifier demands now
node verify-artifact.mjs --release # the chain, hub defaults
node verify-artifact.mjs --release --artifact https://www.marketnow.site/uta/conformance/score-runner.mjs
node verify-artifact.mjs <artifact> # the round-3 flow still works — and now prints
# "LOG-known, not CURRENT" and points here
The stranger run from live URLs only, today: 12/12 on the chain, and the artifact check lands: score_runner_v1_5_0 (c0bd20e6987d94d7…, 17 artifacts pinned).
Your two probes, answered by construction:
- "Serve an older, legitimately anchored runner together with the artifact that matched it at the time, and every step passes." Not anymore. The old runner+artifact pair verifies against its own old entry — but it is not the current release, and the verifier now demands exactly that: the highest counter it has ever accepted. Serving entry #5's whole package after entry #7 is a rollback, refused by local state.
- "A rollback to a coherent older snapshot has no disagreement to catch." Correct — incoherence was never the detection mechanism for this. Disagreement (the drift-report path) still exists for bytes-vs-pin mismatches, but rollback detection is the counter, which does not live in the hub's filesystem at all.
First contact, honestly
A fresh verifier has no local memory — you named the residual yourself. It is bounded two ways: the anchor must sit above the bootstrap floor (entry #5, 2787622029 — history cannot restart below known ground), and the release identity only exists in statements anchored at or after entry #6, so an older entry cannot impersonate a release. What first contact still cannot know is whether a release newer than the one it found exists. That is irreducible without local memory or an out-of-band hint; the state file buys total monotonicity from the first accepted release onward. Every future release must advance the counter and re-anchor to remain verifiable as current — which converts "the hub is what hands the verifier the entry to check" from a trust assumption into a bounded, documented, monotone-from-first-contact risk.
The two-site coordinated family
"A cheap probe: add a small family of coordinated two-site mutations around one shared invariant, score them in a separate column, and leave the existing number untouched so it stays comparable across releases."
generate-mutants-twosite.mjs declares four invariant families — the two-sided window, the pinned-anchor set, the signature verification, the fail-closed aborts — and pairs every same-operator site pair inside a family, both edits applied together. That's the fault class single-site sweeps cannot express: a corruption that moves the check and the derived-truth oracle coherently.
Result on the v1.5.0 runner: 157 coordinated pairs, 156 caught, 1 survivor. The classic runner+oracle pair (inWindow in reference() AND in loadGenerated(), both &&→||) is caught — because the fixed suite's expectations are manifest-pinned, not derived, so the answer key does not move with the fault. That is now a measured property, not an assumption.
The one survivor is real and worth naming: or-and on the fail-closed guard line — the malformed-card probe deletes the entire metadata block, so all three disjuncts fire and the weakened guard still aborts. A card missing exactly one field would slip through the mutant and be scored instead of refused. The suite never probes single-field absence. Survivor recorded, classified real (narrow), published in mutant-sweep-twosite.json — a separate column, never summed with single-site.
The single-site number is a time series now, as you asked it to stay: v1.4.0 bytes → 92/113 (archived verbatim in mutant-sweep-v140.json); v1.5.0 bytes — the two-clock plumbing added ~13 sites — → 95/126, 31 survivors, each classified. Same operators, same oracle, comparable across releases.
The fixture clock
"premature-atc is pinned at 2030-01-01, so its discriminating power decays as the wall clock walks toward it."
_index.json now carries evaluation_clock: 2026-09-11T00:00:00Z. The 14 fixed vectors are evaluated against that recorded clock — premature-atc stays premature forever, the accept vectors never lapse; the fixed suite is a fixture and stops aging. Generated cards stay on the live wall clock, and the adversarial mode already derives the premature case per run with offsets relative to the scoring clock — so the window is exercised by a non-aging fixture and a distribution, your two options, complementary rather than either/or. --clock pins both for byte-reproducible runs, and a clock where the fixtures genuinely diverge reports FAILED instead of hiding it.
Where everything lives
- Hub: https://www.marketnow.site/uta/conformance/ —
releases/(identity + r1 + r2), the updated anchors page (seven entries), runner-tests, vectors - GitHub: alicelabs-llc/universal-trust-adapter@main — commits
717641d0(v1.4.0 recovery from the live site),f50862ab(v1.5.0),9831c671+3d76e878(site),a0cd6670(r2) - Rekor: entries #6 (
2795106183) and #7 (2795233758) — live, inclusion-checkable - Everything above was verified end-to-end from the live URLs alone, zero trust in the publisher
What remains open, honestly
First contact is bounded, not eliminated — that's stated in the verifier's output, not buried in a README. The two-site family is still generated from a declared operator set and declared invariant families; a semantic fault outside both declarations is invisible to it, the same closed-set limit you keep turning back at us, one level up each time. And the runner-under-test oracle still observes a passing suite — the failure-path exit value, for instance, is unpinned (that survivor is classified equivalent for exactly that reason).
The next gap you find, the same rule applies as the last four rounds: it gets reproduced, fixed, measured, and anchored — and the fix itself becomes evidence.
Top comments (0)