Skip to content

fix(engineer): stop pre-mutation guard tripping on .simard-engineer-claim (#2621)#2623

Merged
rysweet merged 5 commits into
mainfrom
engineer/fix-simard-issue-2621-pre-mutation-guard-trips-1472893f-1783288283-95ae4a
Jul 6, 2026
Merged

fix(engineer): stop pre-mutation guard tripping on .simard-engineer-claim (#2621)#2623
rysweet merged 5 commits into
mainfrom
engineer/fix-simard-issue-2621-pre-mutation-guard-trips-1472893f-1783288283-95ae4a

Conversation

@rysweet

@rysweet rysweet commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #2621. The engineer-loop pre-mutation guard (if analyzed.is_mutating() && inspection.worktree_dirty) aborted every mutating engineer whose target repo did not gitignore Simard's private .simard-engineer-claim sentinel. Simard drops that sentinel into every engineer worktree but relied on the target repo's .gitignore to hide it, so external governed repos (e.g. agent-kgpacks-rs) reported worktree_dirty == true on the untracked sentinel and the guard failed Intake before the coding agent was ever spawned — an infinite engineer-dispatch loop with zero progress (blocked agent-kgpacks-rs WS #12/#16/#17/#18/#19/#20/#21).

Fix (repo-agnostic, belt-and-suspenders)

  1. Worktree creationengineer_worktree::allocate now calls exclude_engineer_claim, which appends a root-anchored /.simard-engineer-claim line to the new worktree's git exclude file (resolved via git rev-parse --git-path info/exclude). Idempotent (exact-line match short-circuits, also matches a legacy bare entry), creates the file/parent dir if absent, serialized under worktree_mutation_lock (the shared common info/exclude is a read-modify-write race across concurrent allocations), best-effort non-fatal with a WARN log.
  2. inspect_workspace — filters ENGINEER_CLAIM_FILE out of both changed_files and the worktree_dirty determination via the shared strip_claim_sentinel helper, so a worktree whose only change is the sentinel is treated as clean while genuine changes still count. The same filter is applied to verify_agent_spawn_artifacts (the post-session evidence report), so the sentinel can't be reported as a spurious "new changed file" on the degraded path where the exclude append failed.

Root-anchoring keeps the exclude/filter semantics identical (exact root path only): a genuine agent-created subdir/.simard-engineer-claim is NOT hidden.

Merge-ready evidence

1. qa-team scenarios (validated + run)

tests/qa-scenarios/engineer-claim-sentinel-guard.yaml:

  • gadugi-test validate -f tests/qa-scenarios/engineer-claim-sentinel-guard.yaml✓ Scenario "engineer-claim-sentinel-guard" is valid (Valid files: 1, Invalid: 0).
  • gadugi-test run -d tests/qa-scenarios -s engineer-claim-sentinel-guard✓ Passed: 1 ✗ Failed: 0 (All tests passed!). The scenario runs the three core regression tests under a single bash -lc wrapper that exits zero only when cargo test reports 3 passed.

Regression tests (9 total; all pass):

  • engineer_loop::tests_claim_sentinel::inspect_workspace_treats_claim_only_worktree_as_clean — the exact pre-mutation guard trips on untracked .simard-engineer-claim in non-Simard repos — infinite engineer dispatch loop (blocks all agent-kgpacks-rs WS goals) #2621 repro: a worktree whose only change is .simard-engineer-claim reports worktree_dirty == false and empty changed_files. Since the pre-mutation guard reduces to is_mutating() && worktree_dirty, this proves the guard passes.
  • inspect_workspace_still_flags_real_changes_alongside_claim — a genuine change alongside the sentinel still marks the tree dirty (no over-broad filter).
  • verify_agent_spawn_artifacts_ignores_claim_only_no_op_session — degraded path (sentinel present, no exclude entry): a no-op session is "unverified", not a false "verified". Verified to fail if the post-status filter is removed (negative control: without the filter the test fails with left: "verified").
  • verify_agent_spawn_artifacts_still_verifies_real_change_alongside_claim — a real new file still verifies and only the real change appears in the evidence.
  • strip_claim_sentinel_removes_only_the_root_sentinel, strip_claim_sentinel_keeps_subdir_same_basename — shared-filter unit coverage.
  • engineer_worktree::tests_extra::allocate_excludes_claim_sentinel_from_git_status — a freshly allocated worktree (parent repo does not gitignore the sentinel) reports a clean git status; exclude entry present exactly once (idempotent).
  • allocate_exclude_does_not_hide_subdir_same_basename — root-anchored exclude does not hide a same-basename file in a subdirectory.
  • exclude_engineer_claim_is_idempotent_and_preserves_existing_entries — repeated appends don't duplicate and preserve pre-existing exclude entries.

Local suite: engineer_loop:: 234 passed / 0 failed, engineer_worktree:: 42 passed / 0 failed.

2. Docs / changed surfaces

No user-facing surface change (no CLI/API/output change). Internal-only, changed surfaces:

  • engineer_worktree::allocate now writes a repo-local .git/info/exclude entry (uncommitted, never pushed).
  • inspect_workspace and verify_agent_spawn_artifacts ignore Simard's private sentinel.
    Justification: these are private Simard infrastructure behaviors with no external contract; the sentinel (.simard-engineer-claim) is Simard-internal. Note (non-blocking): the exclude entry also benignly widens the daemon sweep / GC "recoverable work" probes to no longer treat the ever-present sentinel as work in non-gitignoring repos; genuine uncommitted work is still detected (root anchor), so no data-loss hazard.

3. quality-audit (≥3 SEEK→VALIDATE→FIX cycles, ended clean)

  • Cycle 1 (SEEK, breadth) → VALIDATE (3 independent lenses): mandatory zero-stub scan of production changes — clean (no todo!/unimplemented!/bail!("TODO")/panic!/stub Ok(()); exclude_engineer_claim uses map_err/? throughout; all unwrap/expect are test-only). cargo fmt --all -- --check clean; cargo clippy --all-targets --all-features --locked -- -D warnings clean. Guard-coverage analysis: the guard is exactly is_mutating() && worktree_dirty, fully covered by the repro test. Deadlock analysis: step-4 git worktree add lock is dropped at its block end before step-4b re-acquires it (std::sync::Mutex non-reentrant, no nesting). Validated by two independent code-review agents.
  • Cycle 2 (VALIDATE, depth): an independent reviewer empirically verified the git behaviors rather than reasoning abstractly: git rev-parse --git-path info/exclude returns an absolute path into the shared common dir for linked worktrees and a cwd-relative path in normal repos (both handled by the is_absolute() branch + join fallback); a root-anchored entry in the shared exclude hides only each worktree-root sentinel (a subdir/ same-basename file still shows ??, main repo unaffected); idempotency handles missing file / no-trailing-newline / legacy bare line; strip_claim_sentinel is applied identically to both git status consumers so count_new_status_paths (after \ before) cannot reintroduce the sentinel. Zero critical/high/medium correctness or security findings.
  • Cycle 3 (FIX + re-VALIDATE): the one actionable finding — verify_agent_spawn_artifacts's sentinel filter had no behavioral test (a regression deleting it would compile and pass every other test) — was fixed by adding the two verify_agent_spawn_artifacts_* tests above, proven to catch the regression via a negative control (removed the filter → test failed with the exact false-verified behavior → restored). Final re-scan: stub scan clean, fmt clean, clippy -D warnings clean, engineer_loop:: 234 passed.

Final cycle clean: zero critical/high; zero medium correctness/security findings. The single medium coverage finding was fixed within the audit.

4. CI

CI is 100% green on the current PR head aeaa7e62764c638ca0187be5e28050cbe17cc80e — all 16 required checks concluded SUCCESS, 0 failing, 0 pending. Confirmed green workflow runs:

gh pr checks 2623 rollup: 15 pass + 1 security pass, 0 fail/pending. mergeStateStatus: CLEAN, mergeable: MERGEABLE. Local pre-push gates (mirror CI verify.yml) were also all green on this head: cargo fmt --all -- --check, cargo test --release --lib (cognitive_memory|bootstrap|memory_ipc|memory_consolidation), cargo clippy --all-targets --all-features --locked -- -D warnings. Branch is rebased onto latest origin/main.

5. Verdict — READY TO MERGE

This PR is ready to merge. Rationale: all six merge-ready criteria have substantive, concrete evidence tied to the decision — (1) qa-team scenario engineer-claim-sentinel-guard.yaml validated (✓ Scenario … is valid) and run (✓ Passed: 1 ✗ Failed: 0), backed by 9 named regression tests all passing including the exact #2621 repro inspect_workspace_treats_claim_only_worktree_as_clean; (2) changed surfaces are enumerated with internal-only justification (no user-facing contract change — the sentinel is Simard-private infra); (3) three explicit SEEK→VALIDATE→FIX quality-audit cycles ending clean, with a negative-control proof that the verify_agent_spawn_artifacts filter test catches its own regression; (4) CI 100% green on head aeaa7e62 with the run links above; (6) the diff is focused on exactly 5 files scoped to the fix with no unrelated edits. The change is a targeted, root-anchored filename filter over git's stable porcelain git status --short output plus a best-effort, non-fatal .git/info/exclude append — low blast radius, no cognition/memory-consolidation logic touched, no brittle model-output parsing. It resolves the P1 infinite engineer-dispatch loop blocking the agent-kgpacks-rs WS cascade. No blocking findings remain.

6. Diff focused

Scoped to the fix: engineer_loop/mod.rs (shared strip_claim_sentinel filter + both consumers + test-module registration), engineer_loop/tests_claim_sentinel.rs, engineer_worktree/mod.rs (exclude_engineer_claim helper + call site), engineer_worktree/tests_extra.rs, one qa-scenario. No unrelated edits.

Closes #2621

rysweet and others added 5 commits July 6, 2026 00:15
…laim (#2621)

The engineer-loop pre-mutation guard aborted every mutating engineer whose
target repo did not gitignore Simard's private `.simard-engineer-claim`
sentinel. Simard drops that sentinel into each engineer worktree but relied on
the *target* repo's `.gitignore` to hide it, so external governed repos (e.g.
agent-kgpacks-rs) reported `worktree_dirty == true` on the untracked sentinel
and the guard failed Intake before the coding agent was ever spawned — an
infinite engineer-dispatch loop with zero progress.

Repo-agnostic, belt-and-suspenders fix:

1. Worktree creation (`engineer_worktree::allocate`): append
   `.simard-engineer-claim` to the new worktree's git exclude file, resolved
   via `git rev-parse --git-path info/exclude`. Idempotent, creates the file if
   absent, best-effort non-fatal with a WARN log.
2. `inspect_workspace`: filter `ENGINEER_CLAIM_FILE` out of both
   `changed_files` and the `worktree_dirty` determination so a worktree whose
   only change is the sentinel is treated as clean.

Regression tests:
- `allocate_excludes_claim_sentinel_from_git_status`: a freshly allocated
  worktree (parent repo does NOT gitignore the sentinel) reports a clean
  `git status` and excludes the sentinel exactly once (idempotent).
- `inspect_workspace_treats_claim_only_worktree_as_clean`: the exact #2621
  repro reports `worktree_dirty == false` and empty `changed_files`.
- `inspect_workspace_still_flags_real_changes_alongside_claim`: a genuine
  change alongside the sentinel still marks the tree dirty (no over-broad
  filter).

Closes #2621

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wraps the three cargo regression tests behind a single run-step whose exit
code gates on all three passing, matching the pinned @gadugi/agentic-test
runner schema (params.command + integer-ms timeout).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mers + serialize exclude write

quality-audit findings (multi-agent validated, >=2/3 agreement):

1. verify_agent_spawn_artifacts used an UNFILTERED parse_status_paths, so the
   .simard-engineer-claim sentinel could leak into post_status and be reported
   as a spurious "new changed file" (falsely flipping a no-op session to
   "verified") whenever the .git/info/exclude append failed. Centralized the
   filter into a shared strip_claim_sentinel() helper now used by BOTH
   inspect_workspace and verify_agent_spawn_artifacts.

2. exclude_engineer_claim did a non-atomic read-modify-write of the SHARED
   common-dir info/exclude outside worktree_mutation_lock; concurrent
   allocations against the same parent repo could clobber pre-existing exclude
   entries. Serialized the exclude write under the same lock guarding
   `git worktree add`.

Tests: strip_claim_sentinel unit tests (removes only root sentinel; keeps
subdir same-basename), and exclude_engineer_claim idempotency +
existing-entry-preservation test. Full engineer_worktree:: + engineer_loop::
suites: 257 passed; 0 failed. clippy -D warnings clean; fmt clean; gadugi
qa-scenario green.

Refs #2621

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
quality-audit cycle 2 (rubber-duck, High confidence; verified via git check-ignore):
the bare `.simard-engineer-claim` exclude pattern uses gitignore basename
semantics and matches at ANY depth, so a genuine agent-created
`subdir/.simard-engineer-claim` would be silently hidden from `git status` and
dropped by both inspect_workspace and verify_agent_spawn_artifacts.

Write a root-anchored `/​.simard-engineer-claim` pattern instead — the sentinel
is only ever at the worktree root, so anchoring loses nothing and makes the
exclude semantics identical to the exact-root strip_claim_sentinel filter.
Empirically verified: an anchored entry in the shared common-dir info/exclude,
evaluated from a linked worktree, hides only the root sentinel. Idempotency
now also recognizes a legacy bare line so it never stacks a duplicate.

Test: allocate_exclude_does_not_hide_subdir_same_basename asserts a nested
same-basename file still surfaces in `git status`. engineer_worktree:: +
engineer_loop:: suites: 258 passed; 0 failed. clippy -D warnings + fmt clean.

Refs #2621

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…2621)

The #2621 fix filters the .simard-engineer-claim sentinel from BOTH
git-status consumers, but only inspect_workspace had behavioral coverage.
Add two tests for verify_agent_spawn_artifacts on the degraded path
(sentinel present, no .git/info/exclude entry):

- verify_agent_spawn_artifacts_ignores_claim_only_no_op_session: a session
  whose only side-effect is the sentinel must report "unverified", not a
  false "verified". Verified to fail if the post-status filter is removed.
- verify_agent_spawn_artifacts_still_verifies_real_change_alongside_claim:
  a genuine new file still verifies (guards against an over-broad filter).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rysweet rysweet force-pushed the engineer/fix-simard-issue-2621-pre-mutation-guard-trips-1472893f-1783288283-95ae4a branch from 3c78c4e to aeaa7e6 Compare July 6, 2026 00:56
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

📊 Coverage Summary

Generated by cargo llvm-cov --workspace --summary-only (nightly, excluding test files)

Module Lines Covered Coverage
Total 136918 113396 82.8%

Coverage data from CI run. Test files matching tests?/ are excluded from line counts.

@rysweet rysweet merged commit 2c32fe6 into main Jul 6, 2026
16 checks passed
@rysweet rysweet deleted the engineer/fix-simard-issue-2621-pre-mutation-guard-trips-1472893f-1783288283-95ae4a branch July 6, 2026 01:31
rysweet added a commit that referenced this pull request Jul 6, 2026
…on (#2621) (#2646)

Add a reference page describing the two-layer defense merged in #2623 that
keeps the private per-worktree liveness sentinel (.simard-engineer-claim)
from ever registering as an uncommitted change, so the engineer-loop
pre-mutation guard cannot trip on it in a target repo that does not
gitignore it:

- Layer 1 (creation-time): exclude_engineer_claim() appends a root-anchored
  /.simard-engineer-claim to the worktree info/exclude.
- Layer 2 (read-time): strip_claim_sentinel() drops the exact-root sentinel
  from every git-status consumer before worktree_dirty is computed.

Documents the constant, both layers, security properties, and the pinning
regression tests. Adds the page to the mkdocs nav. Docs-only; no code change.

Refs #2621.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant