Research: Governed ops never destroy or mis-report user content
Squad provenance
Research & grounding squad (2026-10-11, base main 3498b426, after PR #6038 merged): three parallel grounding agents reproducing each issue red-first at HEAD and mapping loci, plus orchestrator codegraph mapping of the #6020 residue/ownership predicate family. Operator sign-off on the #5964 approach and the 3-WP structure captured via in-session decision (2026-10-11): scope the upgrade untrack to manifest-owned paths (do NOT extend is_self_bookkeeping_churn); one Mission, three independent WPs.
Base carries both #6020 (mission destructive-residue-context-01M4KBPS, the ResidueContext/_is_disposable_residue guard API) and #6038 (#5968/#5967/#5969/#5863 consolidation/teardown integrity). This mission is the point-fix layer of the #3129 ambient-write-boundary class; the structural ADR is already merged — no shadow-workspace rewrite.
Decisions
D1 — #5964 is fixed at the upgrade-migration ownership seam, NOT the residue predicate
- Decision: scope
m_3_2_5_agents_skills_gitignore_backfill._untrack_tracked_pathsto untrack only spec-kitty-projected.agents/skills/paths, reusing the existing manifest-ownership authority (skills/command_installer.pystablespec-kitty.*command pattern +_is_canonical_rel_path;skills/manifest.pyManagedFileEntry.installed_path;command-skills-manifest.json). Do not route #5964 throughcoordination.coherence.is_self_bookkeeping_churn. - Rationale:
is_self_bookkeeping_churnis a path-pattern predicate for spec-kitty's disposable bookkeeping and feeds the consolidate/accept/review destructive-op guards; it has no wire to the upgrade migration. Extending it to match.agents/skills/**would makeconsolidatetreat team skills as disposable (reset --hard/worktree remove --force) — re-creating the data-loss in a strictly wider blast radius (the #4933 "invisible to one gate, fatal at another" class the predicate's own docstring warns about)..agents/skills/ownership is a manifest question, not a path pattern (team vs pack skills share the prefix; pack namespaces are config-variable). The charter "User Customization Preservation / Proof Trail" already statescommand_installer.pycodifies "third-party paths under.agents/skills/are never touched unless manifest-owned." - Alternatives: extend
is_self_bookkeeping_churn(rejected — widens the bug, operator-confirmed); a new shared manifest-ownership predicate (deferred — a second authority unless a second caller appears; reuse the existing one, C-001).
D2 — #5434 is independent of the residue predicate: fix the exclude write location
- Decision: call
core/git_ops.py::exclude_from_git_index(repo_root, [".spec-kitty/"])at the single code-lane choke point inallocate_lane_worktree(worktree_allocator.py:~1087, before both_validate_worktree_cleancalls, covering reuse/crash-recovery/fresh). Add a migrationm_4_0_0rcX_exclude_spec_kittymodeled onm_0_13_1_exclude_worktrees. Delete the dead per-worktree writercore/worktree.py::_ensure_spec_kitty_exclude+ its two call sites and drop its dead-symbol-allowlist rows. - Rationale:
_validate_worktree_cleanfails on anygit statusentry and does not consultis_self_bookkeeping_churn;.spec-kitty/is not in that predicate's exempt set. The only coupling to the residue family is thematic. The per-worktreeinfo/excludegit never reads; only the common-dir exclude (shared across all worktrees) is honoured. - Alternatives: classify the lock as self-bookkeeping churn in the predicate (rejected — wrong seam, wouldn't fix the untracked-status the allocator trips on, and widens a predicate used elsewhere); write to tracked
.gitignore(rejected — spec-kitty must not write a consumer's tracked.gitignore; FR-016 chose localinfo/excludefor exactly this).
D3 — #4796 is in the #6020 family: hoist a pre-mutation preflight to parity
- Decision: in
orchestrator_api/consolidation.py::_execute_lane_merge, after retention resolution (:566-571) and before the firstconsolidate_lane_into_mission(:588), run the sharedconsolidation/entry_preflight.py::_pre_mutation_safety_preflight(or extract its lane-worktree leg), gated on the same resolvedremove_worktree/teardown_coordination. The raisedDestructiveOpRefusedis still caught at:859and envelopedPREFLIGHT_FAILED, now truthfully pre-mutation. - Rationale: the CLI path already preflights before any mutation (
executor.py:873); the orchestrator path received only the refusal-envelope half of the fix. #6038's snapshot/rollback lives inconsolidation/executor.py+rollback.pyand the orchestrator code-lane path never builds that state — so today it has neither a pre-mutation preflight nor a rollback door. Reuse the existing preflight (C-001), do not write a new check. - Alternatives: wrap the whole orchestrator path in the executor snapshot/rollback machinery (rejected — far larger than the point-fix; the preflight parity is the minimal correct fix).
Red-first reproduction evidence (at 3498b426)
- #5964 — PRESENT: calling
AgentsSkillsGitignoreBackfillMigration().apply()on a repo with a tracked, non-manifest.agents/skills/team-review/SKILL.mdstagesD .agents/skills/team-review/SKILL.md;migration_index_untracksreturns that path andsafe_commit(index_deletions=...)lands it in the upgrade commit. Matches the issue's rc6 / origin-main evidence. - #5434 — PRESENT: a populated per-worktree
.git/worktrees/<n>/info/excludeleavesgit -C <wt> status --porcelainstill listing?? .spec-kitty/; the same pattern in the common-dir.git/info/excludemakes it disappear. (Dogfood caveat: this repo's own.gitignore:213has.spec-kitty/, masking it — tests must build a consumer-like repo.) - #4796 — PRESENT (by airtight sequential code reading):
_execute_lane_mergemutations atconsolidation.py:588-607(lane consolidation, target integration, done-stamping, push) precede the first lane-worktree dirty check at:611→_apply_lane_merge_cleanup→guarded_worktree_remove→destructive_guard.py:280; the refusal is envelopedPREFLIGHT_FAILEDat:168/:195. No existing test pins the timing (the refusal test mocks_execute_lane_mergeto raise).
Sources
- GitHub issues #5964, #5434, #4796 (bodies read in full via REST).
- Grounding-agent reproductions + file:line maps (this mission's three grounding agents, 2026-10-11).
- Codegraph (index at repo root,
3498b426):coordination/coherence.pyresidue family,consolidation/entry_preflight.py,orchestrator_api/consolidation.py. - Sibling mission #6020
destructive-residue-context-01M4KBPS(merged) — theResidueContext/_is_disposable_residueauthority this mission does NOT extend for #5964. - Charter: "User Customization Preservation / Proof Trail"; Standing Orders #1 (adversarial squad), #4 (red-first), #6 (canonical sources).