Skip to content

Build the promised artifact placement convergence pass #2152

Description

@gaborbernat

Problem

artifact-source.md promises a repair pass that reconciles artifact placement with the content store. It does not exist — there is no repair_artifact_placements anywhere in the tree.

That matters because placement completeness cannot be a write-side invariant. reclaim_guard.rs deliberately deletes the placement row before the bytes, documenting that an interruption leaves a digest whose row claims nothing while the bytes survive — the safe direction, since the reverse would promise bytes that are gone. So a correct system produces absent rows, and only convergence can restore the table.

There is a second problem with the pass as documented: it describes iterating placements. A pass that iterates placements can never add a row that was never written. To converge, it has to iterate the content store and reconcile against the table.

Required change

Build the convergence pass the documentation already promises, driven by the content store rather than by the table.

It must add rows for digests the store holds and the table does not, and retire rows for digests the table claims and the store does not. Both directions occur: the first from the eleven byte-commit paths that never recorded, the second from the reclaim window.

Bound it. The content store is the largest thing in the system, so this is a cursor pass with a resumable position, not a single transaction — the retention scan's batching is the local precedent, and the checkpoint work in peryx-storage is the more recent one.

Say what a concurrent reader observes mid-pass, and what happens when the pass races the reclaim window it exists to repair. Repairing a row that reclaim is deliberately removing would resurrect a promise the system just retired.

PlacementEvent is decided here, not before

PlacementEvent has four variants and only BytesVerified is constructed in production. The obvious move — delete the unconstructed ones — is wrong: Repaired { present: false } is precisely the transition that absorbed BytesRemoved when #2140 removed it, so deleting it deletes the eviction transition. And with only BytesVerified remaining, after() collapses to a one-arm match and the enum earns nothing.

So there are two honest outcomes, and this issue is where the evidence to choose exists:

  • Wire the lifecycle up. This pass constructs Repaired, eviction constructs Repaired { present: false }, and the enum becomes real.
  • Delete PlacementEvent outright and replace apply_placement_event with a direct mark_local, if the lifecycle it models is not one the system actually has.

"Keep only the constructed variants" lands on neither and should not be the answer.

Acceptance criteria

  • A digest the store holds with no placement row gains one; a row whose digest the store lacks is retired.
  • The pass is resumable and bounded, and an interrupted run leaves the table no worse than it found it.
  • Racing the reclaim window does not resurrect a row reclaim is removing; a test drives that interleaving by construction.
  • PlacementEvent ends this issue either fully wired or deleted, with the reasoning recorded.
  • The documentation in artifact-source.md matches what was built, including the iteration direction.

Boundary

The convergence pass and the event model it settles. Blocked by #2151, which defines what an absent row means — the pass cannot decide what to repair until absence has one meaning. #2141 adds the missing writes and should land first, so this converges a smaller gap.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:availabilityRuntime availability modes, replication, failover, and recoveryarea:cliCLI commands and operator workflowsarea:storageBlob stores, object storage, and storage backend workpriority:P2Performance, observability, or deferred featuretype:bugIncorrect behavior or missing validation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions