Skip to content

Drop Checkpoints, and persist Pebble sync pages atomically - #1140

Merged
kans merged 7 commits into
mainfrom
matt.kaniaris/CXE-1358/syncer-ledger-plan
Oct 6, 2026
Merged

kans merged 7 commits into
mainfrom
matt.kaniaris/CXE-1358/syncer-ledger-plan

Conversation

@kans

@kans kans commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Pebble collection pages commit records, facts, accounting and pending-work transitions atomically. Resume reads unfinished work in bounded windows through the existing scheduler. Separate work IDs and revisions allow repeated request tokens to execute normally. Existing checkpoint tokens seed the queue atomically during migration. SQLite continues checkpointing. Flags are locked per phase on both engines: a finished sync accepts only WithOnlyExpandGrants; on Pebble a resumer whose collection flags differ from the first attempt's recorded options is refused before any write; an expansion-only invocation plans from the file's skip facts and reads none of its own collection flags or targets, on SQLite as well as Pebble.

Expansion and external import retain main's handlers and optimized write paths; neither is buffered in a page transaction. Static entitlements materialize one resource page at a time, preserving template order and cold resume.

Successful collection archives a mechanical stats report, drops ledger history by default and purges discarded token data. Explicit ledger debug retains scrubbed history; retaining tokens requires an additional explicit option. Debug logging alone does not enable retention. Attempt metadata keeps first/latest options and folded prior-attempt accounting rather than growing with every retry.

Plain EndSync() preserves its existing lifecycle meaning: ending a run does not assert collection completed. It saves committed accounting, finalizes indexes, flushes and detaches while preserving data and pending recovery state through close/reopen. Explicit same-ID continuation can use that state. The existing end/cleanup/start-new reset sequence works; starting a new Pebble sync retains main's reset behavior. EndSyncWithStats remains the syncer's checked completion path. WithConnectorStore(nil) still falls back to the configured file path.

Custom sync stores: Pebble requires PageLedgerStore and is ledgered; every other engine, including an empty or unknown one, checkpoints through the token path as on main and must not expose PageLedgerStore. Pebble wrappers must forward the capability. There is no Pebble checkpoint fallback. The ledger interfaces gain methods; custom implementations must update accordingly.

SDK downgrade constraint: unfinished and early-ended Pebble artifacts can retain the new ledger recovery format. A host reusing those artifacts cannot downgrade its vendored SDK across that format boundary. Keep the host on a ledger-capable SDK, or discard affected recovery artifacts and start fresh before downgrading. This is distinct from rolling back a connector binary without changing the host's SDK.

Validation:

  • The unexpanded-upload/host-expansion regression collects and saves three base grants with expansion disabled, copies the artifact, and requests expansion on the same sync ID. Exact six-grant output is checked after reopen with one and four workers, default/debug retention and normal/early ending. An explicit expansion-only call finishes any prior seal and performs the requested expansion in the same invocation, including unfinished empty queues and prepared seals. Ordinary recovery can finish the prior seal alone. Explicit expansion requests may redo deterministic expansion; accounting records both actual passes. Handoff failure tests cover seal/rebind errors, graph persistence and retention reset. Storage clear failure/crash tests preserve metadata/accounting and queue atomicity. Full suites, focused race tests, CI-equivalent lint and bounded independent correction review pass.
  • Service-mode rollback uses actual daemon and connector subprocesses against a local TLS/gRPC C1 API. Targets bba86699 (v0.30.1) and eb63f1b5 pass 16 cases covering single/batched polling, spare off/on, and process kill/reported sync error. The same directory and options survive the version switch; old daemons upload usable data and complete another task, then the new SDK succeeds after roll-forward. Three ordinary repetitions pass; the current harness also passes race checks. This resource-only fixture simulates C1 redelivery and does not claim production workflow coverage.
  • Full sync, public storage, Pebble and compactor suites pass; focused lifecycle and attachment race checks pass.
  • A real eb63f1b5 checkpoint is compared with that SDK's completed artifact and an uninterrupted ledger migration. Twenty-eight crash combinations cover takeover, grant and terminal commits, repeated crashes, WAL/flush recovery and 1/4 workers. Three repetitions and a race run pass, comparing records, indexes, digest, normalized stats and ledger accounting. Dropping imported action accounting makes the test fail.
  • Independent bounded reviews covered lifecycle changes and the migration test's oracle. Seal/archive crash tests are separate from the combined historical fixture.
  • Changed-code lint passes locally. Final-head CI is pending. Historical-SDK builds and performance drivers remain opt-in.

Review guide and performance · Plan/change orders · Evidence and review dispositions

The evidence records executed coverage and residual gaps. It does not claim complete coverage of every original plan product or completion of the original full C49 performance matrix.

Successful page retries preserve every observed connector call, session usage report and reported wait in committed/live totals. The regression test checks exact totals, repeated-token page isolation, failed-attempt record/fact exclusion and close/reopen, with three race repetitions. Retry observations before any successful commit remain best-effort.

@linear-code

linear-code Bot commented Sep 17, 2026

Copy link
Copy Markdown

CXE-1358

