ADR 0033: Conflict visibility and causal run history

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:

  1. what the operator intended
  2. what the system reserved and allowed
  3. what changed between planning and commit
  4. 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 apply using 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, apply

    runs, 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 apply performs changed-since-plan preflight automatically for plan-spec

    applies

  • --strict-since-plan can fail applies when newer versions touched planned

    write targets

  • preflight explains allowed because in addition to warning about drift since

    plan time

  • apply-run metadata persists:
    • planned read/write scope
    • reservation summary
    • since-plan summary
    • why the run was allowed
    • replan marker summary
  • kl history shows 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.sh demonstrates 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.