P3.4.1 — Signed Time Anchors (STA) — Verification Evidence
1. Gate results
| Gate | Command | Result |
|---|---|---|
| Build | npm run build |
PASS (tsc clean; copy-migrations OK) |
| Lint | npm run lint |
PASS (eslint src — zero warnings) |
| Full test suite | npm test |
2732 passed / 2 failed / 2734 total (failures pre-existing on main; see §3) |
| Focused (this slice) | npm test -- src/__tests__/domains/consensus/time-anchors.test.ts |
47 passed / 0 failed / 47 total |
Baseline at origin/main @ e63a8bcf: 2682 passed / 5 failed / 2687 total.
Worktree state: 2732 passed / 2 failed / 2734 total. Delta is +47 tests
(matches the 47 tests added by this slice — file added 30 numbered cases
T1–T30 plus 17 supplementary cases covering defaults, edges, boundaries,
and ergonomics).
2. Contract invariant → test cross-walk
Every invariant from the
contract §3 maps to at
least one test in
src/__tests__/domains/consensus/time-anchors.test.ts.
| Invariant | What it asserts | Realized in test ID |
|---|---|---|
| I1 STA shape | 4-field readonly struct; signature 64 bytes | T1 |
| I2 eligiblePublishers — top-N by score | Sort DESC, stable tie-break, defaults, edges | T5, T6, T7, T8, T9, plus “default top_n is 7n”, “top_n >= snapshot.size”, “top_n = 0n” |
| I3 signAnchor / verifyAnchor — Ed25519 PURE | Roundtrip success, tampering rejection (sig + ts), determinism | T1, T2, T3, T4, “canonicalSerializeAnchor stable bytes” |
| I4 computeMedian — bigint median over filtered window | Odd, even, empty, all-filtered, dup-publisher, replay-floor, future-dropped, default K, per-publisher tie-break | T10, T11, T11b, T12, T12b, T13, T14, “future-dated dropped”, “default k_epochs”, “tie-break on per-publisher selection” |
| I5 detectDrift — advisory, strict-> threshold | Within / over / exact / negative skew / zero / custom / default | T15, T16, T17, T18, “zero drift”, “custom threshold”, “default threshold” |
| I6 validateMonotonicity — soft fault flagging | Strict-advance violation, advance-clean, same-epoch non-violation, multi-publisher mix, defensive copy, empty, single anchor | T19, T20, T21, T22, “input unmodified”, “empty input”, “single anchor” |
| I7 rejectReplay — boolean past-replay gate | Reject case, boundary keep, current keep, future not rejected, custom window, default | T23, T24, T25, T26, “custom replay_window”, “default replay_window” |
| I8 Determinism (κ-inherited) | Sign deterministic; canonical body byte-identical | T4, T27, T29, plus T28 (static scanner) |
| I9 Error model | Class re-export + instanceof identity | T30 |
3. Pre-existing failures (NOT introduced by this slice)
Worktree shows 2 failed tests, both in
src/__tests__/domains/reputation/tools.test.ts. Verification of pre-
existing status:
reputation_check_gateshappy path —Migration 997_partial.sql failed: SqliteError: no such function: nonexistent_fn- another reputation_check_gates case — same root cause
Both fail when the full Jest suite runs src/__tests__/db-init.test.ts
first; that test writes a 997_partial.sql fixture into the
src/db/migrations/ directory and a later test then picks it up via
initDb(). Run alone, both reputation/tools.test.ts and
db-init.test.ts pass cleanly.
Confirmed pre-existing on main e63a8bcf:
$ cd E:/AMS && npm test --silent --coverage=false
Test Suites: 2 failed, 55 passed, 57 total
Tests: 5 failed, 2682 passed, 2687 total
The worktree shows fewer pre-existing failures (2 vs. 5) because Jest’s
file-ordering differs by suite count (worktree adds 1 suite). Both
states are consistent with the pre-existing 997_partial.sql test-
isolation defect.
This slice does NOT introduce new failures. Net new tests: +47 passing.
4. Forbidden-token scanner result
Test T28 time-anchors.ts source body contains no forbidden tokens runs
in-suite and passes. Scanner sweeps time-anchors.ts for:
Date.now, process.hrtime, performance.now,
Math.random, Math.floor, Math.ceil, Math.round,
setTimeout, setInterval, console.,
crypto.sign(, crypto.verify(, import * as crypto
with JSDoc / line comment / template literal stripping. Zero hits.
5. λ surface integration approach
Per the dispatch packet:
λ DEPENDENCY: SATISFIED. Phase 2 λ closed at #233. Use library- level
selectReputationfromsrc/domains/reputation/schema.tsfor eligible-publisher lookup (avoids MCP roundtrip).
The integration approach taken:
time-anchors.tsis pure — no DB handle, no Map construction, no imports fromsrc/domains/reputation/.eligiblePublishers(reputationSnapshot: ReadonlyMap<string, bigint>, top_n)accepts an opaque snapshot Map constructed by the caller.- Production callers will build the snapshot Map by walking
selectReputation(db, node_id, "arbitration")for each candidate arbiter, OR by queryingidx_reputations_leaderboard ON reputations (domain, score DESC)directly. Either path is a one-step assembly outside this module’s surface. - Test fixtures pass literal Maps — no SQLite handle needed.
This keeps the module pure, decouples it from the λ schema’s mutable
DB surface, and avoids an MCP roundtrip in production. The
“library-level selectReputation” wording in the dispatch packet is
honored — this module reads at the schema-level via
selectReputation-derived snapshots, NOT via an MCP tool.
6. Defaults rationale (π-governable)
Per source prompt §Common gotchas line 1645, every magic number is exposed as a defaulted parameter:
| Constant | Default | Exposed as |
|---|---|---|
top_n |
7n |
eligiblePublishers(snap, top_n?) and isEligiblePublisher(pub, snap, top_n?) |
k_epochs |
10n |
computeMedian(anchors, current, k_epochs?) |
threshold_ms |
30_000n |
detectDrift(local, median, threshold_ms?) |
replay_window |
10n |
rejectReplay(anchor, current, replay_window?) |
All four are bigint and all four have default-handling tests
(“default top_n is 7n”, “default k_epochs is 10n”, “default threshold”,
“default replay_window is 10n”). A future π governance task can tune
these without re-shipping time-anchors.ts.
7. Determinism evidence
- All quantities
bigint(no float arithmetic in the module body). - Zero
Math.*andMath.randomreferences (scanner-enforced). - Zero
Date.now,process.hrtime,performance.nowreferences. - Zero side effects (no
setTimeout,setInterval,console.*). - Ed25519 PURE-mode
sign(null, body, privKey)is deterministic — test T4 asserts byte-equal signatures on repeat sign. - κ canonicalize inheritance: identical input → byte-identical canonical body in every Node ≥ 20 process. Single-process surrogate tested at T27, T29, and the “canonicalSerializeAnchor stable bytes” case.
8. Spec contradiction check
The source prompt, s06 §Signed time anchors, and s08 §Signed Time Anchors (STA) were cross-read in audit §2. All three agree on:
- Field shape
{publisher, timestamp_ms, epoch, signature}. - Drift threshold “30s” (=
30_000nms). - “Deprioritized” semantics, NOT rejection.
- Publisher eligibility tied to reputation.
No contradictions encountered. No STOP condition triggered.
9. Deferred / out-of-scope (per packet §8)
The following are NOT part of this slice:
- STA periodic broadcast scheduler (P3.3.x gossip)
- STA-aware proposal priority queue
- STA persistence layer
- π governance hooks for the four defaults
eligiblePublishersdirect-from-DB convenience
These are Wave 3+ work. The slice ships the foundation they will all build on.
10. Commits
5 commits on feature/p3-4-1-time-anchors:
| # | SHA (short) | Step | Message |
|---|---|---|---|
| 1 | c1329200 |
Audit | audit(p3-4-1-time-anchors): inventory STA surface — λ dep satisfied at #226, P3.1.1 sig path reusable |
| 2 | 90277baa |
Contract | contract(p3-4-1-time-anchors): STA surface + 9 invariants + 30 tests + π-governable defaults |
| 3 | 32fd850a |
Packet | packet(p3-4-1-time-anchors): 9-section module + 30-test suite + 5-commit plan |
| 4 | ec86a6e0 |
Feat | feat(p3-4-1-time-anchors): STA wire + 8 helpers — sign/verify/median/drift/monotonicity/replay |
| 5 | (this) | Verify | verify(p3-4-1-time-anchors): all gates pass; 47 new tests; pre-existing 2 failures |
11. Done
- All 8 exports per contract §2 shipped in
time-anchors.ts - All 30 contract test IDs (T1–T30) implemented in
time-anchors.test.ts npm run buildcleannpm run lintcleannpm test— net delta +47 tests, all passing; pre-existing 2 failures documented and confirmed unrelated- λ surface integration: pure module, snapshot-Map input pattern
- Determinism: bigint-only, no wall-clock, no Math, named crypto imports
- Forbidden-token scanner passes
- Spec contradiction check: clean (s06 + s08 + prompt mutually consistent)
- 5-step chain complete; ready for PR