Comment thread pkg/sync/ledger_schedule.go Outdated
Comment on lines +120 to +123
counters := c1zstore.LedgerCounters{
Counters: make(map[string]uint64), ConnectorCalls: make(map[string]c1zstore.CallStat),
StepDurationsMs: maps.Clone(parts.stats.stepDurationsMs), SessionCalls: make(map[string]c1zstore.CallStat),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (medium confidence): the takeover bucket and the run bucket look like they will double-count the same pre-interruption stats. decodeLedgerCheckpoint imports the checkpoint's cumulative stepDurationsMs, connectorCalls and sessionOps into TakeoverBucketWorker, but loadRunStats (token.go:221-232) restores those same cumulative maps into the resumed run's in-memory runStats. When the caller later hands that in-memory state to flushRunCounters/prepareSeal, it lands in RunBucketWorker, and LedgerCounters sums every bucket — so ledgerSyncStats reports the pre-interruption durations and call counts twice. ledgerCompletedActions is safe because the resumed run only counts actions it actually completes; the stats maps are not, because they are carried whole rather than derived per page.

ledgerRuntime.prior is loaded in newLedgerRuntime and never read anywhere, which is where that reconciliation would go — implementation.md §4 says prior buckets are "aggregated only for reporting and gates". No test covers the combination: every prepareSeal/flushRunCounters test starts from a ledger with no takeover bucket. Worth resolving (or at least asserting) before the production handler starts supplying real run counters.

Comment thread pkg/sync/ledger_walk.go Outdated
Comment thread pkg/sync/ledger_page.go Outdated
Comment thread pkg/sync/ledger_cost_runtime_test.go Outdated
@github-actions

github-actions Bot commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 5d35ecd29bd8

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 1 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base eb63f1b54771.
Review mode: full
View review run

Review Summary

For Pebble stores, the syncer now runs through a page ledger instead of checkpoint tokens. Each page commits its records, assets, grant deletes, ledger row, facts, counters and pending-work transition in one Pebble batch. Resume reads a bounded pending-work queue, and legacy checkpoint tokens seed that queue during takeover. SQLite keeps checkpointing.

gh pr diff refuses a diff this large (over 20k lines), so I read the per-file patches from the API. I scanned all production files in pkg/sync and pkg/dotc1z for security and correctness. The ~100 new test files and the docs were spot-checked only. records.proto is additive only, and buf-breaking passes. go.mod is unchanged.

Criteria triage: HIGH. The change is silent (lost pending work skips work with no error) and durable (c1z ledger and queue state). It depends on crash and schedule timing and on which SDK version wrote the file. That puts it in the multi-artifact, schedule and error-path classes. The PR does include matching instruments: 142 crash/cancel cuts, a differential test, a legacy-artifact test and seeded race soaks. The full §6 pass-set review should still be confirmed before merge.

The legacy "frontier + rows, no queue" shape refused at pending_work.go:98 cannot come from a released SDK, because the base pkg/sync never drove the ledger.

Security Issues

None found.

Correctness Issues

  • Prior — still present (open thread, pkg/sync/syncer.go:3883-3896): setStore now makes NewSyncer fail for WithConnectorStore inputs that used to work. On base it only assigned the store and its caps.
    • A store with Metadata().Engine == "" fails, even though connectorstore.go:44 documents "" as valid for virtual or mock stores.
    • A decorator that hides PageLedgerStore fails with "pebble sync store requires PageLedgerStore".
    • A nil store combined with WithC1ZPath now errors instead of using the path.

Suggestions

  • New (pkg/sdk/version.go, still v0.30.1): the default sync behavior changes for downstream connectors, but the PR has no version bump or migration note.
  • Prior — still present (open, c1zstore/ledger.go:252, duplicates merged): the exported PageWriter and PageLedgerStore gain required methods, so out-of-repo implementations and mocks stop compiling.
  • Prior — still present (ledger_scheduler.go:179): the page Commit runs while the queue's q.mu is held, so disk commits run one at a time across workers. There is no deadlock.
  • Prior — still present (ledger_resource_types.go:111): this replaces the counters map instead of adding to it. It is safe today, but a future earlier write on the page would be lost silently.
  • Prior — still present (ledger_grants.go:60 and 5 siblings): single-value type assertions on the context value. They cannot panic today, but the pattern is brittle.
  • Prior — still present (adapter_page.go:82): staged asset bytes stay in memory until Commit. That is bounded per page, not per sync. Low severity.
  • Prior — still present (rawdb/families.go:187-192): the engine-meta comment still says asset rows are "never batched".
  • Prior — still present (ledger_report_scan.go:58): the constant ledgerTokenHash("") is recomputed for every row.
  • Prior — still present (ledger_report_stream_checks_test.go:182): the test never checks the absolute allocation count the docs claim.
  • Prior — still present (seal_cost_test.go:27-28): the assertion on the by-value copy can never fail.
  • Prior — still present (initial_actions.go:30): the comment explaining why partial syncs skip these phases was dropped.
  • Prior — still present, re-posted inline (ledger_report_references.go:128): the op names are duplicated as string literals.
  • Prior — still present, re-posted inline (ledger_cost_test.go:74, ledger_cost_public_test.go:70): the cost drivers are opt-in, so CI does not enforce the per-page cost curve.

Resolved prior findings

  • Fixed: go-lint passes on this SHA, which resolves the lint bug and makes the evidence.md lint claims accurate.
  • Fixed: ledger_empty.go:28-30 now filters ErrNotFound.
  • Fixed: adapter.go:474-478 now times purgeMarkedResidue, and ClearRows no longer compacts while holding lifecycleMu.
  • Fixed: ledgerDebug is now an explicit option (ledger_report.go:13).
  • Fixed: run accounting now increments the existing counters (ledger_run_accounting.go:60-69).
  • Fixed: static entitlements now materialize one resource page at a time (ledger_static_entitlements.go:95).
  • Obsolete: the replay path, the expansion/external ledger files, the prototype and cost-runtime/baseline tests, and machine.json were all removed. The hooks.go comment finding no longer applies.
  • No action needed: ledger.go:703. EndSync clears the in-flight stamp (adapter.go:492), and a crash in between leaves the conservative v3 stamp.
  • No action needed: ledger_scheduler.go:94/97/113/190. Worker overlap is rejected by errLedgerWorkerBusy, the guard validates the worker range, the commit writes TypeScopedPlanned into the next revision, and ledger mode never reaches run.transitionAction.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Correctness Issues

In `pkg/sync/syncer.go`:
- Around line 3883-3896: setStore makes NewSyncer reject stores with Engine "" (documented valid), decorators hiding PageLedgerStore, and nil store + WithC1ZPath. Treat ""/unknown engines and Pebble stores without PageLedgerStore as non-ledgered (the checkpoint path) instead of erroring, and keep the nil+path fallthrough. Add tests.

## Suggestions

In `pkg/sdk/version.go`:
- Bump the minor version and add a migration note for the default-behavior change.

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 164-259: use versioned interfaces or capability assertions for the new PageWriter/PageLedgerStore methods.

In `pkg/sync/ledger_scheduler.go`:
- Around line 179: commit the page outside parallelActionQueue.mu.

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: add into observations.Counters instead of replacing it.

In `pkg/sync/ledger_grants.go` (and ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53, ledger_assets.go:21, ledger_targeted_resource.go:29):
- Around line 60: use the two-value type assertion and return an error when the value is missing.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: avoid holding staged asset copies until Commit.

In `pkg/dotc1z/engine/pebble/internal/rawdb/families.go`:
- Around line 187-192: remove "asset rows" from the never-batched comment.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: hoist ledgerTokenHash("") to package level.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 182: assert the absolute allocation count.

In `pkg/dotc1z/engine/pebble/seal_cost_test.go`:
- Around line 27-28: remove the no-op assertion.

In `pkg/sync/initial_actions.go`:
- Around line 30: restore the partial-sync rationale comment.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: use shared op constants or a test tying the literals to the op String() values.

In `pkg/sync/ledger_cost_test.go`:
- Around line 74: add an always-on per-page cost bound to CI.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Comment thread pkg/dotc1z/engine/pebble/adapter.go
Comment thread pkg/sync/ledger_cost_runtime_test.go Outdated
Comment thread pkg/dotc1z/engine/pebble/adapter.go
Comment thread pkg/sync/ledger_cost_runtime_test.go Outdated
Comment thread pkg/sync/ledger_cost_runtime_test.go Outdated
Comment thread pkg/sync/ledger_baseline_audit_test.go Outdated
Comment thread pkg/dotc1z/engine/pebble/seal_cost_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Comment thread pkg/sync/ledger_scheduler.go
Comment on lines +72 to +75
worker, _ := ctx.Value(ledgerWorkerKey{}).(int)
if worker < 0 || worker >= int(c1zstore.TakeoverBucketWorker) {
return errors.New("invalid ledger worker index")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: two things in this guard.

  1. The discarded ok makes a missing or wrong-typed ledgerWorkerKey{} indistinguishable from a deliberate worker 0, and the bounds check below cannot detect it. implementation.md §17 says sequential phases intentionally use worker zero, but that intent currently rests on a dropped type assertion — any future call site that forgets to seed the index silently shares bucket 0 instead of failing. Consider the two-value form plus an explicit worker-zero default at the sequential call sites.

  2. int(c1zstore.TakeoverBucketWorker) is a constant conversion of uint32 = 0xFFFFFFFE; on a 32-bit GOARCH that value is not representable in int and the package fails to compile. No 32-bit release target exists here, but this is a library hundreds of connectors import. Comparing in uint32 space avoids it:

Suggested change
worker, _ := ctx.Value(ledgerWorkerKey{}).(int)
if worker < 0 || worker >= int(c1zstore.TakeoverBucketWorker) {
return errors.New("invalid ledger worker index")
}
worker, _ := ctx.Value(ledgerWorkerKey{}).(int)
if worker < 0 || uint32(worker) >= c1zstore.TakeoverBucketWorker {
return errors.New("invalid ledger worker index")
}

Comment thread pkg/sync/hooks.go Outdated
// carry no `test` prefix of their own — any `test`-prefixed field elsewhere
// in the package is a seam that escaped this struct.
type syncTestHooks struct {
ledgerHandler func(context.Context, *Action, *ledgerPage) error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: ledgerHandler is the only field here without the per-seam doc comment the other three carry, and it does not match the type's own description of "observation and fault-injection points". It replaces the production record handler: in invokeActionPage the handler argument is dropped on the ledgered path and a nil ledgerHandler returns "ledger production handlers are not integrated" rather than running production code. A reader of this file would conclude it is an observer like checkpointHook. Document what it substitutes for and that it is the only executable body on the ledgered path until K2d lands.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Comment thread pkg/sync/ledger_scheduler.go Outdated
Comment thread pkg/sync/ledger_scheduler.go Outdated
child.Spawned = recorded.Spawned
children = append(children, child)
}
return s.nextPageOrFinishAction(ctx, action, row.NextPageToken, children...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Bug: the replay transition drops row.TypeScopedPlanned. TypeScopedPlanned is not part of LedgerActionIdentity, so it survives only on the row — which is why walkWithSeen restores it onto the continuation (ledger_walk.go:59). Here the continuation keeps the in-memory action's value (false for a child just pushed by a parent's transition), so when the next page of a SyncEntitlementsOp/SyncGrantsOp root runs, !action.TypeScopedPlanned is true again (syncer.go:2051, syncer.go:2613) and the whole type-scoped fan-out is re-planned — silent duplicate actions. Set s.markTypeScopedPlanned(action) (or copy the flag) from row.TypeScopedPlanned before transitioning.

Comment thread pkg/sync/initial_actions.go
Comment thread pkg/sync/ledger_scheduler.go Outdated
if row.Scrubbed {
return errLedgerScrubbedUnfinished
}
children := make([]Action, 0, len(row.Children))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: replay reconstructs only children and the next token, so a page that originally finished as a warning replays as a clean completion — nextPageOrFinishAction reaches finishActionLocked(..., false) and invokeActionPage returns nil, so the caller never calls finishActionWithWarning. LedgerRow has no warning marker (the count lives only in the counter bucket), so replay cannot recover it, and the in-memory ratio that gates ErrTooManyWarnings under-counts. Either record the warning on the row or note this as an explicit open item alongside the pending fact restoration.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

_, err = s.ledger.runPageWithCommit(ctx, uint32(worker), ledgerIdentity(action), func(pageCtx context.Context, page *ledgerPage) error {
invocation.page = page
page.row.Spawned = action.Spawned
page.row.TypeScopedPlanned = action.TypeScopedPlanned

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: page.row.TypeScopedPlanned is snapshotted here, before the handler runs, but markTypeScopedPlanned (syncer.go:2100, syncer.go:2663) sets action.TypeScopedPlanned during the handler — after nextPageOrFinishAction has already staged the transition. The page that plans the type-scoped fan-out therefore commits a row with TypeScopedPlanned=false, so the new restore at line 90 and ledger_walk.go:59 can never observe it for that page: a resume at the next page re-enters SyncEntitlements/SyncGrants with the flag clear and re-plans the whole type-scoped fan-out. Assign the row field from the transition/commit callback (or have markTypeScopedPlanned write through to invocation.page.row) so the row records the value leaving the page. Note the two tests that cover this set page.row.TypeScopedPlanned = true by hand (ledger_restore_test.go:129, ledger_walk_test.go:21), so the write side is untested.

Comment thread pkg/dotc1z/engine/pebble/ledger.go Outdated
Comment thread pkg/dotc1z/c1zstore/ledger.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Comment thread pkg/sync/initial_actions.go
if !finished && s.cfg.onlyExpandGrants {
return nil, conflict("unstarted", "nothing has been collected under this sync ID")
}
case SyncGrantExpansionOp.String(), SyncExternalResourcesOp.String():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (confidence: medium): when only the import is left in the queue, collectionFlagConflict no longer runs. Its external_source_configured / external_entitlement_id_filter checks used to refuse a resumer that leaves out WithExternalResourceC1ZPath. That resumer now runs SyncExternalResources with s.externalResourceReader == nil, and listExternalResourceTypes / GetEntitlement dereference the nil interface and panic, where it used to get a clean ErrLedgerStateConflict. Suggest refusing (or comparing only the external fields) when SyncExternalResourcesOp is queued and s.externalResourceReader == nil, and adding a third case to TestLedgerExpansionPassWithExternalImportResumes with no external path.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit b6834d0126c0

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 17 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base eb63f1b54771.
Review mode: incremental since 75b38cd6
View review run

Review Summary

What the new commits change

  • setStore (syncer.go:3886-3893): stores with Engine == "" or an unknown engine take the token path again (CO-040). A Pebble store without PageLedgerStore, or a non-Pebble store that has one, is still refused.
  • Flag-conflict scans (ledger_lifecycle.go:137,185): SyncExternalResourcesOp is now grouped with the expansion step, so a crashed expansion pass that has an external import can be resumed.
  • Tests and docs: new routing and resume tests, and matching updates to the verification docs.

Risk triage for this push (per the trusted criteria)

  • Silence: yes. A wrong conflict decision lets a resume go ahead quietly.
  • Durability: yes. It writes to the c1z.
  • Uncontrolled dimensions: yes. The outcome depends on where the crash happened.
  • Consumer distance: low.
  • Verdict: the PR as a whole stays HIGH. This increment is narrow, and it adds a reproducing test: TestLedgerExpansionPassWithExternalImportResumes uses fault injection at CompletePendingWork.
  • Escalation: the full pass-set review per docs/BUG_CATCHING.md §6 is still recommended for the whole PR. This single-shot review is advisory sampling, not coverage.

How the trusted criteria were applied

  • Exported API stability: the PageLedgerStore/PageWriter growth is still there and version.go is unchanged.
  • Defaults: attaching a store is back to main's behavior.
  • Scope control: the new test double matches the shape connectors use.

Coverage

  • The incremental artifact (273 lines, partial: false) was reviewed in full.
  • gh pr diff refused the full PR diff (more than 20,000 lines), and the base commit is not in the local checkout. This run therefore did not re-scan the full diff. The security/correctness pass rests on the complete incremental diff and on earlier full passes of the unchanged code.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  1. New (confidence: medium) pkg/sync/ledger_lifecycle.go:185: when only SyncExternalResourcesOp is queued, collectionFlagConflict is skipped. A resumer without WithExternalResourceC1ZPath used to be refused on external_source_configured. It now reaches SyncExternalResources with a nil externalResourceReader and panics.
  2. Prior — still present, pkg/dotc1z/c1zstore/ledger.go + pkg/sdk/version.go:3: the exported PageLedgerStore/PageWriter interfaces gain required methods, but the version is still v0.30.1.
  3. Prior — still present (confidence: medium), pkg/dotc1z/engine/pebble/adapter.go:111-117: starting a new sync over an open pass throws away its progress, and the only record is a Warn log.
  4. Prior — still present, pkg/dotc1z/engine/pebble/adapter.go:113: workState() is only used for that diagnostic, but its error aborts StartNewSync.
  5. Prior — still present (confidence: medium), pkg/sync/ledger_takeover.go:106-111: a legacy stack of exactly [grant-expansion] is treated as Expanding, so --dont-expand-grants is refused on every retry.
  6. Prior — still present (author kept it on purpose; see the evidence.md entry), pkg/sync/initial_actions.go:19-30: the only-expand planning change also applies to SQLite. TestInitialActionBaseline covers it.
  7. Prior — still present (open thread), docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard. Neither exists in pkg/.
  8. Prior — still present (open thread), docs/verification/syncer-on-ledger/tools/run-cost.py:24: the script runs TestLedgerCostBaseline/TestLedgerCostPublic, which no longer exist.
  9. Prior — still present, pkg/dotc1z/engine/pebble/ledger_report_references.go:128: the op strings are copied as literals from pkg/sync.
  10. Prior — still present (open thread), pkg/dotc1z/engine/pebble/adapter_page.go:82: cloned asset payloads stay in memory until Commit.
  11. Prior — still present (open thread), pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:182: the check pins how allocations scale, but not the absolute count the docs state.
  12. Prior — still present, pkg/sync/hooks.go: the ledger* seams have no doc comments.
  13. Prior — still present (open thread), pkg/sync/initial_actions.go:40: the early return for partial syncs has lost its explanation.
  14. Prior — still present (open thread), pkg/sync/ledger_grants.go:60: the single-value type assertion on a context value panics if the value is missing.
  15. Prior — still present (open threads, merged), pkg/sync/ledger_resource_types.go:111: the code assigns a new counters map instead of adding to the existing one.
  16. Prior — still present (open thread), pkg/sync/ledger_scheduler.go:95: worker, _ := makes a missing worker look the same as worker 0.
  17. Prior — still present (open thread), pkg/sync/ledger_scheduler.go:96-98: the bounds-guard concern is unchanged.

Resolved prior findings

Fixed in this push

  • setStore blocker (empty or unknown engine refused): fixed at pkg/sync/syncer.go:3886-3893. The default: arm now refuses only a store that has a ledger. Covered by TestNewSyncerStoreEngineRouting and the flipped attachment-table cells. The allow-list thread is merged into this item.
  • Expansion-pass external import refused: ledger_lifecycle.go:137,185 now group SyncExternalResourcesOp with expansion. Covered by TestLedgerExpansionPassWithExternalImportResumes.

Fixed earlier, confirmed in current code

  • ErrLedgerStateConflict is returned.
  • BeginExpanding has a production caller.
  • ledger_empty.go tolerates ErrNotFound.
  • The ledger_report_scan.go:58 hash is no longer recomputed per row.
  • The families.go and DropLedger docs are corrected.
  • ledgerDebug is driven by an option.
  • The ledger_scheduler.go:114 snapshot is fixed.
  • Run accounting accumulates.
  • Archive options fall back to the first-attempt fact.

Obsolete

  • BeginPass replaced the ClearRows/ClearLedgerRows/purgeMarkedResidue code (the ledger.go:770 threads).
  • Local steps replaced the expansion, external and static-entitlement handlers.
  • The scheduler's replay, commit-under-lock (:178) and publish-ordering (:190) code was rewritten.
  • The meta, cost, prototype, audit and seal-cost tests and machine.json were deleted.
  • The lint-claim threads no longer apply.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/sync/ledger_lifecycle.go`:
- Around line 177-198: the LedgerQueueCollecting scan now skips collectionFlagConflict when only SyncGrantExpansionOp/SyncExternalResourcesOp are queued. A resumer without WithExternalResourceC1ZPath then runs SyncExternalResources with s.externalResourceReader == nil, and listExternalResourceTypes/GetEntitlement panic. When SyncExternalResourcesOp is queued and s.externalResourceReader is nil, return ErrLedgerStateConflict (or compare only the external_* fields from first_report_options). Do the same in legacyTokenFlagConflict (~line 137). Add a no-external-path subcase to TestLedgerExpansionPassWithExternalImportResumes.

In `pkg/sdk/version.go` and `pkg/dotc1z/c1zstore/ledger.go`:
- PageLedgerStore/PageWriter gained required methods, which breaks out-of-repo implementations. Bump the 0.x minor version and add a migration note to the PR.

In `pkg/dotc1z/engine/pebble/adapter.go`:
- Around line 111-117: workState() is used only for a Warn diagnostic, but its error aborts StartNewSync. Log the error and continue to ResetForNewSync. Consider surfacing the discard of an open pass more prominently than a Warn.

In `pkg/sync/ledger_takeover.go`:
- Around line 106-111: legacyStackPhase maps a stack of exactly [grant-expansion] to Expanding, so dont-expand-grants is refused on every retry. Document this, or allow dont-expand-grants to seal without expansion.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Lines 60 and 67: replace the citations of the deleted TestLedgerGuardMutationSurface/TestLedgerSessionWriteGuard with tests that exist.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Line 24: the referenced tests no longer exist. Point the script at existing tests, or fail when result_path is not written.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the copied op-string literals with shared constants, or add a test that pins them to the pkg/sync ActionOp strings.

In `pkg/sync/ledger_grants.go`:
- Line 60: use the two-value type assertion on ctx.Value(ledgerInvocationKey{}) and return an error when the value is missing.

In `pkg/sync/ledger_resource_types.go`:
- Line 111: add into page.observations.Counters instead of replacing the map.

In `pkg/sync/ledger_scheduler.go`:
- Line 95: check ok on ctx.Value(ledgerWorkerKey{}) so a missing worker is distinguished from worker 0.

Reviewed commit: 5db93030043a

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found — see the full review report

@kans
kans force-pushed the matt.kaniaris/CXE-1358/syncer-ledger-plan branch from 5db9303 to b6834d0 Compare October 5, 2026 18:40
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit b6834d0126c0

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 658269acbc3d.
Review mode: full
View review run

Review Summary

On Pebble, this PR replaces checkpoint-token resume with a page-atomic ledger. Each page commits its records, accounting and pending-work transitions in one batch. A durable pending-work queue with work IDs and revisions drives resume, and existing checkpoint tokens are taken over into that queue. A lifecycle state machine (Collecting/Expanding/Sealing/Sealed, plus BeginPass) and a sealed stats report complete the change. SQLite still checkpoints.

Coverage:

  • Production code: the full diff exceeds gh pr diff's limit (199 files, +27k), so the correctness pass worked from the per-file list. It read the production files locally and ran two bounded read-only passes, one over pkg/sync ledger, scheduler and takeover code and one over Pebble pending_work, ledger, page unit and adapter code. Neither found a confident bug. The checks covered batch atomicity, revision and staleness checks, key bounds, iterator closers, overflow guards and worker-bucket races.
  • Wire and CI: the proto change is additive only (new fields 18–25 and new messages), and buf-lint-and-breaking-change-detection, go-lint and go-test pass on this SHA.
  • Not reviewed: test files and verification docs, most of the line count, were not reviewed line by line.

Repo criteria applied:

  • Risk triage (HIGH):
    • Silence: yes. A wrong pending-work transition skips work without any error.
    • Durability: yes. The ledger format lives in the c1z artifacts.
    • Uncontrolled dimensions: yes. Crash timing, worker schedule and which SDK version wrote the artifact all matter.
    • Consumer distance: future SDK versions read these artifacts.
    • Remediation: rung 3. The PR documents a downgrade boundary for the ledger format.
    • Review-blind classes: multi-artifact and schedule. The PR names the right instruments, and both are present as tests in the diff: a two-artifact cross-version harness (a real eb63f1b5 checkpoint and v0.30.1 service-mode rollback) and 28 crash-injection combinations.
    • A full pass-set review per docs/BUG_CATCHING.md §6 is still recommended. This single-shot review is advisory sampling, not coverage.
    • Cost contracts: the expansion and compaction paths keep main's handlers. The per-checkpoint loop is now per-page commit, and the cost drivers stay opt-in (see the run-cost.py item).
  • Exported API stability: PageLedgerStore and PageWriter both shipped in v0.35.0 and are reshaped without a version bump (new suggestion below).
  • Defaults: attaching a store is back to main's behavior.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • New + Prior — still present (merges the outdated c1zstore/ledger.go interface-growth threads) pkg/dotc1z/c1zstore/ledger.go:329: both interfaces shipped in v0.35.0. PageLedgerStore drops TakeoverToken, BoundSyncFinished and EndSyncWithStats, and about 15 required methods are added across the two interfaces. The ledger format also gains a downgrade boundary, yet pkg/sdk/version.go is still v0.35.0. Medium confidence: the release process may bump it.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_references.go:128: "grant-expansion" and "list-external-resources" are bare literals copied from pkg/sync op strings. Posted a fresh inline comment because the old thread is outdated.
  • Prior — still present pkg/sync/ledger_resource_types.go:111: page.observations.Counters = map[...]{...} replaces the map, while every sibling handler accumulates into it (e.g. ledger_entitlements.go:148-152, ledger_resources.go:162-166). This is two threads describing one issue.
  • Prior — still present pkg/sync/ledger_grants.go:60: a single-value ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation) panics instead of returning an error. The same shape is at ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53 and ledger_targeted_resource.go:29, while sibling entry points use the two-value form.
  • Prior — still present pkg/sync/ledger_scheduler.go:95-98: worker, _ := ctx.Value(ledgerWorkerKey{}).(int) treats a missing worker as worker 0.
  • Prior — still present pkg/sync/ledger_scheduler.go:178 / parallel_syncer.go:652,866: the ledger page commit (pending.commit() in transitionActionState, syncer.go:799-803) still runs while parallelActionQueue.mu is held, which serializes durable commits across workers. The thread was resolved without a code change.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_scan.go:58: emptyHash := ledgerTokenHash("") is computed in ledgerReportProject, which runs once per ledger row (:334). That is a SHA-256 and an allocation per row; a package-level value would do.
  • Prior — still present pkg/dotc1z/engine/pebble/adapter_page.go:82: staged asset payloads are cloned and held in the page unit until Commit.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:183: wideAllocs <= narrow pins only the allocation shape, not the absolute count the docs claim.
  • Prior — still present pkg/sync/initial_actions.go:40: the rationale for skipping expansion and external import on targeted (partial) syncs was dropped during extraction.
  • Prior — still present pkg/sync/hooks.go:22-25: the four ledger* seams lack the per-seam docs the other hook fields carry.
  • Prior — still present docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist anywhere in the repo.
  • Prior — still present docs/verification/syncer-on-ledger/tools/run-cost.py:24: the script runs TestLedgerCostBaseline and TestLedgerCostPublic, which were deleted. The driver "succeeds" without ever writing results.

Resolved prior findings

  • Fixed — empty or unknown store engines and the allow-list (syncer.go bug and suggestion threads): store attachment matches main again. Only a Pebble store without PageLedgerStore, or a non-Pebble store with one, is refused.
  • Fixed — ErrLedgerStateConflict never returned: it is now returned at ledger_lifecycle.go:72,122.
  • Fixed — BeginExpanding had no caller: it is called at parallel_syncer.go:415.
  • Fixed — ledger_empty.go:29/30: ErrNotFound is tolerated at line 28.
  • Fixed — scheduler TypeScopedPlanned snapshot (:114): handlers now set page.row.TypeScopedPlanned directly (ledger_grants.go:47, ledger_entitlements.go:42).
  • Fixed — ledgerDebug tied to zap debug level: it is now driven by cfg.ledgerDebug (ledger_report_options.go:44, ledger_sync.go:32).
  • Fixed — families.go engine-meta wording: the stale "asset rows / never batched" text is gone.
  • Fixed — DropLedger doc: it now describes the drop (c1zstore/ledger.go, LedgerArchive).
  • Fixed — SealCost.LedgerPurge missed the marked purge: both purge kinds are timed (adapter.go:477-484).
  • Fixed — seal_cost_test.go no-op mutation: removed.
  • Fixed — go-lint failure and the evidence "zero issues" claims: go-lint passes on this SHA.
  • Obsolete — scheduler commit/transition ordering (:190): the ledgered transitionActionState commits and then publishes via publishPendingTransition, and no longer calls s.run.transitionAction (syncer.go:799-806).
  • Obsolete — ClearRows, ClearLedgerRows and purgeMarkedResidue under lifecycleMu (ledger.go:780 and related): replaced by BeginPass.
  • Obsolete — options-fact archive link: replaced by LedgerFactFollowOnPass.
  • Obsolete — scrubbed, TypeScopedPlanned and warning replay in the scheduler: the replay walk was replaced by the durable pending-work queue.
  • Obsolete — ledger_run_accounting.go counter overwrite: the code was rewritten.
  • Obsolete — static-entitlement inner loop: replaced by per-resource-page materialization (ledger_static_materialization.go).
  • Obsolete — threads on deleted files: ledger_expansion.go (×2), ledger_external.go (×3) and ledger_external_delete.go, where main's handlers are retained. Also machine.json, ledger_report_prototype_test.go, ledger_cost_runtime_test.go (×4), ledger_baseline_audit_test.go and sync_primitives_meta_test.go.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 329: PageLedgerStore/PageWriter shipped in v0.35.0 and this PR removes and adds required methods and introduces a ledger-format downgrade boundary. Bump the minor version in pkg/sdk/version.go (e.g. v0.36.0) and add a migration note for custom store implementers, or confirm the release process does it.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the bare "grant-expansion" / "list-external-resources" literals with shared exported constants (e.g. in c1zstore) used by pkg/sync's ActionOp strings, or add a test asserting they equal SyncGrantExpansionOp.String() / SyncExternalResourcesOp.String().

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: accumulate instead of replacing: initialize page.observations.Counters if nil, then `+= invalid` on "ingest.invalid_resource_types_observed", matching ledger_entitlements.go:148-152.

In `pkg/sync/ledger_grants.go` (and ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53, ledger_targeted_resource.go:29):
- Around line 60: use the two-value type assertion for ctx.Value(ledgerInvocationKey{}) and return an error when the invocation is missing instead of panicking.

In `pkg/sync/ledger_scheduler.go`:
- Around line 95-98: use `worker, ok := ctx.Value(ledgerWorkerKey{}).(int)` and return an error when !ok so a missing worker index is not silently treated as worker 0.
- Around line 178 (with parallel_syncer.go:652,866): the durable page commit runs while parallelActionQueue.mu is held; consider committing outside the queue mutex (keeping the abort check atomic) so workers' commits are not serialized.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: hoist `ledgerTokenHash("")` to a package-level var so it is not recomputed per row.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: staged asset payloads stay resident until Commit; document or bound the per-page asset memory.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 183: also assert the absolute allocation count the docs claim, not only wideAllocs <= narrow.

In `pkg/sync/initial_actions.go`:
- Around line 40: restore the comment explaining why targeted/partial syncs skip grant expansion and external resources (related resources are likely missing).

In `pkg/sync/hooks.go`:
- Around line 22-25: add per-seam doc comments for ledgerCommitted, ledgerStop, ledgerWalk and ledgerHandler like the other hook fields.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Around line 60 and 67: C37/C44 cite deleted tests TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard; point them at existing tests or mark the coverage as removed.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Around line 24: the driver runs deleted tests TestLedgerCostBaseline/TestLedgerCostPublic; update the test names to existing drivers or delete the tool, and fail when result_path is not written.

BoundSyncUnstarted(ctx context.Context) (bool, error)
}

type PageLedgerStore interface {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (medium confidence): PageLedgerStore and PageWriter both ship in v0.35.0. This PR removes TakeoverToken, BoundSyncFinished and EndSyncWithStats from PageLedgerStore and adds about 15 required methods across the two interfaces, and the PR body also states a Pebble ledger-format downgrade boundary. pkg/sdk/version.go is still v0.35.0, and the repo criteria ask for a 0.x minor bump to signal this kind of break. Bump the minor version here, or confirm the release process will do it.

for _, child := range row.GetChildren() {
id := child.GetIdentity()
// Local phases complete without collection history.
if id.GetOp() == "grant-expansion" || id.GetOp() == "list-external-resources" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: this still matches "grant-expansion" and now also "list-external-resources" as bare string literals, copied from pkg/sync's ActionOp.String(). If either op string changes, the reference check stops skipping local-phase children and starts failing on valid reports. Nothing in this package would catch that. Export the op names as shared constants (for example in c1zstore) or add a cross-package test that pins them.

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 758d5dc65159

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 658269acbc3d.
Review mode: full
View review run

Review Summary

On Pebble, this PR replaces checkpoint-token resume with a page-atomic ledger. Each page commits its records, accounting and pending-work transitions in one batch. A durable pending-work queue with work IDs and revisions drives resume, and existing checkpoint tokens are taken over into that queue. A lifecycle state machine (Collecting/Expanding/Sealing/Sealed, plus BeginPass) and a sealed stats report complete the change. SQLite still checkpoints.

Coverage:

  • Production code: the full diff exceeds gh pr diff's limit (199 files, +27k), so the correctness pass worked from the per-file list. It read the production files locally and ran two bounded read-only passes, one over pkg/sync ledger, scheduler and takeover code and one over Pebble pending_work, ledger, page unit and adapter code. Neither found a confident bug. The checks covered batch atomicity, revision and staleness checks, key bounds, iterator closers, overflow guards and worker-bucket races.
  • Wire and CI: the proto change is additive only (new fields 18–25 and new messages), and buf-lint-and-breaking-change-detection, go-lint and go-test pass on this SHA.
  • Not reviewed: test files and verification docs, most of the line count, were not reviewed line by line.

Repo criteria applied:

  • Risk triage (HIGH):
    • Silence: yes. A wrong pending-work transition skips work without any error.
    • Durability: yes. The ledger format lives in the c1z artifacts.
    • Uncontrolled dimensions: yes. Crash timing, worker schedule and which SDK version wrote the artifact all matter.
    • Consumer distance: future SDK versions read these artifacts.
    • Remediation: rung 3. The PR documents a downgrade boundary for the ledger format.
    • Review-blind classes: multi-artifact and schedule. The PR names the right instruments, and both are present as tests in the diff: a two-artifact cross-version harness (a real eb63f1b5 checkpoint and v0.30.1 service-mode rollback) and 28 crash-injection combinations.
    • A full pass-set review per docs/BUG_CATCHING.md §6 is still recommended. This single-shot review is advisory sampling, not coverage.
    • Cost contracts: the expansion and compaction paths keep main's handlers. The per-checkpoint loop is now per-page commit, and the cost drivers stay opt-in (see the run-cost.py item).
  • Exported API stability: PageLedgerStore and PageWriter both shipped in v0.35.0 and are reshaped without a version bump (new suggestion below).
  • Defaults: attaching a store is back to main's behavior.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • New + Prior — still present (merges the outdated c1zstore/ledger.go interface-growth threads) pkg/dotc1z/c1zstore/ledger.go:329: both interfaces shipped in v0.35.0. PageLedgerStore drops TakeoverToken, BoundSyncFinished and EndSyncWithStats, and about 15 required methods are added across the two interfaces. The ledger format also gains a downgrade boundary, yet pkg/sdk/version.go is still v0.35.0. Medium confidence: the release process may bump it.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_references.go:128: "grant-expansion" and "list-external-resources" are bare literals copied from pkg/sync op strings. Posted a fresh inline comment because the old thread is outdated.
  • Prior — still present pkg/sync/ledger_resource_types.go:111: page.observations.Counters = map[...]{...} replaces the map, while every sibling handler accumulates into it (e.g. ledger_entitlements.go:148-152, ledger_resources.go:162-166). This is two threads describing one issue.
  • Prior — still present pkg/sync/ledger_grants.go:60: a single-value ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation) panics instead of returning an error. The same shape is at ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53 and ledger_targeted_resource.go:29, while sibling entry points use the two-value form.
  • Prior — still present pkg/sync/ledger_scheduler.go:95-98: worker, _ := ctx.Value(ledgerWorkerKey{}).(int) treats a missing worker as worker 0.
  • Prior — still present pkg/sync/ledger_scheduler.go:178 / parallel_syncer.go:652,866: the ledger page commit (pending.commit() in transitionActionState, syncer.go:799-803) still runs while parallelActionQueue.mu is held, which serializes durable commits across workers. The thread was resolved without a code change.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_scan.go:58: emptyHash := ledgerTokenHash("") is computed in ledgerReportProject, which runs once per ledger row (:334). That is a SHA-256 and an allocation per row; a package-level value would do.
  • Prior — still present pkg/dotc1z/engine/pebble/adapter_page.go:82: staged asset payloads are cloned and held in the page unit until Commit.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:183: wideAllocs <= narrow pins only the allocation shape, not the absolute count the docs claim.
  • Prior — still present pkg/sync/initial_actions.go:40: the rationale for skipping expansion and external import on targeted (partial) syncs was dropped during extraction.
  • Prior — still present pkg/sync/hooks.go:22-25: the four ledger* seams lack the per-seam docs the other hook fields carry.
  • Prior — still present docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist anywhere in the repo.
  • Prior — still present docs/verification/syncer-on-ledger/tools/run-cost.py:24: the script runs TestLedgerCostBaseline and TestLedgerCostPublic, which were deleted. The driver "succeeds" without ever writing results.

Resolved prior findings

  • Fixed — empty or unknown store engines and the allow-list (syncer.go bug and suggestion threads): store attachment matches main again. Only a Pebble store without PageLedgerStore, or a non-Pebble store with one, is refused.
  • Fixed — ErrLedgerStateConflict never returned: it is now returned at ledger_lifecycle.go:72,122.
  • Fixed — BeginExpanding had no caller: it is called at parallel_syncer.go:415.
  • Fixed — ledger_empty.go:29/30: ErrNotFound is tolerated at line 28.
  • Fixed — scheduler TypeScopedPlanned snapshot (:114): handlers now set page.row.TypeScopedPlanned directly (ledger_grants.go:47, ledger_entitlements.go:42).
  • Fixed — ledgerDebug tied to zap debug level: it is now driven by cfg.ledgerDebug (ledger_report_options.go:44, ledger_sync.go:32).
  • Fixed — families.go engine-meta wording: the stale "asset rows / never batched" text is gone.
  • Fixed — DropLedger doc: it now describes the drop (c1zstore/ledger.go, LedgerArchive).
  • Fixed — SealCost.LedgerPurge missed the marked purge: both purge kinds are timed (adapter.go:477-484).
  • Fixed — seal_cost_test.go no-op mutation: removed.
  • Fixed — go-lint failure and the evidence "zero issues" claims: go-lint passes on this SHA.
  • Obsolete — scheduler commit/transition ordering (:190): the ledgered transitionActionState commits and then publishes via publishPendingTransition, and no longer calls s.run.transitionAction (syncer.go:799-806).
  • Obsolete — ClearRows, ClearLedgerRows and purgeMarkedResidue under lifecycleMu (ledger.go:780 and related): replaced by BeginPass.
  • Obsolete — options-fact archive link: replaced by LedgerFactFollowOnPass.
  • Obsolete — scrubbed, TypeScopedPlanned and warning replay in the scheduler: the replay walk was replaced by the durable pending-work queue.
  • Obsolete — ledger_run_accounting.go counter overwrite: the code was rewritten.
  • Obsolete — static-entitlement inner loop: replaced by per-resource-page materialization (ledger_static_materialization.go).
  • Obsolete — threads on deleted files: ledger_expansion.go (×2), ledger_external.go (×3) and ledger_external_delete.go, where main's handlers are retained. Also machine.json, ledger_report_prototype_test.go, ledger_cost_runtime_test.go (×4), ledger_baseline_audit_test.go and sync_primitives_meta_test.go.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 329: PageLedgerStore/PageWriter shipped in v0.35.0 and this PR removes and adds required methods and introduces a ledger-format downgrade boundary. Bump the minor version in pkg/sdk/version.go (e.g. v0.36.0) and add a migration note for custom store implementers, or confirm the release process does it.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the bare "grant-expansion" / "list-external-resources" literals with shared exported constants (e.g. in c1zstore) used by pkg/sync's ActionOp strings, or add a test asserting they equal SyncGrantExpansionOp.String() / SyncExternalResourcesOp.String().

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: accumulate instead of replacing: initialize page.observations.Counters if nil, then `+= invalid` on "ingest.invalid_resource_types_observed", matching ledger_entitlements.go:148-152.

In `pkg/sync/ledger_grants.go` (and ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53, ledger_targeted_resource.go:29):
- Around line 60: use the two-value type assertion for ctx.Value(ledgerInvocationKey{}) and return an error when the invocation is missing instead of panicking.

In `pkg/sync/ledger_scheduler.go`:
- Around line 95-98: use `worker, ok := ctx.Value(ledgerWorkerKey{}).(int)` and return an error when !ok so a missing worker index is not silently treated as worker 0.
- Around line 178 (with parallel_syncer.go:652,866): the durable page commit runs while parallelActionQueue.mu is held; consider committing outside the queue mutex (keeping the abort check atomic) so workers' commits are not serialized.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: hoist `ledgerTokenHash("")` to a package-level var so it is not recomputed per row.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: staged asset payloads stay resident until Commit; document or bound the per-page asset memory.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 183: also assert the absolute allocation count the docs claim, not only wideAllocs <= narrow.

In `pkg/sync/initial_actions.go`:
- Around line 40: restore the comment explaining why targeted/partial syncs skip grant expansion and external resources (related resources are likely missing).

In `pkg/sync/hooks.go`:
- Around line 22-25: add per-seam doc comments for ledgerCommitted, ledgerStop, ledgerWalk and ledgerHandler like the other hook fields.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Around line 60 and 67: C37/C44 cite deleted tests TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard; point them at existing tests or mark the coverage as removed.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Around line 24: the driver runs deleted tests TestLedgerCostBaseline/TestLedgerCostPublic; update the test names to existing drivers or delete the tool, and fail when result_path is not written.

Reviewed commit: b6834d0126c0

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found — see the full review report

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 758d5dc65159

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 658269acbc3d.
Review mode: incremental since b6834d01
View review run

Review Summary

The new commit (CO-041) moves the Pebble in-flight keyspace stamp into the same batch as the ledger write it describes. The v3 stamp is now set inside the batch, and inFlight is stored only after commit, at takeover, BeginCollecting, BeginExpanding, BeginPass, PutFacts, PutCounterBucket and pageUnit.Commit. The stamp is cleared inside the Drop batch (ledger.go:399-412) and the seal batch (adapter.go:520-533). The separate synced clear before ended_at is removed. As a result, a crash can no longer leave a v2 stamp over an unfinished ledgered sync, or a v3 stamp over no rows.

What I checked:

  • Every path that sets ended_at. If the stamp is set, active() reports true. The archive is then built (it is never nil when the build succeeds), so the seal batch clears the stamp. preserveRecovery keeps the stamp, as it did before.
  • Drop. DeleteRange and the engine-meta Set fall in disjoint key families. A non-synced Drop batch reverts as one unit.
  • Concurrency. Duplicate stamping by concurrent page commits is harmless.
  • No other writer remains. No caller of markInFlightLocked/clearInFlightLocked is left.
  • Tests. The updated crash-image tests (TestLedgerDiscardDurableSealCuts, TestLedgerTakeoverCrashImages mid, TestDropLedgerCommitFailureKeepsRowsAndStamp) match the new invariant.

I found no new issues.

Coverage:

  • Full diff. It exceeds gh pr diff's limit (~199 files), so the full-diff security and correctness pass relies on the prior run's per-file production pass. This push re-checked the 9 changed Pebble files and all 52 prior findings against the current code. The incremental artifact is not partial.
  • Criteria triage for this commit. Silence: yes (a wrong stamp is silent). Durability: yes (on-disk layout stamp). Uncontrolled dimensions: yes (crash timing, version pairs). Consumer distance: future and older SDKs. Verdict: HIGH.
    • Review-blind class: multi-artifact / crash schedule.
    • The instrument named in the PR is present in the diff: crash-image tests at every seal cut plus token-only-SDK opens (withTokenOnlySDK).
    • A full pass-set review per docs/BUG_CATCHING.md §6 is still recommended.
  • Wire and dependencies. There are no proto or go.mod changes in this push.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • Prior — still present pkg/dotc1z/c1zstore/ledger.go:329: PageLedgerStore and PageWriter shipped in v0.35.0 and are reshaped (methods removed and added), and the ledger format gains a downgrade boundary. pkg/sdk/version.go:3 is still v0.35.0. Medium confidence: the release process may bump it.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_references.go:128: "grant-expansion" and "list-external-resources" are bare literals copied from pkg/sync op strings.
  • Prior — still present pkg/sync/ledger_resource_types.go:111: page.observations.Counters = map[...]{...} replaces the map, while every sibling handler adds into it. This is two threads describing one issue.
  • Prior — still present pkg/sync/ledger_grants.go:59: a single-value ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation) assertion panics instead of returning an error. Sibling handlers have the same shape.
  • Prior — still present pkg/sync/ledger_scheduler.go:95: worker, _ := ctx.Value(ledgerWorkerKey{}).(int) treats a missing worker as worker 0.
  • Prior — still present pkg/sync/ledger_scheduler.go:178: the ledger page commit still runs while parallelActionQueue.mu is held, which serializes durable commits across workers.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_scan.go:58: ledgerTokenHash("") is recomputed for every row.
  • Prior — still present pkg/dotc1z/engine/pebble/adapter_page.go:82: staged asset payloads are cloned and held in the page unit until Commit.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:183: wideAllocs <= narrow pins only the allocation shape, not the absolute count the docs claim.
  • Prior — still present pkg/sync/initial_actions.go:40: the rationale for skipping expansion and external import on partial syncs was dropped.
  • Prior — still present pkg/sync/hooks.go:22-25: the four ledger* seams lack the per-seam docs the other hook fields carry.
  • Prior — still present docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist.
  • Prior — still present docs/verification/syncer-on-ledger/tools/run-cost.py:24: the script runs TestLedgerCostBaseline and TestLedgerCostPublic, which no longer exist. The driver "succeeds" without ever writing results.

Resolved prior findings

  • Fixed — syncer.go store attachment (bug and suggestion threads): an empty or unknown engine takes the token path again. Only a Pebble store without PageLedgerStore, or a non-Pebble store with one, is refused.
  • Fixed — ErrLedgerStateConflict never returned: it is now returned from prepareLedgerState (ledger_lifecycle.go).
  • Fixed — BeginExpanding had no caller: it is called from parallel_syncer.go.
  • Fixed — ledger_empty.go:29/30: ErrNotFound is tolerated at ledger_empty.go:28.
  • Fixed — scheduler TypeScopedPlanned snapshot: handlers set page.row.TypeScopedPlanned directly.
  • Fixed — ledgerDebug tied to the zap debug level: it is now driven by cfg.ledgerDebug.
  • Fixed — families.go engine-meta wording: the stale text is gone (families.go:85-90).
  • Fixed — DropLedger doc: it now describes the drop.
  • Fixed — SealCost.LedgerPurge: both purge kinds are timed (adapter.go:477-484).
  • Fixed — seal_cost_test.go no-op mutation: removed.
  • Fixed — go-lint failures and the evidence "zero issues" claims: go-lint passes.
  • Obsolete — scheduler commit/transition ordering, and the ClearRows / ClearLedgerRows / purgeMarkedResidue in-flight threads: replaced by publishPendingTransition, by BeginPass, and in this push by batch-staged stageMarkInFlight/stageClearInFlight.
  • Obsolete — options-fact archive link: replaced by LedgerFactFollowOnPass.
  • Obsolete — scrubbed, TypeScopedPlanned and warning replay: the replay walk was replaced by the durable pending-work queue.
  • Obsolete — ledger_run_accounting.go overwrite and the static-entitlement inner loop: the code was rewritten; static entitlements now use ledger_static_materialization.go.
  • Obsolete — threads on deleted files: ledger_expansion.go, ledger_external*.go, machine.json, ledger_report_prototype_test.go, ledger_cost_runtime_test.go, ledger_baseline_audit_test.go and sync_primitives_meta_test.go.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 329: PageLedgerStore/PageWriter (shipped in v0.35.0) change shape and the ledger format adds a downgrade boundary; bump the 0.x minor in pkg/sdk/version.go (or confirm the release process does) and add a migration note for custom store implementers.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the "grant-expansion" / "list-external-resources" literals with shared constants (e.g. exported from c1zstore) used by both pkg/sync op strings and this check, or add a test pinning equality with SyncGrantExpansionOp.String() and the external-resources op.

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: accumulate instead of replacing: initialize page.observations.Counters if nil, then += invalid on "ingest.invalid_resource_types_observed", matching ledger_entitlements.go.

In `pkg/sync/ledger_grants.go`:
- Around line 59: use the two-value form `invocation, ok := ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation)` and return an error when !ok; apply the same change in ledger_assets.go, ledger_entitlements.go, ledger_resources.go, ledger_static_entitlements.go and ledger_targeted_resource.go.

In `pkg/sync/ledger_scheduler.go`:
- Around line 95-98: use `worker, ok := ctx.Value(ledgerWorkerKey{}).(int)` and return an error when !ok so a missing worker index is not silently treated as worker 0.
- Around line 178: move the durable page commit out from under parallelActionQueue.mu (commit first, then take the lock to publish), or document why serialized commits are acceptable.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: hoist ledgerTokenHash("") to a package-level var so it is not recomputed for every row.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: staged asset payloads are cloned and retained until Commit; consider marshaling at stage time, or bounding per-page asset bytes.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 183: also assert the absolute allocation count the docs claim (e.g. require.LessOrEqual(t, wideAllocs, 3.0)).

In `pkg/sync/initial_actions.go`:
- Around line 40: restore the comment explaining that partial/targeted syncs skip grant expansion and external resources because related resources are likely missing.

In `pkg/sync/hooks.go`:
- Around line 22-25: add per-seam doc comments for ledgerCommitted, ledgerStop, ledgerWalk and ledgerHandler, like the other hook fields have.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Around line 60 and 67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist; point them at the current tests or mark the claims uncovered.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Around line 24: TestLedgerCostBaseline/TestLedgerCostPublic no longer exist; update the test names, and fail the script when result_path is not written.

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 548f4847fb03

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 658269acbc3d.
Review mode: incremental since b6834d01
View review run

Review Summary

The new commit (CO-041) moves the Pebble in-flight keyspace stamp into the same batch as the ledger write it describes. The v3 stamp is now set inside the batch, and inFlight is stored only after commit, at takeover, BeginCollecting, BeginExpanding, BeginPass, PutFacts, PutCounterBucket and pageUnit.Commit. The stamp is cleared inside the Drop batch (ledger.go:399-412) and the seal batch (adapter.go:520-533). The separate synced clear before ended_at is removed. As a result, a crash can no longer leave a v2 stamp over an unfinished ledgered sync, or a v3 stamp over no rows.

What I checked:

  • Every path that sets ended_at. If the stamp is set, active() reports true. The archive is then built (it is never nil when the build succeeds), so the seal batch clears the stamp. preserveRecovery keeps the stamp, as it did before.
  • Drop. DeleteRange and the engine-meta Set fall in disjoint key families. A non-synced Drop batch reverts as one unit.
  • Concurrency. Duplicate stamping by concurrent page commits is harmless.
  • No other writer remains. No caller of markInFlightLocked/clearInFlightLocked is left.
  • Tests. The updated crash-image tests (TestLedgerDiscardDurableSealCuts, TestLedgerTakeoverCrashImages mid, TestDropLedgerCommitFailureKeepsRowsAndStamp) match the new invariant.

I found no new issues.

Coverage:

  • Full diff. It exceeds gh pr diff's limit (~199 files), so the full-diff security and correctness pass relies on the prior run's per-file production pass. This push re-checked the 9 changed Pebble files and all 52 prior findings against the current code. The incremental artifact is not partial.
  • Criteria triage for this commit. Silence: yes (a wrong stamp is silent). Durability: yes (on-disk layout stamp). Uncontrolled dimensions: yes (crash timing, version pairs). Consumer distance: future and older SDKs. Verdict: HIGH.
    • Review-blind class: multi-artifact / crash schedule.
    • The instrument named in the PR is present in the diff: crash-image tests at every seal cut plus token-only-SDK opens (withTokenOnlySDK).
    • A full pass-set review per docs/BUG_CATCHING.md §6 is still recommended.
  • Wire and dependencies. There are no proto or go.mod changes in this push.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • Prior — still present pkg/dotc1z/c1zstore/ledger.go:329: PageLedgerStore and PageWriter shipped in v0.35.0 and are reshaped (methods removed and added), and the ledger format gains a downgrade boundary. pkg/sdk/version.go:3 is still v0.35.0. Medium confidence: the release process may bump it.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_references.go:128: "grant-expansion" and "list-external-resources" are bare literals copied from pkg/sync op strings.
  • Prior — still present pkg/sync/ledger_resource_types.go:111: page.observations.Counters = map[...]{...} replaces the map, while every sibling handler adds into it. This is two threads describing one issue.
  • Prior — still present pkg/sync/ledger_grants.go:59: a single-value ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation) assertion panics instead of returning an error. Sibling handlers have the same shape.
  • Prior — still present pkg/sync/ledger_scheduler.go:95: worker, _ := ctx.Value(ledgerWorkerKey{}).(int) treats a missing worker as worker 0.
  • Prior — still present pkg/sync/ledger_scheduler.go:178: the ledger page commit still runs while parallelActionQueue.mu is held, which serializes durable commits across workers.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_scan.go:58: ledgerTokenHash("") is recomputed for every row.
  • Prior — still present pkg/dotc1z/engine/pebble/adapter_page.go:82: staged asset payloads are cloned and held in the page unit until Commit.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:183: wideAllocs <= narrow pins only the allocation shape, not the absolute count the docs claim.
  • Prior — still present pkg/sync/initial_actions.go:40: the rationale for skipping expansion and external import on partial syncs was dropped.
  • Prior — still present pkg/sync/hooks.go:22-25: the four ledger* seams lack the per-seam docs the other hook fields carry.
  • Prior — still present docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist.
  • Prior — still present docs/verification/syncer-on-ledger/tools/run-cost.py:24: the script runs TestLedgerCostBaseline and TestLedgerCostPublic, which no longer exist. The driver "succeeds" without ever writing results.

Resolved prior findings

  • Fixed — syncer.go store attachment (bug and suggestion threads): an empty or unknown engine takes the token path again. Only a Pebble store without PageLedgerStore, or a non-Pebble store with one, is refused.
  • Fixed — ErrLedgerStateConflict never returned: it is now returned from prepareLedgerState (ledger_lifecycle.go).
  • Fixed — BeginExpanding had no caller: it is called from parallel_syncer.go.
  • Fixed — ledger_empty.go:29/30: ErrNotFound is tolerated at ledger_empty.go:28.
  • Fixed — scheduler TypeScopedPlanned snapshot: handlers set page.row.TypeScopedPlanned directly.
  • Fixed — ledgerDebug tied to the zap debug level: it is now driven by cfg.ledgerDebug.
  • Fixed — families.go engine-meta wording: the stale text is gone (families.go:85-90).
  • Fixed — DropLedger doc: it now describes the drop.
  • Fixed — SealCost.LedgerPurge: both purge kinds are timed (adapter.go:477-484).
  • Fixed — seal_cost_test.go no-op mutation: removed.
  • Fixed — go-lint failures and the evidence "zero issues" claims: go-lint passes.
  • Obsolete — scheduler commit/transition ordering, and the ClearRows / ClearLedgerRows / purgeMarkedResidue in-flight threads: replaced by publishPendingTransition, by BeginPass, and in this push by batch-staged stageMarkInFlight/stageClearInFlight.
  • Obsolete — options-fact archive link: replaced by LedgerFactFollowOnPass.
  • Obsolete — scrubbed, TypeScopedPlanned and warning replay: the replay walk was replaced by the durable pending-work queue.
  • Obsolete — ledger_run_accounting.go overwrite and the static-entitlement inner loop: the code was rewritten; static entitlements now use ledger_static_materialization.go.
  • Obsolete — threads on deleted files: ledger_expansion.go, ledger_external*.go, machine.json, ledger_report_prototype_test.go, ledger_cost_runtime_test.go, ledger_baseline_audit_test.go and sync_primitives_meta_test.go.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 329: PageLedgerStore/PageWriter (shipped in v0.35.0) change shape and the ledger format adds a downgrade boundary; bump the 0.x minor in pkg/sdk/version.go (or confirm the release process does) and add a migration note for custom store implementers.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the "grant-expansion" / "list-external-resources" literals with shared constants (e.g. exported from c1zstore) used by both pkg/sync op strings and this check, or add a test pinning equality with SyncGrantExpansionOp.String() and the external-resources op.

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: accumulate instead of replacing: initialize page.observations.Counters if nil, then += invalid on "ingest.invalid_resource_types_observed", matching ledger_entitlements.go.

In `pkg/sync/ledger_grants.go`:
- Around line 59: use the two-value form `invocation, ok := ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation)` and return an error when !ok; apply the same change in ledger_assets.go, ledger_entitlements.go, ledger_resources.go, ledger_static_entitlements.go and ledger_targeted_resource.go.

In `pkg/sync/ledger_scheduler.go`:
- Around line 95-98: use `worker, ok := ctx.Value(ledgerWorkerKey{}).(int)` and return an error when !ok so a missing worker index is not silently treated as worker 0.
- Around line 178: move the durable page commit out from under parallelActionQueue.mu (commit first, then take the lock to publish), or document why serialized commits are acceptable.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: hoist ledgerTokenHash("") to a package-level var so it is not recomputed for every row.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: staged asset payloads are cloned and retained until Commit; consider marshaling at stage time, or bounding per-page asset bytes.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 183: also assert the absolute allocation count the docs claim (e.g. require.LessOrEqual(t, wideAllocs, 3.0)).

In `pkg/sync/initial_actions.go`:
- Around line 40: restore the comment explaining that partial/targeted syncs skip grant expansion and external resources because related resources are likely missing.

In `pkg/sync/hooks.go`:
- Around line 22-25: add per-seam doc comments for ledgerCommitted, ledgerStop, ledgerWalk and ledgerHandler, like the other hook fields have.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Around line 60 and 67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist; point them at the current tests or mark the claims uncovered.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Around line 24: TestLedgerCostBaseline/TestLedgerCostPublic no longer exist; update the test names, and fail the script when result_path is not written.

Reviewed commit: 758d5dc65159

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found — see the full review report

kans and others added 7 commits October 6, 2026 13:21
Pebble collection pages commit records, facts, accounting and pending-work transitions atomically. Resume reads unfinished work in bounded windows through the existing scheduler. Separate work IDs and revisions allow repeated request tokens to execute normally. Existing checkpoint tokens seed the queue atomically during migration. SQLite continues checkpointing. Flags are locked per phase on both engines: a finished sync accepts only `WithOnlyExpandGrants`; on Pebble a resumer whose collection flags differ from the first attempt's recorded options is refused before any write; an expansion-only invocation plans from the file's skip facts and reads none of its own collection flags or targets, on SQLite as well as Pebble.

Expansion and external import retain main's handlers and optimized write paths; neither is buffered in a page transaction. Static entitlements materialize one resource page at a time, preserving template order and cold resume.

Successful collection archives a mechanical stats report, drops ledger history by default and purges discarded token data. Explicit ledger debug retains scrubbed history; retaining tokens requires an additional explicit option. Debug logging alone does not enable retention. Attempt metadata keeps first/latest options and folded prior-attempt accounting rather than growing with every retry.

Plain `EndSync()` preserves its existing lifecycle meaning: ending a run does not assert collection completed. It saves committed accounting, finalizes indexes, flushes and detaches while preserving data and pending recovery state through close/reopen. Explicit same-ID continuation can use that state. The existing end/cleanup/start-new reset sequence works; starting a new Pebble sync retains main's reset behavior. `EndSyncWithStats` remains the syncer's checked completion path. `WithConnectorStore(nil)` still falls back to the configured file path.

Custom sync stores: Pebble requires `PageLedgerStore` and is ledgered; every other engine, including an empty or unknown one, checkpoints through the token path as on main and must not expose `PageLedgerStore`. Pebble wrappers must forward the capability. There is no Pebble checkpoint fallback. The ledger interfaces gain methods; custom implementations must update accordingly.

**SDK downgrade constraint:** unfinished and early-ended Pebble artifacts can retain the new ledger recovery format. A host reusing those artifacts cannot downgrade its vendored SDK across that format boundary. Keep the host on a ledger-capable SDK, or discard affected recovery artifacts and start fresh before downgrading. This is distinct from rolling back a connector binary without changing the host's SDK.

Validation:

- The unexpanded-upload/host-expansion regression collects and saves three base grants with expansion disabled, copies the artifact, and requests expansion on the same sync ID. Exact six-grant output is checked after reopen with one and four workers, default/debug retention and normal/early ending. An explicit expansion-only call finishes any prior seal and performs the requested expansion in the same invocation, including unfinished empty queues and prepared seals. Ordinary recovery can finish the prior seal alone. Explicit expansion requests may redo deterministic expansion; accounting records both actual passes. Handoff failure tests cover seal/rebind errors, graph persistence and retention reset. Storage clear failure/crash tests preserve metadata/accounting and queue atomicity. Full suites, focused race tests, CI-equivalent lint and bounded independent correction review pass.
- Service-mode rollback uses actual daemon and connector subprocesses against a local TLS/gRPC C1 API. Targets `bba86699` (v0.30.1) and `eb63f1b5` pass 16 cases covering single/batched polling, spare off/on, and process kill/reported sync error. The same directory and options survive the version switch; old daemons upload usable data and complete another task, then the new SDK succeeds after roll-forward. Three ordinary repetitions pass; the current harness also passes race checks. This resource-only fixture simulates C1 redelivery and does not claim production workflow coverage.
- Full sync, public storage, Pebble and compactor suites pass; focused lifecycle and attachment race checks pass.
- A real `eb63f1b5` checkpoint is compared with that SDK's completed artifact and an uninterrupted ledger migration. Twenty-eight crash combinations cover takeover, grant and terminal commits, repeated crashes, WAL/flush recovery and 1/4 workers. Three repetitions and a race run pass, comparing records, indexes, digest, normalized stats and ledger accounting. Dropping imported action accounting makes the test fail.
- Independent bounded reviews covered lifecycle changes and the migration test's oracle. Seal/archive crash tests are separate from the combined historical fixture.
- Changed-code lint passes locally. Final-head CI is pending. Historical-SDK builds and performance drivers remain opt-in.

[Review guide and performance](https://github.com/ConductorOne/baton-sdk/blob/0bd0e5fb/docs/verification/syncer-on-ledger/README.md) · [Plan/change orders](https://github.com/ConductorOne/baton-sdk/blob/0bd0e5fb/docs/verification/syncer-on-ledger/plan.md) · [Evidence and review dispositions](https://github.com/ConductorOne/baton-sdk/blob/0bd0e5fb/docs/verification/syncer-on-ledger/evidence.md)

The evidence records executed coverage and residual gaps. It does not claim complete coverage of every original plan product or completion of the original full C49 performance matrix.

Successful page retries preserve every observed connector call, session usage report and reported wait in committed/live totals. The regression test checks exact totals, repeated-token page isolation, failed-attempt record/fact exclusion and close/reopen, with three race repetitions. Retry observations before any successful commit remain best-effort.

Squash of 240 commits on matt.kaniaris/CXE-1358/syncer-ledger-plan; pre-squash head kept at backup/CXE-1358-pre-squash (5db9303).

Co-authored-by: Cursor <cursoragent@cursor.com>
The in-flight stamp was its own synced write on both sides of a ledger's
life: set before the first ledger batch, cleared after the purge and before
the seal batch that writes ended_at. A crash between the clear and the seal
left a v2 stamp over an unfinished sync with no token; a token-only SDK
opened that file, seeded Init, and collected again on top of the sealed
records, after which this SDK refused the file as a legacy checkpoint beside
pending work. The arm side had the inverse image: a v3 stamp over no rows.

Ledger.stageMarkInFlight stages the stamp in the batch that writes the
first ledger key; Ledger.stageClearInFlight stages its return to v2 in the
Drop batch and the seal batch. TestLedgerDiscardDurableSealCuts asserts the
stamp at every seal image and its before-ended cell was red before this.
TestLedgerTakeoverCrashImages' mid cell now opens under a token-only SDK.
TestDropLedgerCommitFailureKeepsRowsAndStamp covers Drop's commit route.
CO-041.

Co-authored-by: Cursor <cursoragent@cursor.com>
…l import

A baseline token whose stack is exactly the expansion step cannot say
whether expansion ran: baseline SDKs clear its cursor and keep no graph.
main's resumer treats that stack as a step still to take, so
--dont-expand-grants skips it and seals. Taking it over as Expanding made
this SDK refuse that resume, which a self-hosted connector with the flag
in fixed configuration could never satisfy. legacyStackPhase is removed
and the refusal with it.

SyncExternalResources returns an error when the invocation has no
external resource source instead of dereferencing the nil reader; the
pending entry stays for an invocation that has one.

Comments on the in-flight stamp updated for CO-041.

Co-authored-by: Cursor <cursoragent@cursor.com>
first_options was written before parallelSync and compared only once
collection work was queued. An attempt that died between that write and
its Init commit left a record with no plan behind it; the next attempt,
finding only the Init seed, was not compared, planned under its own
flags, and left the first attempt's flags as the record a third attempt
was held to.

The record now rides the planning commit: the Init page
(recordFirstReportOptions), or the takeover batch when a legacy stack
already carries collection work. BeginFromToken takes facts as a map so
the batch can carry the value. The attempt snapshot (latest_options)
stays a coordinator write before any page.

CO-042 in plan.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
BeginCollecting takes facts map[string]string so the batch that seeds a
frontier's stack carrying collection work can stage first_options, as the
takeover batch does. Closes review H1 at 45ea871b: that reseed planned the
pass without the record, so the attempt that seeded it was never the lock.
TestLedgerFrontierSeedArmsCollectionFlagLock was red at the record
assertion before the change.
The token is the only record that collection saw external-match grants; the
takeover carries it into the pass C1 runs with only-expand and an external
source. The control arm (fact absent) leaves the placeholder principal.
Both branches shadowed err, so the deferred EndSpanWithError saw nil.
@kans
kans force-pushed the matt.kaniaris/CXE-1358/syncer-ledger-plan branch from 758d5dc to 548f484 Compare October 6, 2026 19:22
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 548f4847fb03

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base d36b62d1f466.
Review mode: full
View review run

Review Summary

On the Pebble engine, the syncer stops writing checkpoint tokens. Each page now commits its records, facts, counters and pending-work changes in one batch, and resume reads a durable pending-work queue in windows of at most 100. Legacy checkpoint tokens are migrated into that queue. Seal archives a stats report and drops ledger history by default. SQLite keeps checkpointing. records.proto changes are additive only (new field numbers, no renumbering), and the regenerated pb output is included. go.mod/go.sum are unchanged.

gh pr diff returns HTTP 406 for this PR (201 files, +27k lines), so the security and correctness pass used the local diff against base d36b62d1f466. All production files in pkg/sync and pkg/dotc1z were scanned, with two bounded read-only sub-agents covering the pkg/sync scheduler and handlers and the Pebble engine. Test and docs files were only spot-checked.

Criteria triage:

  • Silence: yes. A wrong pending-work transition would drop or repeat pages without an error.
  • Durability: yes. The c1z ledger format, the pending queue and the archived report all outlive the process.
  • Uncontrolled dimensions: yes. Correctness depends on crash timing, worker schedule, and which SDK version wrote the file (the PR documents a downgrade boundary).
  • Consumer distance: future SDK versions and custom stores.

Verdict: HIGH. The review-blind classes are multi-artifact (cross-version) and schedule/crash. The instruments the PR names are present in the diff: crash-image tests at each seal cut, a token-only-SDK open harness, and a real eb63f1b5 checkpoint migration fixture. A full pass-set review per docs/BUG_CATCHING.md §6 is still recommended before merge.

Exported-API criteria: the PageLedgerStore/PageWriter reshape is breaking, and it is covered under Suggestions.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • New pkg/sync/ledger_resources.go:57 and pkg/sync/ledger_static_entitlements.go:33,40: the shared err that the deferred EndSpanWithError reads is shadowed (resp, err := and for rts, err := range) or never assigned. Failing pages therefore end the syncer.SyncResources / syncer.SyncStaticEntitlements spans as successful. syncLedgerGrants assigns err correctly. High confidence; affects tracing only.
  • Prior — still present pkg/dotc1z/c1zstore/ledger.go:329: the exported PageLedgerStore/PageWriter, released in v0.35.0/v0.36.0, are reshaped, and the ledger format gains a downgrade boundary. pkg/sdk/version.go stays at v0.36.0, which was released today. Medium confidence, because the release process may do the bump.
  • Prior — still present pkg/sync/ledger_grants.go:60: a single-value ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation) assertion panics instead of returning an error. The same pattern is at ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53 and ledger_targeted_resource.go:29.
  • Prior — still present pkg/sync/ledger_scheduler.go:95-98: worker, _ := ctx.Value(ledgerWorkerKey{}).(int) treats a missing worker as worker 0.
  • Prior — still present pkg/sync/ledger_resource_types.go:111: page.observations.Counters = map[...]{...} replaces the map, while sibling handlers add into it. Two threads describe this one issue.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_scan.go:58: ledgerTokenHash("") is recomputed for every row.
  • Prior — still present pkg/dotc1z/engine/pebble/adapter_page.go:82: staged asset payloads are cloned and held in the page until Commit.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_references.go:128: "grant-expansion" and "list-external-resources" are bare literals copied from the pkg/sync op strings.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:183: wideAllocs <= narrow checks only that allocations don't grow with child count, not the absolute count the docs claim.
  • Prior — still present pkg/sync/initial_actions.go:40: the rationale for skipping expansion and external import on partial syncs was dropped.
  • Prior — still present pkg/sync/hooks.go:22-25: the four ledger* seams lack per-field docs. A fresh inline comment was posted because the old thread is outdated.
  • Prior — still present docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist.
  • Prior — still present docs/verification/syncer-on-ledger/tools/run-cost.py:24: it runs TestLedgerCostBaseline and TestLedgerCostPublic, which no longer exist, so the driver "succeeds" without writing results.

Resolved prior findings

  • Fixed — ledger commit held parallelActionQueue.mu (ledger_scheduler.go:178): queue.commitUnlocked is set for ledgered syncs (parallel_syncer.go:843), and transition releases q.mu around the commit (parallel_syncer.go:692-708).
  • Fixed — setStore refused empty or unknown engines (the syncer.go bug and suggestion threads): those engines take the token path again. Only a Pebble store without PageLedgerStore, or a non-Pebble store with one, is refused.
  • Fixed — ErrLedgerStateConflict never returned: it is now returned from prepareLedgerState (ledger_lifecycle.go).
  • Fixed — BeginExpanding had no caller: it is called from parallel_syncer.go.
  • Fixed — ledger_empty.go:29/30: ErrNotFound is tolerated at ledger_empty.go:28.
  • Fixed — TypeScopedPlanned snapshot (ledger_scheduler.go:114): handlers set page.row.TypeScopedPlanned directly (ledger_grants.go:46).
  • Fixed — ledgerDebug tied to the zap debug level: it is now driven by cfg.ledgerDebug.
  • Fixed — families.go engine-meta wording: the stale "asset rows / always single-key" text is gone.
  • Fixed — DropLedger doc: it now describes the drop.
  • Fixed — SealCost.LedgerPurge: both purge kinds are timed.
  • Fixed — seal_cost_test.go no-op mutation: removed.
  • Fixed — public-repo machine.json and the evidence.md lint claims: those files and claims were removed or rewritten.
  • Obsolete — ledger_scheduler.go:190 publish ordering and the scrubbed / replay / warning-replay threads: replaced by publishPendingTransition and the durable pending-work queue.
  • Obsolete — ClearRows / ClearLedgerRows / purgeMarkedResidue threads and the earlier PageWriter method-addition threads: replaced by BeginPass and batch-staged in-flight stamps. The remaining interface-break concern is tracked in the ledger.go:329 suggestion.
  • Obsolete — options-fact archive link: replaced by LedgerFactFollowOnPass.
  • Obsolete — ledger_run_accounting.go overwrite and the static-entitlement inner loop: rewritten; static entitlements now use ledger_static_materialization.go.
  • Obsolete — threads on deleted files: ledger_expansion.go, ledger_external*.go, ledger_report_prototype_test.go, ledger_cost_runtime_test.go, ledger_baseline_audit_test.go and sync_primitives_meta_test.go.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/sync/ledger_resources.go`:
- Around line 34-57: the deferred `uotel.EndSpanWithError(span, err)` reads an outer `err` that is never assigned (shadowed by `resp, err :=`, and the final return calls `collectLedgerResources` directly). Assign the outer err before each return (e.g. `err = s.collectLedgerResources(ctx, action); return err`), mirroring `syncLedgerGrants`.

In `pkg/sync/ledger_static_entitlements.go`:
- Around line 33-49: same problem — `return s.collectLedgerStaticEntitlements(...)` and `for rts, err := range` never set the outer err. Assign it before returning.

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 329: the exported `PageLedgerStore` and `PageWriter` interfaces released in v0.35.0/v0.36.0 change shape (methods removed and added), and the ledger format gains a downgrade boundary. Ensure the release carrying this bumps the 0.x minor version in `pkg/sdk/version.go`, and that its notes include the PR's downgrade constraint.

In `pkg/sync/ledger_grants.go`:
- Around line 60: replace the single-value `ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation)` assertion with the two-value form, and return an error when !ok. Apply the same change in ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53 and ledger_targeted_resource.go:29.

In `pkg/sync/ledger_scheduler.go`:
- Around line 95-98: use `worker, ok := ctx.Value(ledgerWorkerKey{}).(int)` and return an error when !ok, so a missing worker index is not silently treated as worker 0.

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: add `ingest.invalid_resource_types_observed` into `page.observations.Counters` (initializing the map if nil) instead of replacing the map.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: compute `ledgerTokenHash("")` once at package level instead of per row.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: staged asset payloads stay resident until Commit. Bound or document the per-page memory, or marshal at stage time.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the bare "grant-expansion" / "list-external-resources" literals with shared constants, or add a test that ties them to the pkg/sync op strings.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 183: also assert the absolute allocation count the docs claim, not only `wideAllocs <= narrow`.

In `pkg/sync/initial_actions.go`:
- Around line 40: restore the comment explaining why partial syncs skip grant expansion and external resources.

In `pkg/sync/hooks.go`:
- Around line 22-25: add a per-field doc comment to each ledger* seam, matching the other fields.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Around line 60 and 67: replace the citations of the deleted `TestLedgerGuardMutationSurface` / `TestLedgerSessionWriteGuard` with tests that still exist, or mark the coverage removed.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Around line 24: point the driver at tests that exist, and fail when the result file is not written.

return s.nextPageOrFinishAction(ctx, action, resp.GetNextPageToken(), actions...)
}

return s.collectLedgerResources(ctx, action)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (high confidence, tracing only): the deferred EndSpanWithError(span, err) reads the outer err, but nothing ever assigns it. resp, err := at line 34 shadows it, and this return doesn't set it. A failing ListResources page therefore closes syncer.SyncResources as successful. syncLedgerStaticEntitlements has the same problem (ledger_static_entitlements.go:33 and the for rts, err := range at line 40). Assign it the way syncLedgerGrants does (err = s.collect...; return err). This is the same class of bug 548f484 fixed for SyncExternalResources.

Comment thread pkg/sync/hooks.go
ledgerCommitted func(c1zstore.LedgerRow)
ledgerStop func(context.Context)
ledgerWalk func(bool)
ledgerHandler func(context.Context, *Action, *ledgerPage) error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (prior — still present; the earlier thread is outdated): ledgerCommitted, ledgerStop, ledgerWalk and ledgerHandler are the only seams here without a per-field doc comment. ledgerHandler also replaces the production handler rather than observing or injecting a fault. Add one line per seam saying what it observes or replaces and which boundary it guards.

BoundSyncUnstarted(ctx context.Context) (bool, error)
}

