Expand description
ADR-099 (cross-op atomicity for bulk apply) — prepared write-plan types.
Async prepare materializes a synchronous write plan outside any
transaction; commit later applies its statements as DML under a per-op
SAVEPOINT. This module defines the plan shapes only, one family per
admissible verb group (update, delete, link, merge,
gtd.transition, gtd.complete, the governance verbs) — not yet wired
into a live handler or the dispatch path. Every plan is deliberately
inert (plain data, no async, no embedding reference).
Two validation-staleness invariants every plan must satisfy:
- Predicate-based plans carry an “all rows matching a condition”
effect as a statement evaluated inside the transaction
(
PlanPredicate), never as a prepare-time-enumerated row list. - Affected-row guards (
PlanStatement::guard) are attached to the exact statement they validate, checked in-transaction; a mismatch fails the op and rolls back the whole unit.
See docs/atomic-plan.md for why guards are per-statement rather than
per-plan.
Structs§
- AddEntity
Plan - Write plan for an
AddEntityproposal change: a fresh entity row plus its FTS document in the same atomic unit. Vector indexing remains a deferred effect because embedding may suspend. - AddNote
Plan - Write plan for an
AddNoteproposal change: a fresh note row plus its FTS document in the same atomic unit. - Affected
RowGuard - An affected-row guard (ADR-099 D1, rule 2): the row-count prepare assumed its target write would affect, re-verified in-transaction. A prepare-time validation is a plan hypothesis, never a commitment — if the guard does not hold at apply time, the op fails inside the atomic unit and the whole unit rolls back (ADR-099 acceptance criteria: “zero-row apply fails the unit”).
- Delete
Plan - Write plan for a
deleteop (soft or hard). - Edge
Natural Key - The natural key a committed symmetric edge update’s surviving row must
be looked up by (ADR-099 B3, second
half).
khive-db’sedge_symmetric_refresh_or_update_inplace_statementpair never trusts a prepare-time-computed target id (see that builder’s doc comment); a caller rendering this op’s result derives the actual surviving id by queryinggraph_edges’s ownUNIQUE(namespace, source_id, target_id, relation)constraint (e.g. viaKhiveRuntime::list_edgesfiltered on these fields — the same mechanism the atomiclinkop’s own result rendering already uses), strictly after commit. - Governance
Plan - Write plan for a governance op (
propose,review, orwithdraw— the event-sourced change-proposal lifecycle, ADR-046). - GtdComplete
Plan - Write plan for a
gtd.completeop (task lifecycle terminal transition). - GtdTransition
Plan - Write plan for a
gtd.transitionop (explicit task lifecycle change). - Link
Plan - Write plan for a
linkop (create a typed directed edge). Endpoint existence is checked structurally, not via an unanchored plan-level guard:statementis a guardedINSERT ... SELECT ... WHERE EXISTSshape whoseSELECTre-probes both endpoints inside the transaction, so the runner’s affected-row check on this one statement is the in-transaction existence probe (ADR-099 acceptance criteria’s dangling-edge case —[delete(X, hard), link(A, X)]— is closed by this guard failing once X is gone, regardless of statement ordering convention). - Merge
Plan - Write plan for a
mergeop (deduplicate two entities). Rewires and lifecycle writes are split into separate fields precisely so a guard is never ambiguous between them: the edge rewire is predicate-based (ADR-099 D1 rule 1) and may touch zero or many rows depending on earlier in-file writes, so it is never guarded; thefrom/intoentity lifecycle write assumes both rows exist, so it always is. - Plan
Predicate - The predicate a prepare pass validated a plan’s target against, replayed
as a statement evaluated inside the transaction (ADR-099 D1, rule 1:
“predicate-based plans wherever a write’s scope depends on current
state”). Carrying the predicate rather than a prepare-time-enumerated row
list is what lets a later op in the same file (e.g. an intervening
link) be visible to this plan’s apply. - Plan
Statement - One statement in a plan, paired with the guard (if any) that validates
it. Runner contract: a present
guardis checked against the affected-row count of applyingstatementalone (SqlWriter::execute’s return value for this statement), not a batch total and not another statement’s count.guard: Nonemeans prepare made no row-existence assumption about this particular statement (e.g. a cascade delete that may legitimately touch zero rows). - Update
Plan - Write plan for an
updateop (entity or note shape — ADR-099 D3’supdatecaveat covers both substrates the same way: row/FTS DML in the plan, any reindex deferred topost_commit).
Enums§
- Governance
Op - Which governance verb (
propose/review/withdraw) aGovernancePlanapplies. - Post
Commit Effect - A deferred side effect recorded during prepare and run once, after the
atomic unit commits (ADR-099 D1, “post-commit pass”). v1’s admissible set
computes no embeddings during prepare (D3’s
update/mergecaveat), so the only post-commit effects are reindex kicks computed from the committed row content, plus the GAP-5 addition under B3: the best-effort GTD lifecycle audit row.