Expand description
ADR-099: the per-verb async prepare pass for the KG-substrate v1
admissible verbs (update, delete, link, merge). Each prepare_*
function reads current state (async, outside any transaction) and
returns a plain-data crate::atomic_runner::AtomicOpPlan
(crate::atomic_plan) for the synchronous commit pass
(crate::atomic_runner::run_atomic_unit) to apply.
gtd.transition / gtd.complete prepare is deliberately not here:
their lifecycle vocabulary (is_terminal, can_transition, …) lives in
khive-pack-gtd, which depends on khive-runtime: not the other way
around. Reproducing that dependency here would invert the crate graph, so
their prepare functions live in kkernel (which already depends on both
crates), calling back into the plain PlanStatement/AffectedRowGuard
shapes exported from this module’s sibling, crate::atomic_plan.
propose / review / withdraw (the event-sourced governance lifecycle)
are on the v1 admissible list ([khive_types::pack:: ATOMIC_ADMISSIBLE_VERBS]) but have no prepare implementation here: their
apply path is a changeset-interpreter (apply_worker) over a dedicated
proposals_open table, not a small number of guarded DML statements — a
faithful, non-stub atomic prepare for them is separate follow-on work.
prepare_governance_unimplemented fails loudly, before any write, naming
this as a known scope gap rather than silently no-opping.
merge is likewise on the v1 admissible list but is deferred: full-parity
field folding, survivor index reindex, loser index purge, provenance, and
same-kind rejection are achievable as static DML, but
curation::merge_entity_sql’s graceful edge-conflict resolution is not
(it is per-row procedural, incompatible with the static predicate/guard
plan shape): rather than ship a partially-scoped atomic merge, it is
rejected at the same pre-runtime static guard as governance
(khive_types::pack::ATOMIC_KNOWN_UNIMPLEMENTED_VERBS). prepare_merge
below is therefore unreachable through --atomic; it remains only as the
pre-fix-round direct-prepare implementation, exercised by this module’s
own tests, and as defense in depth.
Enums§
- Atomic
Delete Kind - Caller-supplied delete-kind expectation, resolved via the canonical
resolve_kind_specat the kkernel--atomicseam.khive-runtimemust not depend onkhive-pack-kg(packs depend on the runtime, not the other way around), so this is a plain substrate-level shape rather thankhive_pack_kg::handlers::KindSpecitself: the kkernel seam does the pack-awareresolve_kind_specresolution (which needs aVerbRegistry, unreachable from this crate) and passes down only whatprepare_deleteneeds to enforce the mismatch check. - Atomic
Update Kind - Caller-supplied update-kind expectation, resolved via the canonical
resolve_kind_specat the kkernel--atomicseam: the same patternAtomicDeleteKinduses. Without this check,update(kind="document", id=<concept>)would be canonicallyNotFoundbut the atomic path would ignore the explicit kind and mutate the resolved entity anyway.khive-runtimemust not depend onkhive-pack-kg, so this is a plain substrate-level shape rather thankhive_pack_kg::handlers::KindSpecitself: the kkernel seam does the pack-aware resolution and passes down only whatprepare_updateneeds to enforce the mismatch check.
Functions§
- apply_
post_ commit_ effects - Run every deferred
PostCommitEffectafter a committed atomic unit, outside any transaction. Re-fetches each target’s now-committed row and reuses the existingreindex_entity/reindex_note(FTS + embedding, same as the non-atomic path) for exact parity. - prepare_
delete expected_kind:Nonewhen the caller omittedkind(no check, parity with canonical’s own optional discriminator);Some(_)enforces an exact-parity mismatch check against the resolved record’s actual substrate/specific kind, mirroringhandle_delete’sentity.kind != *expected/note.kind != *expectedchecks.- prepare_
op - Build the prepared
AtomicOpPlanfor one KG-substrate admissible op (update,delete,link,merge). Returns a loudRuntimeErrorforpropose/review/withdraw(known scope gap, see module doc) and any other verb (the CLI boundary must reject those before calling this — a verb reaching here is either KG-substrate-admissible or a bug upstream). - prepare_
update expected_kind:Nonewhen the caller omittedkind(no check, parity with canonical’s own optional discriminator);Some(_)enforces an exact-parity mismatch check against the resolved record’s actual substrate/specific kind, mirroringhandle_update’sentity.kind != *k/ note kind checks (update.rs:200-201, :229-234).