- **WHEN** `derive_safety_tier(op_type, is_destructive, is_reversible, lifecycle)` is evaluated for any input where `op_type` = `generative`, or (for a non-lifecycle op) `is_destructive` = true, or `is_reversible` = false
#### Scenario: A lifecycle delete with scanner-proven evidence and tracked+clean state derives to auto
- **WHEN** a `delete` entry's `lifecycle` argument shows a satisfied `served_when_path` and the path is tracked and clean
- **THEN** the derived `safety_tier` is `auto`
#### Scenario: A lifecycle delete with classifier-judged evidence always derives to confirm
- **WHEN** a `delete` or `extract-then-delete` entry's `lifecycle` argument carries `served_when` (classifier-judged)
- **THEN** the derived `safety_tier` is `confirm`, regardless of tracked/clean state
#### Scenario: A lifecycle delete on a dirty or untracked path derives to confirm
- **WHEN** a `delete` entry's evidence is scanner-proven but the path is dirty or untracked
- **THEN** the derived `safety_tier` is `confirm`
### Requirement: Lifecycle Signal Fields on Shortlist and Entries
A shortlist entry or report entry carrying a lifecycle signal SHALL include a
`lifecycle` object with `rule_ref` (identifying which rulebook rule matched),
`lifetime` (`keep`, `temporary`, or `delete-once-served`), and exactly one of
`served_when_path` (deterministic) or `served_when` (classifier-judged, free
text), mirroring the rulebook rule's own fields. A directory-rule aggregate
entry SHALL carry the same `lifecycle` object shape as a file entry, with its
`path` set to the matched directory.
#### Scenario: A file-rule match carries the lifecycle object
- **WHEN** a file matches a `temporary`-lifetime file rule
- **THEN** its shortlist/report entry includes a `lifecycle` object with `rule_ref`, `lifetime: "temporary"`, and no `served_when`/`served_when_path` (temporary entries are keyed on age, not a served signal)
#### Scenario: A delete-once-served match carries exactly one served field
- **WHEN** a file matches a `delete-once-served` rule using `served_when_path`
- **THEN** its `lifecycle` object includes `served_when_path` and omits `served_when`, never both
#### Scenario: A directory-rule aggregate entry carries the same shape
- **WHEN** a directory rule produces one aggregate shortlist entry
- **THEN** that entry's `lifecycle` object has the same fields as a file entry's, with `path` set to the directory
- **WHEN** an `exact_edit` has a `kind` outside the closed set `delete-range`, `move-to-archive`, `insert-frontmatter`, `replace-text`, `dedupe`, `delete`, `extract-then-delete`
#### Scenario: delete and extract-then-delete require the lifecycle object
- **WHEN** an `exact_edit` has `kind` = `delete` or `kind` = `extract-then-delete`
- **THEN** it includes a `lifecycle` object with `rule_ref`, `lifetime`, and exactly one of `served_when_path` or `served_when`; omitting the `lifecycle` object or supplying both `served_when_path` and `served_when` makes the report invalid
#### Scenario: extract-then-delete requires an extraction_target
- **WHEN** an `exact_edit` has `kind` = `extract-then-delete`
- **THEN** it includes `extraction_target` set to `repo-durable` or `cross-repo`, with a target document reference required when `repo-durable`
Each entry SHALL include a `token_estimate`. In v1, only `raw_tokens` (a
local-tokenizer count of the removed or reduced span, with no API call) SHALL be
required. The `injection_frequency` (`per-session` or `on-demand`) and
`weighted_tokens` (`raw_tokens` adjusted by injection frequency) fields SHALL be
optional and MAY be `null` or omitted in v1; populating them (injection-frequency
weighting plus bottom-up rollup) is the v2 bonus per the PRD build order. When
populated, auto-injected files SHALL be weighted as real per-session savings and
on-demand docs SHALL be weighted as theoretical-max savings. Roll-up to category
and total SHALL be computed bottom-up from the entries.
#### Scenario: v1 entry carries only the required raw token count
- **WHEN** a v1 check writes an entry without the weighting fields
- **THEN** `token_estimate.raw_tokens` is present (a local-tokenizer count, no API call) and `injection_frequency` and `weighted_tokens` may be `null` or omitted
#### Scenario: Auto-injected file weighted per session
- **WHEN** the weighting fields are populated (v2) and the affected file is auto-injected (for example `CLAUDE.md`)
- **THEN** `injection_frequency` = `per-session` and `weighted_tokens` reflects real per-session savings
#### Scenario: On-demand doc weighted as theoretical max
- **WHEN** the weighting fields are populated (v2) and the affected file is read only on demand
- **THEN** `injection_frequency` = `on-demand` and `weighted_tokens` reflects theoretical-max savings
### Requirement: Schema Is a Frozen Contract
The report schema SHALL be treated as a frozen contract that every other
component consumes. The freeze SHALL be enforced by `invariants.md` (which
carries the schema-freeze and `safety_tier` derivation invariants); that file is
the authoritative enforcement mechanism for this contract. Any change to a field,
an enum value, or a documented semantic SHALL require updating `invariants.md`
with explicit human approval before it takes effect. Schema-shape verification
SHALL be carried by a schema-validator script and hand-authored **schema
fixtures** (one valid machine report, one invalid) under `examples/golden/`;
these schema-shape fixtures are distinct from classifier golden examples, which