ADR 0033: Conflict visibility and causal run history
- Status: Proposed
- Date: 2026-07-07
- Decider(s): @davesade (David Kubec)
- Relates to: ADR 0007, ADR 0024, ADR 0028, ADR 0029
Context
Kilolock's parallel apply story is only credible if the system makes causality visible, not just concurrency possible.
Resource-level reservations solve only one part of the operator problem:
- they allow independent writes to proceed concurrently
- they prevent overlapping writes from committing silently
They do not, by themselves, answer the questions engineers actually ask after a surprising run:
- who planned what
- which slice of state that run read and intended to write
- what changed while that run was planning or waiting
- why the system allowed two runs to proceed concurrently
- whether the final commit still matched the reviewed plan
- whether a re-plan, validation re-check, or staleness decision occurred
Without that visibility, Kilolock risks a bad product outcome:
- the global lock is gone
- concurrency exists on paper
- but engineers still do not trust the final state
That is not good enough for large shared-state workflows. The product promise is not only "more things can run at once." It is also "you can understand why the result was safe, or why it was blocked."
This concern becomes stronger as Kilolock evolves toward:
- file-scoped and targeted plan/apply
- state-engine slice fetch
- reservation-based concurrency
- delta-style native commit
Those features narrow the runtime surface area, but they also increase the need for operator legibility. A narrower execution model is harder to debug if the system does not preserve and expose the decision trail.
Decision
Kilolock will treat conflict visibility and causal run history as a first-class part of the parallel apply architecture.
Every plan/apply workflow must preserve enough structured metadata to answer:
- what the operator intended
- what the system reserved and allowed
- what changed between planning and commit
- whether the runtime still matched the original assumptions
This is a product requirement, not a nice-to-have debugging extra.
Decision details
D1. Run history must preserve the planning envelope
Each run should record, or be able to reconstruct from attached artifacts:
- actor / holder identity
- start time and finish time
- state identity
- source serial the plan was computed against
- plan scope selectors such as file scope or target scope
- planned write set
- planned read set
- planned reservation set
- state-engine slice metadata when applicable
This is the minimum causal envelope for explaining what one run believed it was doing.
D2. "Why was this run allowed?" must be answerable from stored facts
Kilolock should preserve enough information to explain why two runs were considered safe to coexist.
That explanation should be grounded in structured facts such as:
- disjoint write sets
- compatible reservation modes
- state-engine closure that proved the fetched slice sufficient
- staleness checks that passed at apply time
The answer must not rely on ephemeral logs alone.
D3. The system must surface "what changed while I was planning?"
When the trunk advances after a plan is produced, Kilolock should make the intervening change visible in operator-facing history.
That view should distinguish at least:
- changes outside the run's read/write footprint
- changes inside the read set but outside the write set
- changes inside the write set
This distinction matters because the operator question is not only "did anything change?" but "did anything change that should have invalidated my assumptions?"
D4. Re-plan and revalidation events must be explicit
If Kilolock performs any re-check between original plan generation and final commit, the run history should say so clearly.
Examples include:
- staleness guard passed after trunk advanced
- staleness guard failed
- state-engine validation re-plan changed the trusted native intent
- runtime widened or fell back from a trusted narrow lane to a broader one
These are not implementation footnotes. They change the meaning of the run and should be visible as first-class markers.
D5. Commit provenance must connect plan intent to final state version
For committed runs, the system should preserve provenance linking:
- apply run id
- source version / serial
- destination version / committed serial
- plan spec identity or embedded plan metadata
- commit mode such as snapshot-merge vs state-engine delta
This lets operators inspect not only that a new version exists, but which run produced it and under which assumptions.
D6. Operator views must be optimized for conflict investigation
The required information should be available through operator-facing commands and APIs, not only internal tables.
The intended experience is that an engineer can answer questions like:
- what is running now
- what did the previous run touch
- what changed since my plan
- why did my run wait or fail
- did this run commit exactly what it planned
This ADR does not freeze one exact CLI shape, but it establishes that the product surface must exist.
D7. Conflict visibility is shared across Terraform-lane and native-lane work
This requirement applies whether the runtime path was:
- plain
kl applyusing plan-spec plus reservation orchestration - trusted state-engine lane with narrower slice and delta commit
- fallback from trusted lane to broader snapshot behavior
The explanation model may be richer for native state-engine flows, but the user expectation is the same: concurrent behavior must be legible.
D8. The system should distinguish independence from invalidation
Kilolock should avoid collapsing all concurrent activity into one red/yellow warning.
There is a meaningful difference between:
- another run happened, but it was independent of mine
- another run changed something I read
- another run changed something I intended to write
The product should present those cases differently so operators can build trust in safe concurrency instead of learning to fear all concurrent activity.
Consequences
Positive
- makes parallel apply easier to trust operationally
- turns reservations into an explainable safety mechanism instead of a hidden
implementation detail
- improves debugging for stale-plan, fallback, and revalidation cases
- creates a stronger foundation for future UI, API, and audit surfaces
Negative
- increases metadata and history-shape complexity
- may require additional joins, retained artifacts, or derived summaries for
good operator ergonomics
- raises the bar for what counts as a "complete" apply/history implementation
Neutral
- this ADR does not require one exact schema in one step
- some of the needed fields already exist in
PlanSpec, reservations, applyruns, and version metadata, but they are not yet unified into one operator
story
Implementation direction
Near-term work should aim to unify existing pieces before inventing entirely new subsystems.
Likely building blocks:
- attach plan-derived metadata to apply-run history
- expose recent apply-run history alongside state version history
- preserve reservation and commit provenance in a stable shape
- mark stale-plan / revalidation / fallback outcomes explicitly
- add operator-facing views for:
- recent runs
- planned resource slice
- reserved resource slice
- changed-since-plan
- re-plan markers
Current implementation snapshot
As of 2026-07-10, the first operator-facing slice of this ADR is implemented in the vanilla Terraform lane with KL as the wrapper/orchestrator.
Shipped pieces:
kl applyperforms changed-since-plan preflight automatically for plan-specapplies
--strict-since-plancan fail applies when newer versions touched plannedwrite targets
- preflight explains
allowed becausein addition to warning about drift sinceplan time
- apply-run metadata persists:
- planned read/write scope
- reservation summary
- since-plan summary
- why the run was allowed
- replan marker summary
kl historyshows committed-version provenance with:- producing run
- why line
- replan line
kl history --since-plan=...shows:- what changed while the plan was waiting
- whether the change was outside scope / plan input changed / planned target changed
- matching producing-run provenance when available
examples/big-state/conflict-visibility-demo.shdemonstrates the intended UX
Still intentionally incomplete:
- dedicated run-centric history view
- richer native state-engine-only causality markers
- broader proactive surfacing of parallel user activity outside explicit
history/preflight views
- commit provenance
- "why was this run allowed?"
The key requirement is not that every field live in one table. The key requirement is that the operator can inspect the causal story without stitching it together manually from logs and raw SQL.