feat(meta): config-driven L9_META pipeline, schema v2, full-repo stamp - #147
feat(meta): config-driven L9_META pipeline, schema v2, full-repo stamp#147cryptoxdog wants to merge 4 commits into
Conversation
The injector had a hardcoded FILE_REGISTRY as its source of truth, and two of its five formatters were stubs that emitted a delimiter pair with no field lines. So `--apply` on any YAML, shell, Makefile, or Python path wrote an empty block, and the registry drifted from the tree with no way to notice: 10 entries pointed at files that no longer existed while ~500 tracked files were invisible to it. Replace the registry with l9-meta.yaml, which resolves header values from path rules instead of per-file entries, and make the whole thing verifiable: - tools/l9_meta/ — model, discovery, config, resolve, adopt, emit, cli, and one module per format. tools/l9_meta_injector.py stays as a shim so existing docs and hooks that invoke it by path keep working. - Restore the comment and docstring formatters; add legacy bare-form `# L9_META` stripping, front-matter-aware markdown insertion (header goes after the front matter, never on line 1), and delimiter-anchored detection so prose that merely mentions L9_META is never matched as a header. - Discovery enumerates `git ls-files -z`, so paths with spaces and non-ASCII names survive, and untracked or generated output cannot be stamped. - Tags become three facets — family (exactly 1), capability (0-2), concern (0-2) — with a min_files floor. This folds 343 ad-hoc tags to a vocabulary of 188, dropping single-use tags and ones that only restated `layer`. - Schema v2 drops `owner`, which had no consumer. The parser still accepts v1. - Unknown keys in l9-meta.yaml are rejected rather than ignored: a misspelled vocabulary key silently disables the constraint it declares, and `check` would then pass while enforcing nothing. Stamp all 607 eligible files, and enforce via an l9-meta-check pre-commit hook and a meta-headers CI job that `test` now depends on. Contract C-018 moves from SHOULD over engine+chassis Python to schema v2 over every tracked file. Verified: check reports 607/607 consistent and a second apply writes 0 files (idempotent); 1412 unit+contract tests pass; ruff check and ruff format --check clean; contract_scanner and verify_contracts unchanged; all 16 JSON, 344 Python, 92 YAML and 16 front-matter files still parse. Co-authored-by: Cursor <cursoragent@cursor.com>
Three files in the pipeline itself were reported INVALID, and the sixteen tools/l9_meta modules were reported MISSING, immediately after the previous commit — the tool could not stamp itself. Two distinct false positives: - Delimiters were matched with optional leading whitespace, so the indented `--- L9_META ---` example inside comment.py's and docstring.py's own docstrings parsed as a header. Since the surrounding prose has no field lines, both files reported malformed. The block is always emitted flush-left, so anchoring at column 0 makes indentation the discriminator between an example and a header. - The comment finders scanned the whole file, so the `# --- L9_META ---` inside a fixture string in test_l9_meta_formats.py was read as that file's header. Bound the search to the leading comment region, which is the only place a header can legally appear. Add a rule for tools/l9_meta/ and an override for l9-meta.yaml so the pipeline and its config resolve like everything else, and stamp the sixteen modules. Verified: check reports 623/623 consistent, a second apply writes 0 files, 1416 unit+contract tests pass, ruff check and ruff format --check clean. Co-authored-by: Cursor <cursoragent@cursor.com>
JSON injection round-trips through the parser, so stamping a file also reserializes it. packet-envelope.schema.json came back +159/-29 for a 9-line header and devcontainer.json lost its section spacing and had its compact arrays exploded one element per line. The result is equivalent JSON, but the churn is unrelated to metadata and would dominate review. Exclude `**/*.json` and record DEFERRED-003 for the textual insert. The formatter and its fixtures stay — only the corpus shrinks, from 623 files to 607. Co-authored-by: Cursor <cursoragent@cursor.com>
|
❌ Too Many Reviewable Files Changed ❌ PR Too Large 📋 Best Practices for Large Changes
🚫 This PR is blocked until reviewable size limits are met. |
| @@ -1,3 +1,11 @@ | |||
| # --- L9_META --- | |||
| steps: | ||
| # fetch-depth is irrelevant here, but the checkout must be a real git repo: | ||
| # discovery enumerates via `git ls-files -z`, not a filesystem walk. | ||
| - uses: actions/checkout@v6 |
| # fetch-depth is irrelevant here, but the checkout must be a real git repo: | ||
| # discovery enumerates via `git ls-files -z`, not a filesystem walk. | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/setup-python@v5 |
| with: | ||
| python-version: "3.12" | ||
| - run: pip install pyyaml | ||
| - run: python tools/l9_meta_injector.py check |
| class Format(Protocol): | ||
| """The four operations every format implements.""" | ||
|
|
||
| def render(self, rec: MetaRecord) -> Any: ... |
| """The four operations every format implements.""" | ||
|
|
||
| def render(self, rec: MetaRecord) -> Any: ... | ||
| def parse(self, text: str) -> MetaRecord | None: ... |
|
|
||
| def render(self, rec: MetaRecord) -> Any: ... | ||
| def parse(self, text: str) -> MetaRecord | None: ... | ||
| def strip(self, text: str) -> str: ... |
| def render(self, rec: MetaRecord) -> Any: ... | ||
| def parse(self, text: str) -> MetaRecord | None: ... | ||
| def strip(self, text: str) -> str: ... | ||
| def inject(self, text: str, rec: MetaRecord) -> str: ... |
The four files were last written on this branch before the L9_META pipeline and
contract-registry work landed, so the resume state pointed at PRs that have since
been opened and at next actions that are done.
activeContext.md is rewritten rather than appended: a session-end hook had
overwritten it in the main working tree with a bare template ("No summary
provided" plus a generic file list), which would have destroyed the PR #144
narrative this branch already carried. The rewrite keeps that context, folds in
#145-#148, and records the one non-obvious hazard — PR #147 already contains the
full-repo header stamp, so a second "meta-only" PR would duplicate it.
progress.md, tasks.md and tech-debt.md are additive; the three tech-debt entries
that appear removed are replaced by updated versions in the same commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
Records which Quantum-L9 CI repos are actually wired into this one (l9-ci-core, l9-ci-sdk) versus referenced but not adopted (l9-harness, l9-assurance), and the extend-never-replace rule for tools/audit_harness.py and .github/workflows/audit.yml. Without this written down the failure mode is silent: a future wiring pass reads audit.yml, sees a thin caller, and replaces the repo's own harness instead of extending it. The AGENTS.md table entry that points here is part of a separate docs stream and is not included, so the doc lands unreferenced for now. Its header is schema v1; PR #147 moves the repo to v2 and will restamp it. Co-authored-by: Cursor <cursoragent@cursor.com>
The four files were last written on this branch before the L9_META pipeline and
contract-registry work landed, so the resume state pointed at PRs that have since
been opened and at next actions that are done.
activeContext.md is rewritten rather than appended: a session-end hook had
overwritten it in the main working tree with a bare template ("No summary
provided" plus a generic file list), which would have destroyed the PR #144
narrative this branch already carried. The rewrite keeps that context, folds in
#145-#148, and records the one non-obvious hazard — PR #147 already contains the
full-repo header stamp, so a second "meta-only" PR would duplicate it.
progress.md, tasks.md and tech-debt.md are additive; the three tech-debt entries
that appear removed are replaced by updated versions in the same commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
TROUBLESHOOTING now describes `l9_meta_injector.py apply` and the resolve-by-path model; PRODUCTION-SPEED-PACK drops its Suite-6 header for schema v2; DEFERRED records the JSON exclusion (DEFERRED-003).
|
|
Closing: the L9_META v2 injection pipeline is stale. The 607-file schema-v2 stamp it carried has been discarded from the working tree; the full pre-discard state is preserved locally on safety ref 591b66c if any of it is wanted later. tools/l9_meta_injector.py remains on main in its original form. |




Why
The injector kept a hardcoded
FILE_REGISTRYas its source of truth, and two of its five formatters were stubs that emitted a delimiter pair with no field lines. So--applyon any YAML, shell, Makefile, or Python path wrote an empty block, and the registry drifted from the tree with nothing to catch it: 10 entries pointed at files that no longer existed, while ~500 tracked files were invisible to it. Contract C-018 claimed every file carries a header; in practice 371 did, inconsistently.What changed
Config replaces the registry.
l9-meta.yamlresolves header values from path rules rather than per-file entries:defaults -> rules (last match wins) -> overrides[exact path]. Adding a file no longer means editing the tool.Formatters restored and hardened.
tools/l9_meta/formats/is one module per format. Beyond emitting the field lines again:# L9_METAblocks are recognized and replaced, not duplicatedL9_METAliteral inside a test fixture is never read as a headerDiscovery is git-based. Enumeration is
git ls-files -z, so paths with spaces and non-ASCII names survive, and untracked or generated output cannot be stamped.Tags become a closed taxonomy. Three facets —
family(exactly 1),capability(0-2),concern(0-2) — with amin_filesfloor. The corpus had 343 distinct tags across 371 files: 197 used exactly once and 41 that merely restatedlayer. Neither is queryable. Folded to a vocabulary of 188.Schema v2 drops
owner, which had no consumer. The parser still accepts v1; the writer emits v2.Unknown config keys are rejected. A misspelled
vocabularykey silently disables the constraint it declares, andcheckwould then pass while enforcing nothing.Enforcement
l9-meta-checkpre-commit hook (always_run, since a rule edit changes expectations for files that are not themselves staged)meta-headersCI job incontracts.yml, whichtestnow depends onengine/+chassis/Python to schema v2 over every tracked filetools/l9_meta_injector.pyremains as a shim overtools.l9_meta.cli, so the six docs and hooks that invoke it by path keep working.l9-meta initscaffolds a config in a fresh repo.Deferred
**/*.jsonis excluded and tracked as DEFERRED-003. JSON injection round-trips through the parser, so stamping also reserializes:packet-envelope.schema.jsoncame back +159/−29 for a 9-line header. Equivalent JSON, but churn unrelated to metadata. 16 files.Test plan
checkreports 607/607 consistent (0 missing, 0 drift, 0 misplaced, 0 invalid)applywrites 0 files — idempotentruff check .clean;ruff format --check .— 344 files already formattedfrom __future__stays the first statementcontract_scannerandverify_contractsunchangedrender -> parse -> render) and idempotency (inject(inject(x)) == inject(x)) fixtures per formatBranched from
origin/mainin a clean worktree, so the diff is only the meta pipeline plus the headers it writes.Known pre-existing on
main, not touched here:mypyreports a syntax error inengine/inference_rule_registry.py.Made with Cursor