Skip to content

Setup-step tree mutations are indistinguishable from a gate that dirtied the repository #84

Description

@nnennandukwe

Summary

Split out of #78, which adds declared per-gate setup steps. #78 runs those steps inside the existing
clean/HEAD observation window and does not change what invalidated means. That leaves a real gap worth
settling on its own, because it is a question about the meaning of invalidated, not about declaring setup.

Current behavior

observeProofRepository (src/adapters/git/client.ts:82) defines cleanliness as:

git status --porcelain=v1 -z --untracked-files=all --ignore-submodules=none

No --ignored. So gitignored paths are invisible, but modified tracked files and untracked non-ignored files
both make the tree dirty.

Both execution paths then force the gate result to invalidated when the tree is dirty at either end
(scripts/run-ci-gate-sensor.ts:91, src/services/session-service.ts:670).

The gap

Provisioning is exactly the kind of step that writes to the tree. Under the current rule:

Setup writes Reported dirty Gate result
Modified tracked file, e.g. uv sync refreshing uv.lock yes invalidated
Untracked non-ignored path, e.g. .venv/ absent from .gitignore yes invalidated
Gitignored path, e.g. .venv/ when ignored no passes

So a consumer whose declared setup is legitimate but whose repository does not gitignore its provisioning
output gets invalidated, whose message points at repository state rather than at the setup declaration that
actually caused it. invalidated currently means "the evidence does not describe HEAD", and a lockfile
refresh during provisioning is a different situation from a gate that mutated the repository under test.

#78 records per-step clean observations, so the receipt does contain enough to tell these apart. Nothing
consumes that distinction yet.

Why this is deliberately not in #78

Relaxing or subdividing invalidated changes the meaning of an existing recorded status that already appears
in stored receipts and in the runner skill's status allowlist. #78 is already a two-contract change; folding a
third semantic change into it would make a partial landing more likely.

Options

  1. Leave invalidated as is and document the constraint: governed setup must use frozen or locked install
    modes and must write only gitignored paths. Free, but every adopter has to discover the rule.
  2. Use the per-step observations A declared gate must be self-provisioning, so typical consumer verify targets cannot be adopted #78 records to classify a setup-caused mutation distinctly from a
    gate-caused one, so the operator handoff names the offending setup step and what it wrote.
  3. Narrow the post-setup cleanliness check to tracked files only, letting setup create untracked output
    without requiring the consumer to gitignore it. Weakens the current invariant.
  4. Snapshot and restore, or otherwise scope, the tree between setup and gate execution. Strongest guarantee,
    most machinery.

Option 2 looks most consistent with the direction #78 takes, since it adds meaning to evidence that will
already be recorded rather than relaxing an invariant.

Acceptance criteria

  • A setup step that dirties the working tree produces an outcome whose message names the setup step and the
    paths it changed, distinct from a gate that mutated the repository.
  • The invariant that a passing receipt describes exactly the observed HEAD is preserved or its intentional
    change is documented.
  • Local and CI execution paths classify the situation identically.
  • Whatever rule applies to provisioning output is documented in docs/consumer-onboarding.md with a worked
    example.
  • Existing stored receipts carrying invalidated keep their current meaning.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions