174 lines
8.1 KiB
Markdown
174 lines
8.1 KiB
Markdown
|
|
# Spec: lifecycle-deletion
|
||
|
|
|
||
|
|
## ADDED Requirements
|
||
|
|
|
||
|
|
### Requirement: Delete Is True Git Deletion in a Dedicated Hygiene Commit
|
||
|
|
|
||
|
|
A lifecycle `delete` op SHALL perform a true `git rm` (file or, for a
|
||
|
|
directory-rule aggregate entry, recursive directory removal) staged into the
|
||
|
|
same single hygiene commit the run produces. There SHALL be no archive
|
||
|
|
directory, graveyard branch, or other relocation of the deleted content —
|
||
|
|
git history SHALL be the sole archive.
|
||
|
|
|
||
|
|
#### Scenario: A deleted file is git-rm'd, not moved
|
||
|
|
|
||
|
|
- **WHEN** a `delete` op is applied to a tracked file
|
||
|
|
- **THEN** the file is removed via `git rm` and staged into the run's single hygiene commit, with no copy relocated anywhere in the working tree
|
||
|
|
|
||
|
|
#### Scenario: A deleted directory-rule entry is removed recursively
|
||
|
|
|
||
|
|
- **WHEN** a `delete` op targets a directory-rule aggregate entry
|
||
|
|
- **THEN** the entire directory is removed via a recursive `git rm` staged into the same commit
|
||
|
|
|
||
|
|
### Requirement: Deletion Autonomy Tier Matrix
|
||
|
|
|
||
|
|
Deletion autonomy SHALL be determined by evidence quality and recoverability,
|
||
|
|
not file type, per the following matrix:
|
||
|
|
|
||
|
|
| Case | Behavior |
|
||
|
|
|---|---|
|
||
|
|
| IGNORE surface | never walked, never a delete candidate |
|
||
|
|
| lifetime `keep` | scanned + reported, never deleted |
|
||
|
|
| tracked + delete rule + clean worktree | auto |
|
||
|
|
| tracked + delete rule + dirty worktree | confirm |
|
||
|
|
| untracked + delete rule | confirm |
|
||
|
|
| no rule match | unmanaged, never deleted |
|
||
|
|
|
||
|
|
Tracked/clean status SHALL be verified at runtime via `git ls-files` plus a
|
||
|
|
dirty check, and SHALL NEVER be trusted from the rule's own claim or from a
|
||
|
|
cached report field.
|
||
|
|
|
||
|
|
#### Scenario: Tracked and clean deletes automatically
|
||
|
|
|
||
|
|
- **WHEN** a file matches a delete rule, is tracked, and the worktree for that path is clean
|
||
|
|
- **THEN** the deletion proceeds without a confirmation prompt
|
||
|
|
|
||
|
|
#### Scenario: Tracked but dirty requires confirmation
|
||
|
|
|
||
|
|
- **WHEN** a file matches a delete rule, is tracked, but has uncommitted changes
|
||
|
|
- **THEN** the deletion is escalated to confirm — an uncommitted diff would otherwise be lost with the file
|
||
|
|
|
||
|
|
#### Scenario: Untracked requires confirmation
|
||
|
|
|
||
|
|
- **WHEN** a file matches a delete rule but is untracked
|
||
|
|
- **THEN** the deletion is escalated to confirm — there is no git history to recover it from
|
||
|
|
|
||
|
|
#### Scenario: Runtime verification never trusts the rule
|
||
|
|
|
||
|
|
- **WHEN** a delete op is about to be applied
|
||
|
|
- **THEN** the applier re-checks tracked/clean status via `git ls-files` and a dirty check at that moment, regardless of what the report or rule previously claimed
|
||
|
|
|
||
|
|
#### Scenario: No rule match is never deleted
|
||
|
|
|
||
|
|
- **WHEN** a file has no lifecycle rule match
|
||
|
|
- **THEN** it is never a candidate for deletion on lifecycle grounds
|
||
|
|
|
||
|
|
### Requirement: Temporary Tier Retention Semantics
|
||
|
|
|
||
|
|
The `temporary` lifetime SHALL use retain-recent-N plus age, not age alone.
|
||
|
|
`retain_recent` (default 3) SHALL always keep the N most recent matching
|
||
|
|
entries for a rule regardless of age. An entry ranked `retain_recent + 1` or
|
||
|
|
older SHALL become eligible for deletion once it exceeds `max_age_days`
|
||
|
|
(default 3). The retention unit SHALL be the rule's own match granularity: a
|
||
|
|
file-rule's unit is the individual file; a directory-rule's unit is the
|
||
|
|
matched directory as a whole (e.g., a run directory), never files nested
|
||
|
|
inside one such matched directory.
|
||
|
|
|
||
|
|
#### Scenario: The 3 newest entries are always kept regardless of age
|
||
|
|
|
||
|
|
- **WHEN** a temporary rule has `retain_recent: 3` and five matching entries, the three newest exceeding `max_age_days`
|
||
|
|
- **THEN** the three newest are kept and only the two oldest (ranked 4th and 5th) are eligible for deletion
|
||
|
|
|
||
|
|
#### Scenario: An entry younger than max_age_days is kept even if not in the top N
|
||
|
|
|
||
|
|
- **WHEN** a temporary rule's 4th-ranked entry is younger than `max_age_days`
|
||
|
|
- **THEN** it is not deleted this run
|
||
|
|
|
||
|
|
#### Scenario: Directory-rule retention unit is the whole directory
|
||
|
|
|
||
|
|
- **WHEN** a directory rule matches `autoresearch/<run-id>/` entries
|
||
|
|
- **THEN** retain-recent-N and age are computed per matched run directory as a whole, not per file within the newest run
|
||
|
|
|
||
|
|
### Requirement: Temporary Tier Age Source
|
||
|
|
|
||
|
|
Age for the temporary tier SHALL be computed from the git commit time of the
|
||
|
|
path's most recent commit, falling back to filesystem mtime only when the
|
||
|
|
path is untracked. There SHALL be no per-rule `age_source` override field.
|
||
|
|
|
||
|
|
#### Scenario: Tracked file age comes from git commit time
|
||
|
|
|
||
|
|
- **WHEN** age is computed for a tracked file matching a temporary rule
|
||
|
|
- **THEN** the age is derived from that file's most recent commit time, not its filesystem mtime
|
||
|
|
|
||
|
|
#### Scenario: Untracked file age falls back to mtime
|
||
|
|
|
||
|
|
- **WHEN** age is computed for an untracked file matching a temporary rule
|
||
|
|
- **THEN** the age is derived from the file's filesystem mtime
|
||
|
|
|
||
|
|
#### Scenario: No per-rule age_source field exists
|
||
|
|
|
||
|
|
- **WHEN** a rule in the rulebook is inspected
|
||
|
|
- **THEN** it has no `age_source` field — the git-commit-time-with-mtime-fallback behavior is fixed, not configurable per rule
|
||
|
|
|
||
|
|
### Requirement: Untracked Directory Entry Age Uses Directory Inode mtime
|
||
|
|
|
||
|
|
For an untracked directory matched by a directory rule, age SHALL be
|
||
|
|
computed from a single `stat()` of the directory inode itself, not a
|
||
|
|
recursive walk computing the maximum mtime of its contents.
|
||
|
|
|
||
|
|
#### Scenario: Directory age is one stat call, not a recursive scan
|
||
|
|
|
||
|
|
- **WHEN** age is computed for an untracked directory matching a directory rule
|
||
|
|
- **THEN** the computation reads only the directory inode's own mtime and does not recurse into or stat any file inside it
|
||
|
|
|
||
|
|
### Requirement: delete-once-served Split by Evidence Quality
|
||
|
|
|
||
|
|
The `delete-once-served` lifetime SHALL support two mutually exclusive served
|
||
|
|
signals per rule: `served_when_path`, a deterministic path pattern the
|
||
|
|
scanner itself can prove satisfied (e.g. a sibling archive directory
|
||
|
|
existing), and `served_when`, free text describing a condition the
|
||
|
|
classifier must judge. A rule with `served_when_path` satisfied by the
|
||
|
|
scanner MAY be deleted under the autonomy tier matrix (i.e., auto when
|
||
|
|
tracked+clean). A rule relying on `served_when` SHALL ALWAYS be forced to
|
||
|
|
confirm, regardless of tracked/clean status, because it depends on a model
|
||
|
|
judgment rather than a provable filesystem fact.
|
||
|
|
|
||
|
|
#### Scenario: Scanner-proven served_when_path may auto-delete
|
||
|
|
|
||
|
|
- **WHEN** a rule's `served_when_path` condition is satisfied by the filesystem and the matched path is tracked and clean
|
||
|
|
- **THEN** the deletion may proceed automatically under the tier matrix
|
||
|
|
|
||
|
|
#### Scenario: Classifier-judged served_when always forces confirm
|
||
|
|
|
||
|
|
- **WHEN** a rule uses `served_when` (free text) and the classifier judges the condition met
|
||
|
|
- **THEN** the deletion is always escalated to confirm, even if the matched path is tracked and clean
|
||
|
|
|
||
|
|
#### Scenario: The LLM may propose but never silently destroy on served_when
|
||
|
|
|
||
|
|
- **WHEN** the classifier judges a `served_when` condition satisfied
|
||
|
|
- **THEN** it produces a proposal for a human to confirm; it never causes an unattended deletion
|
||
|
|
|
||
|
|
### Requirement: Extract Modifier Routes Through Existing Knowledge Destinations Only
|
||
|
|
|
||
|
|
The `extract` modifier on a deletion SHALL distill durable content before
|
||
|
|
deleting, routing exclusively through the existing knowledge-routing
|
||
|
|
destinations: repo-durable residue SHALL be written into an ADR, `CLAUDE.md`,
|
||
|
|
or a `docs/` file; cross-repo lessons SHALL be written to the SecondBrain
|
||
|
|
vault via `/os-vault:write`. No new destination (e.g., a "retired specs"
|
||
|
|
directory) SHALL be introduced.
|
||
|
|
|
||
|
|
#### Scenario: Repo-durable extraction targets ADR/CLAUDE.md/docs
|
||
|
|
|
||
|
|
- **WHEN** an `extract-then-delete` op is classified as repo-durable
|
||
|
|
- **THEN** the extracted content is written into an ADR, `CLAUDE.md`, or a `docs/` file, never a new bespoke location
|
||
|
|
|
||
|
|
#### Scenario: Cross-repo extraction routes through /os-vault:write
|
||
|
|
|
||
|
|
- **WHEN** an `extract-then-delete` op is classified as cross-repo
|
||
|
|
- **THEN** the extracted content is written to the SecondBrain vault via `/os-vault:write`, and no other cross-repo destination is used
|
||
|
|
|
||
|
|
#### Scenario: No new destination is introduced
|
||
|
|
|
||
|
|
- **WHEN** extraction routing is implemented
|
||
|
|
- **THEN** it reuses only the destinations named above; it does not create a new "retired" or "archive" content store
|