Skip to content

feat: add crash-safe ownership accumulation engine - #82

Merged
Aliferous3 merged 4 commits into
mainfrom
feat/ownership-accumulate-b2
Oct 19, 2026
Merged

Aliferous3 merged 4 commits into
mainfrom
feat/ownership-accumulate-b2

Conversation

@Aliferous3

Copy link
Copy Markdown
Owner

Summary

Phase 1C-B2 — the ACCUMULATE half of the ownership checkpoint engine, on the merged B1 substrate (PR #81, migration 013).

  • B1 merged prerequisite: builds on own_checkpoint_page CAS, own_build_preflight/own_build_init, versioned scope membership, and the hard-off OWNERSHIP_INGEST_* gate.
  • ACCUMULATE only: fresh-build start/resume → HEAD probe (the single mutable-branch read) → pinned Commit validation → guarded init → versioned membership (depth, scope_path_hash) → pinned history(path:) pages → grouped (period_start × work_key) deltas → own_checkpoint_page. One scope at a time, ending at pass='resolve' — the B3 handoff.
  • Pinned immutable commit history: every page anchors at Repository.object(oid: $pinnedOid), verifies __typename === 'Commit' and returned oid === pinnedOid. One retry on pinned-object failure, then fail-closed — never repins mid-build.
  • DB CAS owns exactly-once: no application-side page ledger. A committed page replayed with the old expected cursor returns 'stale' — no double-count. GitHub requests and reduction may repeat; the checkpoint decides.
  • No RESOLVE implementation: p_identities is always []; no identity staging, no github_node_id/display_login persistence anywhere.
  • No retained snapshot materialization and no promotion: ownership_contributors, ownership_contributor_build_work, and ownership_snapshots all stay empty (asserted in real-PG proofs).
  • No production ownership writes: the only entrypoint (runOwnershipAccumulate) sits behind the hard-off gate before any network or DB work; nothing schedules it — no cron, webhook, or UI.
  • Production untouched: all validation ran against disposable local Postgres 17 and in-memory doubles.

Implementation shape

  • lib/ownership/accumulate.mjs — transport/db-agnostic engine (page loop, lease, §5.6b resumability, dispatch)
  • lib/ownership/accumulate-runtime.mjs — supabase adapter + octokit transport + dormant gated entrypoint
  • lib/ownership/page-deltas.mjs — pure page→deltas reducer (reuses canonical classifier + monthStartUtc)
  • lib/ownership/queries.mjs — pinned production queries (ownershipHeadProbeQuery, pinnedObjectQuery, pinnedHistoryPageQuery) + response-contract tripwire
  • scripts/ownership-pg-db.mjs, scripts/ownership-work-projection.mjs, scripts/ownership-engine-proofs.mjs — real-PG drill legs
  • test/ownership-accumulate.test.mjs — 28 unit tests (deltas, payload privacy, pinned-query statics, crash/replay A–K, lease, differential, adversarial partitions)

Validation

  • 660 tests pass / 0 fail / 3 pre-existing env-gated skips
  • Real-Postgres 17 drill: all schema + checkpoint + engine proofs, backup/restore, 012→013 upgrade leg
  • typecheck, production build, npm audit (0 vulns), gitleaks clean

Test plan

  • unit: page-delta reduction, payload privacy, pinned-query statics, crash/replay matrix
  • differential: every page partition projects exactly to reduceHistoryToMonthly
  • real-PG: end-to-end accumulate, resume equivalence, stale/replay CAS, pin-loss fail-closed, lease CAS, canonical abandon, zero-identity persistence
  • CI: gitleaks, recovery drill, test/build/audit, Vercel

Generated with Devin

Implements the ACCUMULATE half of the checkpoint architecture on the B1
substrate: preflight -> HEAD probe -> pinned Commit validation -> guarded
init -> versioned scope membership (depth, scope_path_hash) -> pinned
history(path:) pages -> grouped (period x work_key) deltas ->
own_checkpoint_page CAS, one scope at a time until resolve-ready.

Exactly-once is owned by the DB CAS — a committed page replayed with the
old cursor returns 'stale' and never double-counts. Slices are bounded
(~40s wall clock, ~300 measured GraphQL points, 500-point rate reserve)
and a soft per-repo lease inside history_cursor serializes workers; both
boundaries stop only at committed page boundaries.

ACCUMULATE only: p_identities is always [], no RESOLVE pass, no promotion,
no retained snapshots, no identity staging, no production wiring — the
single entrypoint stays behind the hard-off OWNERSHIP_INGEST gate.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
dev-ledger Ready Ready Preview Oct 19, 2026 7:24pm UTC

Two B2 correctness gaps before merge review:

- page-deltas: first-observation-wins is_bot collapsed contradictory
  classifications for one work_key inside a page, hiding drift from the
  stored-row DB guard. A page-wide frozen work_key -> is_bot map now
  fails closed via OwnershipIdentityError on any conflicting observation
  (any order, any month); the DB guard remains the cross-page barrier.
- accumulate: GraphQL telemetry was only counted inside the history page
  loop. One shared per-slice budget now records cost/remaining/resetAt
  for every request — HEAD probe, pin validation, scope-prep pinned
  revalidation, history pages, and the identical retry — and gates each
  subsequent request on cost budget, 500-point reserve, and wall clock.
  The retry is budget-gated too and its telemetry replaces the failed
  shape's.

Coverage: page-reducer drift matrix (same/reverse/cross-month),
engine-level no-checkpoint/no-cursor-move proof, cross-page stored-row
rejection, per-request cost accumulation, retry double-cost +
authoritative remaining, reserve-stop, and cost-budget-stop — plus real
Postgres proofs E11/E12 in the recovery drill.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A thrown own_checkpoint_page call does not prove rollback — Postgres may
have applied the deltas, advanced the durable cursor, and committed while
the response was lost. Treating every RPC exception as "not committed"
could double-apply a page or fail a healthy build.

The engine now reconciles the sync row before deciding: a proven commit is
adopted; a row still exactly at the attempted position is retried once
with the identical payload (the B1 CAS makes 'applied' and 'stale' both
safe); a canonical transition elsewhere is followed, never overwritten.

Error marking is now position-aware: phase='error' can land only while the
durable row matches the exact attempted position (version, full
history_cursor, cursor, lease), so a stale worker cannot poison a newer
cursor or the ACCUMULATE->RESOLVE handoff.

Proven by unit tests UA-UG and real-Postgres proofs E13-E15 in the PG17
recovery drill.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A continuing page (hasNextPage=true) that returns the request cursor — or
an empty endCursor — would checkpoint the same boundary and re-page the
identical slice forever, double-counting deltas on every commit. Exactly-
once assumes each accepted next cursor is a strictly new page boundary.

Validate progression before own_checkpoint_page: a continuing page must
return a non-empty endCursor different from the request cursor. Violations
fail closed through the position-aware error path with
OWNERSHIP_HISTORY_FAILED — no deltas persist, the durable cursor stays at
the last valid boundary. Final pages are exempt: B1 flips the pass to
resolve regardless of the terminal cursor value.

Covered by unit tests CA-CE and real-Postgres proof E16 in the PG17
recovery drill.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@Aliferous3
Aliferous3 merged commit 2bb80de into main Oct 19, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — d245ae70 Deployed Oct 19, 2026 by vercel[bot]
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