type PageLedgerStore interface {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (prior — still present, medium confidence; the earlier threads are outdated): PageLedgerStore and PageWriter shipped in v0.35.0/v0.36.0. Here they lose methods (TakeoverToken, BoundSyncFinished, EndSyncWithStats) and gain many, which breaks any out-of-repo implementation or wrapper. The ledger format also gains a downgrade boundary. pkg/sdk/version.go is unchanged at v0.36.0. Make sure the release that carries this is a minor bump and that its notes repeat the PR's downgrade constraint.

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

General PR Review: Drop Checkpoints, and persist Pebble sync pages atomically

Blocking Issues: 0 | Suggestions: 13 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base d36b62d1f466.
Review mode: full
View review run

Review Summary

On the Pebble engine, the syncer stops writing checkpoint tokens. Each page now commits its records, facts, counters and pending-work changes in one batch, and resume reads a durable pending-work queue in windows of at most 100. Legacy checkpoint tokens are migrated into that queue. Seal archives a stats report and drops ledger history by default. SQLite keeps checkpointing. records.proto changes are additive only (new field numbers, no renumbering), and the regenerated pb output is included. go.mod/go.sum are unchanged.

gh pr diff returns HTTP 406 for this PR (201 files, +27k lines), so the security and correctness pass used the local diff against base d36b62d1f466. All production files in pkg/sync and pkg/dotc1z were scanned, with two bounded read-only sub-agents covering the pkg/sync scheduler and handlers and the Pebble engine. Test and docs files were only spot-checked.

Criteria triage:

  • Silence: yes. A wrong pending-work transition would drop or repeat pages without an error.
  • Durability: yes. The c1z ledger format, the pending queue and the archived report all outlive the process.
  • Uncontrolled dimensions: yes. Correctness depends on crash timing, worker schedule, and which SDK version wrote the file (the PR documents a downgrade boundary).
  • Consumer distance: future SDK versions and custom stores.

Verdict: HIGH. The review-blind classes are multi-artifact (cross-version) and schedule/crash. The instruments the PR names are present in the diff: crash-image tests at each seal cut, a token-only-SDK open harness, and a real eb63f1b5 checkpoint migration fixture. A full pass-set review per docs/BUG_CATCHING.md §6 is still recommended before merge.

Exported-API criteria: the PageLedgerStore/PageWriter reshape is breaking, and it is covered under Suggestions.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • New pkg/sync/ledger_resources.go:57 and pkg/sync/ledger_static_entitlements.go:33,40: the shared err that the deferred EndSpanWithError reads is shadowed (resp, err := and for rts, err := range) or never assigned. Failing pages therefore end the syncer.SyncResources / syncer.SyncStaticEntitlements spans as successful. syncLedgerGrants assigns err correctly. High confidence; affects tracing only.
  • Prior — still present pkg/dotc1z/c1zstore/ledger.go:329: the exported PageLedgerStore/PageWriter, released in v0.35.0/v0.36.0, are reshaped, and the ledger format gains a downgrade boundary. pkg/sdk/version.go stays at v0.36.0, which was released today. Medium confidence, because the release process may do the bump.
  • Prior — still present pkg/sync/ledger_grants.go:60: a single-value ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation) assertion panics instead of returning an error. The same pattern is at ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53 and ledger_targeted_resource.go:29.
  • Prior — still present pkg/sync/ledger_scheduler.go:95-98: worker, _ := ctx.Value(ledgerWorkerKey{}).(int) treats a missing worker as worker 0.
  • Prior — still present pkg/sync/ledger_resource_types.go:111: page.observations.Counters = map[...]{...} replaces the map, while sibling handlers add into it. Two threads describe this one issue.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_scan.go:58: ledgerTokenHash("") is recomputed for every row.
  • Prior — still present pkg/dotc1z/engine/pebble/adapter_page.go:82: staged asset payloads are cloned and held in the page until Commit.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_references.go:128: "grant-expansion" and "list-external-resources" are bare literals copied from the pkg/sync op strings.
  • Prior — still present pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go:183: wideAllocs <= narrow checks only that allocations don't grow with child count, not the absolute count the docs claim.
  • Prior — still present pkg/sync/initial_actions.go:40: the rationale for skipping expansion and external import on partial syncs was dropped.
  • Prior — still present pkg/sync/hooks.go:22-25: the four ledger* seams lack per-field docs. A fresh inline comment was posted because the old thread is outdated.
  • Prior — still present docs/verification/syncer-on-ledger/current-coverage.md:60,67: C37 and C44 cite TestLedgerGuardMutationSurface and TestLedgerSessionWriteGuard, which no longer exist.
  • Prior — still present docs/verification/syncer-on-ledger/tools/run-cost.py:24: it runs TestLedgerCostBaseline and TestLedgerCostPublic, which no longer exist, so the driver "succeeds" without writing results.

