Skip to content

feat(server): operator back-anchor of the un-anchored tail (POST /v2/anchors/backfill) - #54

Draft
zachyking wants to merge 4 commits into
mainfrom
feat/back-anchor
Draft

zachyking wants to merge 4 commits into
mainfrom
feat/back-anchor

Conversation

@zachyking

@zachyking zachyking commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

What

Back-anchoring of checkpoints whose TSA stamp failed, tail only, operator-driven, no automatic sweep.

  • Store.UnanchoredTailCheckpoints(project, limit): un-anchored checkpoints with a seq above the newest anchored one, oldest first, bounded. Mem and Postgres (primary-key index probes; parity test passes on Postgres 16).
  • POST /v2/anchors/backfill?project=P (admin, like checkpoints): stamps the tail, at most 10 per call, 40 s wall-clock budget (inside the 60 s WriteTimeout), stops at the first failure, one backfill per project at a time (409 otherwise), 409 without a TSA. Returns {back_anchored, remaining_unanchored, stopped_on_error}.
  • Counters averin_checkpoint_backanchored_total, averin_checkpoint_backanchor_failures_total; one log line per back-anchor, ALERT: on a failed sweep.
  • Kit claim back-anchoring-bounded-after-successful-stamp (3 mutants, all killed), docs.

Why tail only (round 2)

The offline verifier's backdating gate (check_anchor_backdating) requires verified anchor times to be non-decreasing with seq. A late stamp on a checkpoint that has a newer anchored checkpoint has a genTime later than that anchor, so the bundle fails integrity (anchor time decreased ...) unless both genTimes fall in the same second (RFC 3161 genTime has 1 s granularity, so the outcome depends on TSA latency). Round 1 swept older checkpoints after each cut and would have done this at random. Now:

  • The per-cut automatic sweep is removed.
  • Only the un-anchored tail is stamped; this cannot decrease the order (TestLateTailBackAnchorVerifies: ok, 3 of 3 anchored). The mid-chain case is pinned by TestMidChainLateBackAnchorTripsBackdatingGateToday.
  • A gap below the newest anchor stays un-anchored (it is committed by the next anchor through prev_checkpoint_hash) and still counts as un-anchored in checkpoints_anchored, so a readiness/accept-proof gate requiring equality still fails on such a run. Counting a checkpoint committed by a later anchor as anchored is a separate spec decision for the owner.
  • Residual race: a new cut's own stamp racing a backfill of the previous checkpoint can reorder times; documented, run the backfill on a quiet project.

Gates

make test-core, make test-server, go vet, gofmt, both claim checks, 3 mutants killed, sfw make supply-chain: green under taskset 0-7. Postgres parity run on Postgres 16.

🤖 Generated with Claude Code

…cut, operator backfill endpoint)

After a cut whose own TSA stamp succeeded, stamp up to 3 older un-anchored
checkpoints, oldest first, stopping at the first failure. Add admin-only
POST /v2/anchors/backfill (bound 100) and Store.UnanchoredCheckpointSeqs
(Mem and Postgres). A test pins that the verifier's backdating gate rejects a
back-anchor stamped later than a newer checkpoint's anchor.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@zachyking
zachyking marked this pull request as draft October 26, 2026 22:35
zachyking and others added 3 commits October 26, 2026 22:37
…sweep

A back-anchor on a checkpoint with a newer anchored checkpoint trips the offline verifier's backdating gate. Only the tail (seq above the newest anchor) is stamped, via the operator endpoint (bound 10, 40 s budget, single-flight per project).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@zachyking zachyking changed the title feat: back-anchor un-anchored checkpoints (bounded sweep + backfill endpoint) [blocked on verifier backdating gate] feat(server): operator back-anchor of the un-anchored tail (POST /v2/anchors/backfill) Oct 26, 2026
@zachyking

Copy link
Copy Markdown
Member Author

Lead: DO NOT MERGE. Back-anchoring a mid-chain checkpoint trips the offline verifier's backdating gate (check_anchor_backdating: anchor times must be non-decreasing by seq), turning an honest run into a false tamper finding. Redesign pending: count checkpoints committed by a later honored anchor (cp_committed_at) in readiness/accept-proof, and only ever stamp the un-anchored tail. This PR stays a draft until then.

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