docs(cluster): RFC-004 — graph & schema apply design (Phase 4) - #168
Merged
Conversation
The design the implementation spec's exit criteria require before
graph-moving cluster apply ships. Core positions:
- Cluster recovery is roll-forward-only: the engine's own sidecars make every
graph-level operation atomic within the graph, so the cluster never rolls a
graph back — its sidecars (__cluster/recoveries/{ulid}.json) classify and
record, converging the ledger to observable reality (axiom 5) or surfacing
a loud pending-repair condition. Eight-row decision matrix, every row
testable with the Stage 3B failpoint harness.
- Irreversible operations (graph delete, allow_data_loss schema apply)
consume digest-bound approval artifacts written by a new cluster approve
command and retired into state.approval_records (axiom 11). A stale
approval can never authorize a different change.
- cluster apply gains an actor, threaded to apply_schema_as so engine Cedar
enforcement and commit attribution work unchanged; the cluster adds no
policy engine of its own.
- Deterministic ordering (creates -> schema applies -> catalog -> deletes),
per-resource apply groups, cross-graph atomicity explicitly not promised.
- Staged 4A graph create / 4B schema apply / 4C graph delete, each gated on
per-matrix-row failpoint tests.
Answers exit criteria 2 and 4 fully, 1/5/6 partially; 3/7/8/9 deferred to
their phases (coverage table in the RFC). Linked from the dev index and the
implementation spec's Phase 4 section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cas_base contract Two greptile findings: (1) D3 row 2 could not be evaluated for graph_delete (no manifest to version-check after prefix removal) and 'root absent, state already tombstoned' fell into the stale row — split into rows 7 (delete's analog of row 2) and 7b (the roll-forward), with expected_manifest_version documented as always null for the delete kind. (2) state_cas_base is now explicitly audit/diagnostics-only — the sweep never consults it; independent state mutations are handled by the ordinary CAS like any concurrent write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The design slice the implementation spec's exit criteria require before any graph-moving cluster apply code ships. Docs-only.
What the RFC settles
__cluster/recoveries/{ulid}.json, schema in §D2) exist to classify and record, converging the JSON ledger to observable reality (axiom 5) or surfacing a loudactual_applied_state_pendingcondition. An eight-row decision matrix (§D3) covers every crash window, each row testable with the Stage 3B failpoint harness; the sweep's only mutations are ordinary CAS-checked state writes under the existing lock.allow_data_lossschema apply): written by a newcluster approvecommand, consumed by apply only on exact digest match, retired intostate.approval_records(axiom 11's durable audit reference).allow_data_lossis never a CLI flag on apply.cluster apply --actorflows to the engine'sapply_schema_as, so Cedar enforcement and commit attribution work unchanged; the cluster adds no policy engine.Dispositions the implementation spec's high-risk decisions: #2 (derived graph roots), #3 (nested apply under sidecar coverage), #7 (the recovery sweep never auto-deletes). Exit-criteria coverage table maps 2 and 4 answered fully, 1/5/6 partially, 3/7/8/9 deferred to their phases.
Also: dev-index row under Active Implementation Plans, and a pointer from the implementation spec's Phase 4 section.
Verification
scripts/check-agents-md.sh— green (new doc linked from docs/dev/index.md)db/manifest/recovery.rs🤖 Generated with Claude Code
Greptile Summary
This docs-only PR introduces RFC-004, the Phase 4 design for cluster graph and schema apply. It establishes the cluster recovery sidecar schema, a roll-forward-only decision matrix for crash recovery, digest-bound approval artifacts for irreversible operations, actor threading through to the engine's
apply_schema_as, and a three-stage sequencing plan (4A graph create → 4B schema apply → 4C graph delete).graph_deletesidecars, and the "only rows 3/4/7b mutate state" claim conflicts with rows 5/6/8 also setting persistent status/condition fields.allow_data_lossto an explicit approval path, and preserves the single-final-CAS invariant from Stage 3A.Confidence Score: 3/5
The RFC is a design document, but two normative gaps in the D3 decision matrix could lead to implementation bugs — one of which would silently suppress the graph_delete_incomplete diagnostic for partial prefix-removal crashes, violating the loud-partials invariant the RFC explicitly commits to.
Both gaps are in the D3 recovery matrix, the load-bearing implementation contract. The row 1 / graph_delete overlap is a real ambiguity: a mid-removal crash leaves the manifest at its pre-deletion version, satisfying row 1's condition; a top-to-bottom sweep would delete the sidecar and emit no graph_delete_incomplete condition, contradicting the loud-partials invariant. The only-rows-3/4/7b-mutate claim needs qualification because rows 5/6/8 also require CAS-checked state writes to enforce the cross-command drifted/error blocking the RFC describes.
docs/dev/rfc-004-cluster-graph-schema-apply.md — specifically the D3 matrix (rows 1, 5, 6, 8 and the only-mutations claim).
Important Files Changed
Sequence Diagram
sequenceDiagram participant Op as Operator participant CLI as cluster apply participant Lock as State Lock participant Sidecar as __cluster/recoveries/ participant Engine as Omnigraph Engine participant State as state.json (CAS) Op->>CLI: cluster apply CLI->>Lock: acquire CLI->>CLI: run sweep (D3 matrix) CLI->>Sidecar: write sidecar (observed_manifest_version) CLI->>Lock: release CLI->>Engine: graph_create / schema_apply / graph_delete Engine-->>CLI: SchemaApplyResult (manifest_version) CLI->>Sidecar: rewrite sidecar (expected_manifest_version) CLI->>Lock: acquire CLI->>State: CAS write (all outcomes + approval_records) CLI->>Sidecar: delete sidecar CLI->>Lock: release Note over CLI,State: Crash anywhere leaves sidecar on disk Note over CLI,State: Next state-mutating command runs sweep (rows 1-8)Reviews (2): Last reviewed commit: "docs(cluster): RFC-004 review fixes — gr..." | Re-trigger Greptile