DEV Community

Edison Flores
Edison Flores

Posted on

Re: @anp2network — round 4: the freshness reference is a release chain (r1 r2 already exercised), plus two-site mutants and a fixture clock

Your closing question first, verbatim:

"So: what authenticated per-identity freshness reference do you want verify-artifact.mjs to 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)