Data Model: User-content preservation for mutating flows
This mission is behavioural (guarding mutating flows), not schema-additive. The "entities" are the decision types the flows reason over. No new persisted schema is introduced except the overwrite backup sidecar naming convention.
Entities / value objects
MutatingFlow
A Spec Kitty command that writes, deletes, or rewrites files or git state.
manifest), implement (hook install), intake (+--auto), validate-encoding --fix, agent tasks finalize-tasks, safe_commit/upgrade.
user-authored content.
- Members in scope:
agent config remove,agent config sync(orphan sweep + - Invariant (FR-014): never reports success while destroying, losing, or corrupting
UserAuthoredAsset
A file/hook/brief the operator created that Spec Kitty did not deploy and cannot prove it owns.
.git/hooks/pre-commit, a hand-written .kittify/mission-brief.md, committed spec *.md, a repository-pinned command-skills-manifest.json, staged git hunks.
- Examples:
.claude/commands/*.md, other agents' managed subdirs, a foreign
OwnershipProof (reused, tool_surface.operations.OwnershipProof)
Content-based evidence that authorizes removal/replacement. Kinds:
- manifest: manifest entry +
content_hash == sha256(current bytes)+ copy delivery. - signature: hook body bears
# Generated by spec-kitty.. - canonical: bytes match a known canonical/version-marker.
- Never name- or directory-based (C-003).
PreservationVerdict (reused, asset_preservation.OwnershipVerdict)
The guard's decision for one path: owned (removed) | preserved-in-place | archived-then-removed. Carries diagnostic (the operator-facing warning) and (when archived) backup_path.
verdict.owned; a preserved verdict never renders as "Removed".
- New usage rule (FR-015): the caller's success/΅"Removed" messaging is driven by
OverwriteBackup (new)
A byte-exact sidecar copy of a foreign file taken before an overwrite.
existing backup), symlink-aware (captures os.readlink, does not deref).
- Naming:
<path>.<timestamp>in-place sidecar (e.g..git/hooks/pre-commit.2026-09-22T17-58-26Z). - Producer: new
asset_preservation.backup.backup_before_overwrite(path) -> Path. - Properties: byte-identical, mode+mtime preserved,
O_EXCL(never clobbers an - Surfaced: the producing flow prints the returned path.
EffectiveDependencyGraph
The WP dependency graph actually persisted to frontmatter after preservation — the graph agent tasks finalize-tasks must validate (not the tasks.md-parsed graph).
self-refs, unknown-WP refs). On failure: exit non-zero, seed no status.
- Validation:
detect_dependency_cycles+validate_dependencies(cycles,
SafeCommitOutcome
The typed result of safe_commit.
every caller surfaces both fields; upgrade must not flatten it to a generic skip warning with exit 0.
- Success: commit created via a temporary index; operator index/worktree untouched.
- Failure (residual path):
SafeCommitRecoveryFailed(orphan_stash_ref, commit_sha)—
State transition — removal decision (per candidate path)
exists? ── no ──► skip
│ yes
▼
prover.prove(path) ── proof ──► REMOVE (guard performs it) ──► prune manifest ──► render "Removed …" (verdict.owned=True)
│ None (unprovable)
▼
backup_parent given? ── no ──► PRESERVE in place ──► render "Preserved …; not package-owned" (verdict.owned=False, exit 0)
│ yes
▼
ARCHIVE verbatim ──► remove original ──► render "Preserved …; archived to <backup>"
State transition — overwrite decision (hook / intake)
target pre-exists & foreign?
│ no ──► write as today
▼ yes
flow is workflow-internal (implement hook)? ──► backup_before_overwrite() ──► replace ──► surface backup path (exit 0)
flow is user-driven (intake)? ──────────────► refuse, require --force (exit non-zero), brief byte-identical