# Delta: lifecycle-rulebook (calibrate-assessment-inventory)

## MODIFIED Requirements

### Requirement: Rulebook Locations and Envelope

The plugin SHALL ship a global rulebook at
`plugins/os-doc-hygiene/rulebook.json`, resolved relative to plugin scripts,
present in every project. A project MAY additionally provide a committed
repo-root `.dochygiene-rules.json` override. Both files SHALL use the
envelope `{"schema_version": 1, "rules": [...]}`. The project file MAY
additionally carry a top-level `nominations` key (see the Nominations Memory
requirement); the loader SHALL ignore unknown top-level keys, so the key is
additive to `schema_version` 1. The per-project override SHALL NOT live under
gitignored `.cc-os/` — it SHALL be a committed, reviewable dotfile.

#### Scenario: Global rulebook is always present

- **WHEN** the rulebook loader runs in any project
- **THEN** it loads `plugins/os-doc-hygiene/rulebook.json` resolved relative to the plugin scripts directory

#### Scenario: Per-project override is optional and committed

- **WHEN** a project has no `.dochygiene-rules.json` at its repo root
- **THEN** the loader proceeds using only the global rulebook, and when the file is present it is read as a committed, reviewable file, never from `.cc-os/`

#### Scenario: Both files share one envelope shape

- **WHEN** either the global rulebook or a project override is loaded
- **THEN** it is validated against the envelope `{"schema_version": 1, "rules": [...]}`

#### Scenario: A nominations key does not disturb the v1 loader

- **WHEN** a project `.dochygiene-rules.json` carries a top-level `nominations` key alongside `rules`
- **THEN** `rulebook.py` loads the `rules` array exactly as before, ignoring the unknown top-level key without warning or error

## ADDED Requirements

### Requirement: Nominations Memory Lives in the Project Rules File

The project `.dochygiene-rules.json` MAY carry a top-level `nominations` key
holding exactly two lists: `consults` (open questions — entries with `glob`,
`question`, `evidence`, `cluster_key`, `asked_on`, and deliberately NO
lifetime) and `rejected` (settled "no" answers — entries with `glob`,
`lifetime`, `why`, optional `consider_instead`, `rejected_by` (`"judge"` or
`"human"`), `judged_on`). The `nominations` key SHALL never affect which
files the rulebook governs — only entries in `rules` decide that.
`rulebook.py` SHALL remain nomination-unaware; only the calibrate helpers
read the key, and the calibrate reader SHALL warn on unrecognized nomination
fields, mirroring the rules array's unknown-field discipline. Rejected
entries and exact-path singleton keep rules SHALL exit only by hand-deletion
(removals stay HITL with recorded reasoning); no automated revisit path
SHALL exist.

#### Scenario: Nominations never filter files

- **WHEN** the scanner or rulebook resolves the governing rule for a path that only a `nominations` entry's glob matches
- **THEN** the path is treated as unmatched/unmanaged — nominations carry no lifecycle authority

#### Scenario: Consult entries carry no lifetime

- **WHEN** a consult entry is written to `nominations.consults`
- **THEN** it records `glob`, `question`, `evidence`, `cluster_key`, `asked_on` and no lifetime field — presence in the list means open, with no status field

#### Scenario: Unrecognized nomination fields warn in the calibrate reader

- **WHEN** the calibrate helpers read a nominations entry containing an unknown field
- **THEN** a warning is emitted and the field is preserved, never silently dropped

#### Scenario: A rejection leaves only by hand-deletion

- **WHEN** a calibration pass runs against a rules file containing a stale rejection
- **THEN** no automated path removes or expires the entry; it is removed only by explicit human edit

### Requirement: Canonical Writer-Enforced Ordering

Every code path that serializes `.dochygiene-rules.json` SHALL write through
one canonical writer that emits: `rules` grouped by lifetime tier in the
order delete-once-served, temporary, keep, glob-sorted within each group;
`nominations` after `rules`; `consults` before `rejected`, each glob-sorted.
The writer SHALL be idempotent (canonicalizing an already-canonical file is a
no-op) and SHALL round-trip unknown fields with a warning rather than
dropping them. Ordering SHALL NOT be enforced by any hook; hand edits
re-canonicalize on the next write.

#### Scenario: Writes are grouped and sorted canonically

- **WHEN** the writer serializes a rules file containing rules of all three tiers plus nominations
- **THEN** the output orders rules delete-once-served → temporary → keep with globs sorted within each group, and nominations follows rules with consults before rejected, each list glob-sorted

#### Scenario: Canonicalization is idempotent

- **WHEN** the writer serializes a file it previously wrote, unchanged
- **THEN** the output is byte-identical

#### Scenario: A hand-edited file re-canonicalizes on the next write

- **WHEN** a human appends a rule out of tier order and a later calibrate run persists a new entry
- **THEN** the whole file is rewritten in canonical order in that write, with no hook involved in the interim
