cc-os/plugins/os-doc-hygiene/openspec/changes/calibrate-assessment-inventory/specs/lifecycle-rulebook/spec.md

5.1 KiB

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