Design the rulebook schema #40

Closed
opened 2026-07-14 16:52:04 +00:00 by jared · 3 comments
Owner

Map: #31

Question

Define the rulebook format: file location(s) in the plugin and per-project override; glob matching semantics and precedence (file over directory); fields per rule (lifetime, extract modifier, ignore-propagation flags, provenance/confirmed-by); how the scanner consumes it.

Map: #31 ## Question Define the rulebook format: file location(s) in the plugin and per-project override; glob matching semantics and precedence (file over directory); fields per rule (lifetime, extract modifier, ignore-propagation flags, provenance/confirmed-by); how the scanner consumes it.
jared added the
wayfinder:grilling
label 2026-07-14 16:52:04 +00:00
jared self-assigned this 2026-07-14 17:11:04 +00:00
Author
Owner

RESOLUTION: Rulebook schema locked.

Format & locations: JSON (stdlib both directions; per-rule note field substitutes for comments). Global rulebook at plugins/os-doc-hygiene/rulebook.json (resolved relative to plugin scripts, per #34). Per-project override at repo-root .dochygiene-rules.json — committed config, matching the .dochygiene-ignore precedent, deliberately NOT in gitignored .cc-os/. ⚠ ADR required for the repo-root-dotfile-vs-.cc-os/ deviation (user-flagged; fold into #46's ADR set).

Glob dialect: gitignore-style via stdlib glob.translate(recursive=True, include_hidden=True) (Python ≥3.13), patterns repo-root-relative, real **; compiled once at load. Directory rules = patterns covering a subtree (#38 semantics).

Precedence (two-axis, source first): project file-rule > project directory-rule > global file-rule > global directory-rule; ties broken by longest pattern, then last-defined. A project neutralizes a global rule by shadowing it with lifetime: "keep" — no rule-removal mechanism; the merge is add-only.

Per-rule fields:

{
  "glob": "autoresearch/*/**",
  "lifetime": "keep|temporary|delete-once-served",
  "extract": true,
  "served_when": "free text — hint consumed by the LLM classifier, no trigger DSL",
  "max_age_days": null,
  "propagate_ignore": ["graphify"],
  "confirmed_by": "human | <strong-model-id>",
  "confirmed_on": "2026-07-14",
  "source": "clutter-inventory #41",
  "note": "free-text rationale"
}

max_age_days values/defaults are #43's decision; propagate_ignore semantics are #44's — the schema fixes only that the slots exist.

Envelope: {"schema_version": 1, "rules": [...]} for both files. Unmatched files get NO lifetime — they flow through existing signals unchanged and become :calibrate candidates (unmatched-means-unmanaged).

Scanner consumption: new stdlib rulebook.py loader; a directory-rule match prunes the walk (files beneath never opened, one aggregate entry); a file-rule match attaches a lifecycle signal (rule ref + lifetime + served_when) consumed by the classifier as #37's new signal class.

Validation: skip-and-warn per invalid/unconfirmed rule (a rule missing confirmed_by never acts, per #39, but doesn't take scanning down); hard-fail on unparseable JSON or unknown schema_version.

RESOLUTION: Rulebook schema locked. **Format & locations:** JSON (stdlib both directions; per-rule `note` field substitutes for comments). Global rulebook at `plugins/os-doc-hygiene/rulebook.json` (resolved relative to plugin scripts, per #34). Per-project override at repo-root `.dochygiene-rules.json` — committed config, matching the `.dochygiene-ignore` precedent, deliberately NOT in gitignored `.cc-os/`. **⚠ ADR required** for the repo-root-dotfile-vs-`.cc-os/` deviation (user-flagged; fold into #46's ADR set). **Glob dialect:** gitignore-style via stdlib `glob.translate(recursive=True, include_hidden=True)` (Python ≥3.13), patterns repo-root-relative, real `**`; compiled once at load. Directory rules = patterns covering a subtree (#38 semantics). **Precedence (two-axis, source first):** project file-rule > project directory-rule > global file-rule > global directory-rule; ties broken by longest pattern, then last-defined. A project neutralizes a global rule by shadowing it with `lifetime: "keep"` — no rule-removal mechanism; the merge is add-only. **Per-rule fields:** ```json { "glob": "autoresearch/*/**", "lifetime": "keep|temporary|delete-once-served", "extract": true, "served_when": "free text — hint consumed by the LLM classifier, no trigger DSL", "max_age_days": null, "propagate_ignore": ["graphify"], "confirmed_by": "human | <strong-model-id>", "confirmed_on": "2026-07-14", "source": "clutter-inventory #41", "note": "free-text rationale" } ``` `max_age_days` values/defaults are #43's decision; `propagate_ignore` semantics are #44's — the schema fixes only that the slots exist. **Envelope:** `{"schema_version": 1, "rules": [...]}` for both files. Unmatched files get NO lifetime — they flow through existing signals unchanged and become :calibrate candidates (unmatched-means-unmanaged). **Scanner consumption:** new stdlib `rulebook.py` loader; a directory-rule match prunes the walk (files beneath never opened, one aggregate entry); a file-rule match attaches a `lifecycle` signal (rule ref + lifetime + served_when) consumed by the classifier as #37's new signal class. **Validation:** skip-and-warn per invalid/unconfirmed rule (a rule missing `confirmed_by` never acts, per #39, but doesn't take scanning down); hard-fail on unparseable JSON or unknown `schema_version`.
jared closed this issue 2026-07-14 17:29:03 +00:00
Author
Owner

RESOLUTION: Rulebook schema locked.

Format & locations: JSON (stdlib both directions; per-rule note field substitutes for comments). Global rulebook at plugins/os-doc-hygiene/rulebook.json (resolved relative to plugin scripts, per #34). Per-project override at repo-root .dochygiene-rules.json — committed config, matching the .dochygiene-ignore precedent, deliberately NOT in gitignored .cc-os/. ⚠ ADR required for the repo-root-dotfile-vs-.cc-os deviation (user-flagged; fold into #46's ADR set).

Glob dialect: gitignore-style via stdlib glob.translate(recursive=True, include_hidden=True) (Python ≥3.13), patterns repo-root-relative, real **; compiled once at load. Directory rules = patterns covering a subtree (#38 semantics).

Precedence (two-axis, source first): project file-rule > project directory-rule > global file-rule > global directory-rule; ties broken by longest pattern, then last-defined. A project neutralizes a global rule by shadowing with lifetime: "keep" — no rule-removal mechanism; merge is add-only.

Per-rule fields:

{
  "glob": "autoresearch/*/**",
  "lifetime": "keep|temporary|delete-once-served",
  "extract": true,
  "served_when": "free text — hint consumed by the LLM classifier, no trigger DSL",
  "max_age_days": null,
  "propagate_ignore": ["graphify"],
  "confirmed_by": "human | <strong-model-id>",
  "confirmed_on": "2026-07-14",
  "source": "clutter-inventory #41",
  "note": "free-text rationale"
}

max_age_days values/defaults are #43's decision; propagate_ignore semantics are #44's — schema fixes only that the slots exist.

Envelope: {"schema_version": 1, "rules": [...]} for both files. Unmatched files get NO lifetime — they flow through existing signals unchanged and become :calibrate candidates (unmatched-means-unmanaged).

Scanner consumption: new stdlib rulebook.py loader; directory-rule match prunes the walk (files beneath never opened, one aggregate entry); file-rule match attaches a lifecycle signal (rule ref + lifetime + served_when) consumed by the classifier as #37's new signal class.

Validation: skip-and-warn per invalid/unconfirmed rule (a rule missing confirmed_by never acts, per #39, but doesn't take scanning down); hard-fail on unparseable JSON or unknown schema_version.

RESOLUTION: Rulebook schema locked. **Format & locations:** JSON (stdlib both directions; per-rule `note` field substitutes for comments). Global rulebook at `plugins/os-doc-hygiene/rulebook.json` (resolved relative to plugin scripts, per #34). Per-project override at repo-root `.dochygiene-rules.json` — committed config, matching the `.dochygiene-ignore` precedent, deliberately NOT in gitignored `.cc-os/`. **⚠ ADR required** for the repo-root-dotfile-vs-.cc-os deviation (user-flagged; fold into #46's ADR set). **Glob dialect:** gitignore-style via stdlib `glob.translate(recursive=True, include_hidden=True)` (Python ≥3.13), patterns repo-root-relative, real `**`; compiled once at load. Directory rules = patterns covering a subtree (#38 semantics). **Precedence (two-axis, source first):** project file-rule > project directory-rule > global file-rule > global directory-rule; ties broken by longest pattern, then last-defined. A project neutralizes a global rule by shadowing with `lifetime: "keep"` — no rule-removal mechanism; merge is add-only. **Per-rule fields:** ```json { "glob": "autoresearch/*/**", "lifetime": "keep|temporary|delete-once-served", "extract": true, "served_when": "free text — hint consumed by the LLM classifier, no trigger DSL", "max_age_days": null, "propagate_ignore": ["graphify"], "confirmed_by": "human | <strong-model-id>", "confirmed_on": "2026-07-14", "source": "clutter-inventory #41", "note": "free-text rationale" } ``` `max_age_days` values/defaults are #43's decision; `propagate_ignore` semantics are #44's — schema fixes only that the slots exist. **Envelope:** `{"schema_version": 1, "rules": [...]}` for both files. Unmatched files get NO lifetime — they flow through existing signals unchanged and become :calibrate candidates (unmatched-means-unmanaged). **Scanner consumption:** new stdlib `rulebook.py` loader; directory-rule match prunes the walk (files beneath never opened, one aggregate entry); file-rule match attaches a `lifecycle` signal (rule ref + lifetime + served_when) consumed by the classifier as #37's new signal class. **Validation:** skip-and-warn per invalid/unconfirmed rule (a rule missing `confirmed_by` never acts, per #39, but doesn't take scanning down); hard-fail on unparseable JSON or unknown `schema_version`.
Author
Owner

Schema amendment (from #44): the propagate_ignore field is dropped. #44 resolved that a lifecycle rule never writes into another tool's ignore surface (separation of concerns; .graphifyignore ownership stays with os-vault:onboard-project per ADR-017). The reserved slot is removed, not left empty. Everything else in this schema stands. #46 must assemble from schema-minus-propagate_ignore.

**Schema amendment (from #44):** the `propagate_ignore` field is **dropped**. #44 resolved that a lifecycle rule never writes into another tool's ignore surface (separation of concerns; `.graphifyignore` ownership stays with os-vault:onboard-project per ADR-017). The reserved slot is removed, not left empty. Everything else in this schema stands. #46 must assemble from schema-minus-propagate_ignore.
Sign in to join this conversation.
No description provided.