Resolved prior findings

  • Fixed — ledger commit held parallelActionQueue.mu (ledger_scheduler.go:178): queue.commitUnlocked is set for ledgered syncs (parallel_syncer.go:843), and transition releases q.mu around the commit (parallel_syncer.go:692-708).
  • Fixed — setStore refused empty or unknown engines (the syncer.go bug and suggestion threads): those engines take the token path again. Only a Pebble store without PageLedgerStore, or a non-Pebble store with one, is refused.
  • Fixed — ErrLedgerStateConflict never returned: it is now returned from prepareLedgerState (ledger_lifecycle.go).
  • Fixed — BeginExpanding had no caller: it is called from parallel_syncer.go.
  • Fixed — ledger_empty.go:29/30: ErrNotFound is tolerated at ledger_empty.go:28.
  • Fixed — TypeScopedPlanned snapshot (ledger_scheduler.go:114): handlers set page.row.TypeScopedPlanned directly (ledger_grants.go:46).
  • Fixed — ledgerDebug tied to the zap debug level: it is now driven by cfg.ledgerDebug.
  • Fixed — families.go engine-meta wording: the stale "asset rows / always single-key" text is gone.
  • Fixed — DropLedger doc: it now describes the drop.
  • Fixed — SealCost.LedgerPurge: both purge kinds are timed.
  • Fixed — seal_cost_test.go no-op mutation: removed.
  • Fixed — public-repo machine.json and the evidence.md lint claims: those files and claims were removed or rewritten.
  • Obsolete — ledger_scheduler.go:190 publish ordering and the scrubbed / replay / warning-replay threads: replaced by publishPendingTransition and the durable pending-work queue.
  • Obsolete — ClearRows / ClearLedgerRows / purgeMarkedResidue threads and the earlier PageWriter method-addition threads: replaced by BeginPass and batch-staged in-flight stamps. The remaining interface-break concern is tracked in the ledger.go:329 suggestion.
  • Obsolete — options-fact archive link: replaced by LedgerFactFollowOnPass.
  • Obsolete — ledger_run_accounting.go overwrite and the static-entitlement inner loop: rewritten; static entitlements now use ledger_static_materialization.go.
  • Obsolete — threads on deleted files: ledger_expansion.go, ledger_external*.go, ledger_report_prototype_test.go, ledger_cost_runtime_test.go, ledger_baseline_audit_test.go and sync_primitives_meta_test.go.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/sync/ledger_resources.go`:
- Around line 34-57: the deferred `uotel.EndSpanWithError(span, err)` reads an outer `err` that is never assigned (shadowed by `resp, err :=`, and the final return calls `collectLedgerResources` directly). Assign the outer err before each return (e.g. `err = s.collectLedgerResources(ctx, action); return err`), mirroring `syncLedgerGrants`.

In `pkg/sync/ledger_static_entitlements.go`:
- Around line 33-49: same problem — `return s.collectLedgerStaticEntitlements(...)` and `for rts, err := range` never set the outer err. Assign it before returning.

In `pkg/dotc1z/c1zstore/ledger.go`:
- Around line 329: the exported `PageLedgerStore` and `PageWriter` interfaces released in v0.35.0/v0.36.0 change shape (methods removed and added), and the ledger format gains a downgrade boundary. Ensure the release carrying this bumps the 0.x minor version in `pkg/sdk/version.go`, and that its notes include the PR's downgrade constraint.

In `pkg/sync/ledger_grants.go`:
- Around line 60: replace the single-value `ctx.Value(ledgerInvocationKey{}).(*ledgerInvocation)` assertion with the two-value form, and return an error when !ok. Apply the same change in ledger_assets.go:21, ledger_entitlements.go:56, ledger_resources.go:61, ledger_static_entitlements.go:53 and ledger_targeted_resource.go:29.

In `pkg/sync/ledger_scheduler.go`:
- Around line 95-98: use `worker, ok := ctx.Value(ledgerWorkerKey{}).(int)` and return an error when !ok, so a missing worker index is not silently treated as worker 0.

In `pkg/sync/ledger_resource_types.go`:
- Around line 111: add `ingest.invalid_resource_types_observed` into `page.observations.Counters` (initializing the map if nil) instead of replacing the map.

In `pkg/dotc1z/engine/pebble/ledger_report_scan.go`:
- Around line 58: compute `ledgerTokenHash("")` once at package level instead of per row.

In `pkg/dotc1z/engine/pebble/adapter_page.go`:
- Around line 82: staged asset payloads stay resident until Commit. Bound or document the per-page memory, or marshal at stage time.

In `pkg/dotc1z/engine/pebble/ledger_report_references.go`:
- Around line 128: replace the bare "grant-expansion" / "list-external-resources" literals with shared constants, or add a test that ties them to the pkg/sync op strings.

In `pkg/dotc1z/engine/pebble/ledger_report_stream_checks_test.go`:
- Around line 183: also assert the absolute allocation count the docs claim, not only `wideAllocs <= narrow`.

In `pkg/sync/initial_actions.go`:
- Around line 40: restore the comment explaining why partial syncs skip grant expansion and external resources.

In `pkg/sync/hooks.go`:
- Around line 22-25: add a per-field doc comment to each ledger* seam, matching the other fields.

In `docs/verification/syncer-on-ledger/current-coverage.md`:
- Around line 60 and 67: replace the citations of the deleted `TestLedgerGuardMutationSurface` / `TestLedgerSessionWriteGuard` with tests that still exist, or mark the coverage removed.

In `docs/verification/syncer-on-ledger/tools/run-cost.py`:
- Around line 24: point the driver at tests that exist, and fail when the result file is not written.

Reviewed commit: 548f4847fb03

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found — see the full review report

@kans
kans merged commit 47f5027 into main Oct 6, 2026
16 checks passed
@kans
kans deleted the matt.kaniaris/CXE-1358/syncer-ledger-plan branch October 6, 2026 21:01
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.

2 participants