Quickstart: the ownership-mutation guard
Audience: a Spec Kitty maintainer adding or reviewing a mutating-flow cleanup.
The rule (charter L463–479)
A mutating flow (init, upgrade, migrations) must NOT delete/rename/overwrite a path in a user-visible command/skill/template/governance directory unless it can prove the package owns that exact path. If it cannot prove ownership, it preserves the content and warns — it never deletes-by-name.
Adding a destructive cleanup step — do this
1. Pick the prover matching your ownership signal:
2. Call the guard instead of a raw rmtree/unlink:
- manifest-tracked skills/commands →
ManifestProver - package-managed/regenerable-this-run path →
ManagedPathProver - generated file with a version-marker / byte-matches the shipped canonical →
CanonicalContentProver
```python from specify_cli.asset_preservation import guard_destructive_removal, CanonicalContentProver
verdict = guard_destructive_removal(path, project_path, prover=CanonicalContentProver(...)) if verdict.owned: _safe_unlink(path) # your existing delete + manifest prune changes.append(f"Removed {rel}") else: warnings.append(verdict.diagnostic) # preserved / archived — content survives ``` 3. Add a one-line comment documenting the ownership proof (charter L479). 4. Write a red-first test: seed a user-authored file that collides by name but is not package-owned; assert it survives (in place or backup) with a diagnostic AND that a genuinely package-owned target is still removed. It must be RED on the base commit, GREEN after your fix.
If your op is genuinely safe (ephemeral scratch, worktree teardown, package-only)
Do NOT route it. Add it to the frozen allowlist in tests/architectural/test_mutation_ownership_routing.py with a concrete one-line rationale (what the path is, why no user content can be there). The allowlist is shrink-only.
Don't
- Don't put the guard in
specify_cli/ownership/(that's WP-scope manifests). It lives inspecify_cli/asset_preservation/. - Don't add a
--featureCLI flag; don't put guard code inspecify_cli/__init__.py. - Don't add a raw
rmtree/unlinkof a user-visible dir — the gate will fail your PR. - Don't signal preservation via a non-zero exit code; preservation exits success with a warning.
Verify
PWHEADLESS=1 make test-fast
.venv/bin/python -m pytest tests/specify_cli/asset_preservation tests/architectural/test_mutation_ownership_routing.py -q
.venv/bin/python -m pytest tests/architectural -q # new module + gate ⇒ run the arch suite