From 24c96ae868067637d396194d6390e151215455cb Mon Sep 17 00:00:00 2001 From: zaebee Date: Wed, 14 Oct 2026 20:26:57 +0000 Subject: [PATCH 1/5] =?UTF-8?q?docs(plan):=20language-packs=20step=206=20?= =?UTF-8?q?=E2=80=94=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/plans/2026-10-14-language-packs-step6.md | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 docs/plans/2026-10-14-language-packs-step6.md diff --git a/docs/plans/2026-10-14-language-packs-step6.md b/docs/plans/2026-10-14-language-packs-step6.md new file mode 100644 index 00000000..d8b0deff --- /dev/null +++ b/docs/plans/2026-10-14-language-packs-step6.md @@ -0,0 +1,270 @@ +# Language Packs — Step 6: Gates — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the observe-only `project_level` quotient binding with the enforced gates this migration built. Give each language pack and `core` its own drift domain, so the next pack is measured against the same bar the moment it lands. Close #146. + +**Architecture:** This step changes ontology configuration and self-parse tests only. No product code moves, so no stored graph can change. +- `docs/ontology/patterns.yaml` loses its `project_level` binding. `extraction` becomes one domain per pack, plus a `core` domain. `tolerances.lock` follows. +- `tests/self_parsing/test_drift.py` loses the `project_level` test. The plane-quotient report builds its own binding from `QUOTIENT_PREFIX` instead of borrowing the deleted one. +- A new test makes every registered pack declare its domain at the shared tolerance. A pack added in step 7 without a domain then fails the suite. +- The `project_level` feature itself (`DriftScorer.load_project_level`, the CLI, the guardian collector, `cgis_init_ontology`'s skeleton) stays as it is. It serves other repositories' `patterns.yaml`. Only this repository stops using it. + +**Tech Stack:** Python 3.12, pytest, ruff, mypy strict, YAML. + +**Spec:** `docs/specs/2026-10-14-language-packs-design.md` (rev 3): "Drift gates" (the **Domains** and **Plane quotients** paragraphs), and Migration row 6. Outline: `docs/plans/2026-10-14-language-packs.md` "Step 6". Steps 1–5: #597, #599, #600, #601, #602. + +## Global Constraints + +- **Full gate before every commit:** `make format && make lint && make type-check && make pytest && make doc-coverage` passes. Chain with `&&`, never with `;` or a pipe on the gating command. +- **Snapshot:** `tests/fixtures/graph_identity/snapshot.json` is not modified. Nothing in `src/` changes, so no identity diff is required. Task 3 checks that with `git diff --stat main -- src`. +- **Tolerances:** + - A tolerance may only go down, or be set for a new domain, with the measurement in the commit message (spec, Migration). + - The new domains' tolerances and hygiene baselines are **measured on the branch**, each set at the measurement plus the project's usual small headroom (≈0.02–0.03, rounded up to two places). + - **Stop and report** if a domain's best-fit measurement contradicts the pattern ruled below by more than that headroom. +- **Test-count floor:** raise `.github/test-count-baseline` (now 3095) if the suite passes 3245. +- **Commits:** end with `Co-Authored-By: Claude Opus 5.5 `. The PR body ends with `🤖 Generated with [Claude Code](https://claude.com/claude-code)`. +- **Workspace:** worktree `/home/zaebee/projects/ownima/cgb-step6`, branch `feat/language-packs-step6`. + +## Measurements taken while writing this plan (main ca32430, fresh ingest of `src/`) + +| domain (prefix) | layered_dag | pure_utility | pipeline_stage | orchestrator | funnel | best fit | unresolved | nodes | +|---|---|---|---|---|---|---|---|---| +| `cgis.languages.python` | **0.393** | 0.494 | 0.523 | 0.483 | 0.328 | funnel | 0.356 | 131 | +| `cgis.languages.typescript` | **0.184** | 0.636 | 0.463 | 0.323 | 0.410 | layered_dag | 0.295 | 103 | +| `cgis.core` | **0.215** | 0.554 | 0.377 | 0.377 | 0.377 | layered_dag | 0.191 | 59 | + +## Rulings — two of them need your decision (G1, G2) + +- **G1 — `core` is `layered_dag`, not the spec's `pure_utility`.** + - Measured against `pure_utility`, `core` scores 0.554, which would fail any sensible tolerance. Against `layered_dag` it scores 0.215, its best fit. + - `core` is no longer a flat bag of utilities. Steps 1–4 put the pack contract there, and the contract is layered: `extractor → models`, `index → models`, `resolution → index, models`, `project`. + - **Ruling (recommended):** `core` gets `layered_dag`, drift_tolerance 0.25, hygiene 0.21. + - **Alternative:** keep `pure_utility` and restructure `core`. That is out of scope here and would undo step 1's ruling R4. +- **G2 — packs share one pattern and one tolerance, set by the loosest pack.** + - The spec asks for one shared tolerance, so that a new pack faces the same bar as the existing ones. + - The Python pack is the loosest on `layered_dag` at 0.393. Its best fit is `funnel` (0.328): many extractor handlers converge on `_syntax`. + - **Ruling (recommended):** both packs `layered_dag`, shared drift_tolerance **0.42** (0.393 + headroom). This is still at or above the old `extraction` lock of 0.40, which was a single domain over both packs; it is set as new domains, with the measurement in the commit. Hygiene baselines are per pack, since unresolved ratios are language-specific: python 0.37 (the old extraction baseline), typescript 0.31. + - **Cost:** the TypeScript pack (0.184) gets a loose bar, and so will Go. The test in Task 2 enforces the *same* tolerance for every pack, so tightening it later tightens it for all. + - **Alternative A:** per-pack tolerances (python 0.42, typescript 0.21). That gives a tighter TS bar but drops the spec's "shared". + - **Alternative B:** the Python pack on `funnel` (0.36) and TypeScript on `layered_dag` (0.21). Each pack gets its best fit, but there is no single bar for Go. +- **G3 — the plane-quotient report keeps running.** Deleting `project_level` removes the binding the plane test borrowed its `fqn_prefix` from. The test now builds a `DomainConfig` from `QUOTIENT_PREFIX` itself. It stays observe-only, as the spec says. +- **G4 — `_PLANES` follows the domain names** (deferred minor from step 0). The build plane becomes `pipeline, languages.python, languages.typescript, resolution, storage`, and `core` is added to neither plane (it is shared by both). If a domain name containing a dot breaks the quotient (`quotient.languages.python`), the domains are named `languages-python` / `languages-typescript` instead. Ledger which. + +## Review Focus + +- **A pack registered without a drift domain**, which is what step 7's Go pack would be if someone forgot. It must fail the suite and say which pack (Task 2, `test_every_pack_has_a_domain_at_the_shared_tolerance`). +- **A pack domain given a looser tolerance than the others.** It must fail, because that is what "shared" means (same test). +- **The `project_level` feature for other repositories.** It must still load and score: the `tests/unit/test_drift.py` and `test_drift_service.py` cases that write their own `project_level` YAML stay green and unmodified. +- **`cgis drift` on this repository.** With no `project_level` binding it must still print a report (no crash on an empty binding list), checked in Task 1 Step 5. +- **The tolerance lock.** It must cover exactly the declared domains (`test_tolerances_lock_covers_exactly_the_declared_domains`). `extraction` leaves and the three new domains enter in the same commit. + +--- + +### Task 1: Retire the `project_level` binding + +**Files:** +- Modify: + - `docs/ontology/patterns.yaml:260-273` (delete the binding and its comment block) + - `docs/ontology/tolerances.lock` (delete `project_level:`) + - `tests/self_parsing/test_drift.py` (delete `test_py_quotient_drift_observed_not_enforced`; rewrite the plane test's binding) + - `tests/unit/test_patterns_yaml.py:201-209` (`test_project_level_binding_observe_only` becomes the absence check below) + +- [ ] **Step 1: Write the failing test.** In `tests/unit/test_patterns_yaml.py`, replace `test_project_level_binding_observe_only` with: + +```python +def test_this_repository_binds_no_project_level_quotient() -> None: + """Retired in language-packs step 6 (#146): the enforced topology gate is the + forbidden-edge test in test_architecture.py; plane quotients are reported, not bound. + The feature itself stays for other repositories' patterns.yaml.""" + assert "project_level" not in _load() +``` + +- [ ] **Step 2: Run it and watch it fail.** + - Run: `uv run pytest tests/unit/test_patterns_yaml.py -q -k project_level` + - Expected: FAIL. The binding is still present. + +- [ ] **Step 3: Delete the binding.** + 1. In `patterns.yaml`, delete everything from the `# ── Project-level quotient binding` comment through the `enforce: false` line. + 2. In its place, leave one comment: + +```yaml +# ── Project-level quotient ─────────────────────────────────────────────────── +# Retired in language-packs step 6 (#146). cgis's topology is enforced by the +# forbidden-edge gate (tests/self_parsing/test_architecture.py, exact shrinking +# allowlists); the build/read plane quotients are printed observe-only by +# tests/self_parsing/test_drift.py. The `project_level` section remains a +# supported feature for other repositories. +``` + + 3. In `tolerances.lock`, delete the `project_level:` section (two lines). + 4. In `test_drift.py`: + - delete `test_py_quotient_drift_observed_not_enforced`; + - in `test_plane_quotients_observed_not_enforced`, replace `binding = scorer.load_project_level()[0]` with a binding the test owns: + +```python + binding = DomainConfig( + name="plane", fqn_prefix=QUOTIENT_PREFIX, expected_pattern="", profile="python", + drift_tolerance=1.0, enforce=False, + ) # fmt: skip +``` + + - import `DomainConfig` from wherever `DriftScorer.load_project_level` gets it (`git grep -n "class DomainConfig" src`), and `QUOTIENT_PREFIX` from `cgis.query.drift.quotient`; + - read `DomainConfig`'s required fields first and pass exactly those. If `profile` is not a field, drop it. + 5. Update the `test_drift.py` module docstring if it mentions the project-level binding. + +- [ ] **Step 4: Run the tests.** + - Run: `uv run pytest tests/unit/test_patterns_yaml.py tests/self_parsing/test_drift.py tests/unit/test_drift.py tests/unit/test_drift_service.py -q -s -k "plane or project_level or quotient or tolerance"` + - Expected: + - all pass; + - the plane test still prints its two `[plane observe-only]` lines with the same numbers as before (build 0.238, read 0.676 at step 0; whatever main prints now). Compare against `git stash`-free main, run from `/home/zaebee/projects/ownima/codegraph-brain`. + +- [ ] **Step 5: `cgis drift` on this repository.** + - Run: `uv run cgis drift --db --profile python` + - Expected: a report with no `Quotient k=1 [cgis-project]` line, and no traceback. Delete the database afterwards. + +- [ ] **Step 6: Gate and commit.** + - Message: `chore(ontology): retire cgis's project_level quotient binding (#146)`. + - Body: the gate it is replaced by, and that the feature stays for other repositories. + +--- + +### Task 2: One drift domain per pack, and `core` + +**Files:** +- Modify: + - `docs/ontology/patterns.yaml` (the `extraction` entry → `languages.python`, `languages.typescript`; new `core` entry) + - `docs/ontology/tolerances.lock` + - `tests/self_parsing/test_drift.py` (`_PLANES`) +- Test: `tests/unit/test_patterns_yaml.py` (append) + +**Interfaces:** +- Consumes: `cgis.languages.MANIFESTS`, each with `.name`. + +- [ ] **Step 1: Write the failing test.** Append to `tests/unit/test_patterns_yaml.py`: + +```python +from cgis.languages import MANIFESTS + +#: One bar for every language pack (spec: Drift gates, Domains) — set by the +#: loosest pack at step 6 (ruling G2); a new pack meets it or the suite says so. +_PACK_PATTERN = "layered_dag" + + +def test_every_pack_has_a_domain_at_the_shared_tolerance() -> None: + domains = {d["name"]: d for d in _load()["project_domains"]} + packs = {m.name: domains.get(f"languages.{m.name}") for m in MANIFESTS} + missing = sorted(name for name, domain in packs.items() if domain is None) + assert not missing, f"packs without a drift domain in patterns.yaml: {missing}" + tolerances = {name: domain["drift_tolerance"] for name, domain in packs.items()} + assert len(set(tolerances.values())) == 1, f"pack tolerances must be shared: {tolerances}" + for name, domain in packs.items(): + assert domain["fqn_prefix"] == f"cgis.languages.{name}" + assert domain["expected_pattern"] == _PACK_PATTERN + assert domain["profile"] == "python" + + +def test_core_has_a_domain() -> None: + domains = {d["name"]: d for d in _load()["project_domains"]} + assert domains["core"]["fqn_prefix"] == "cgis.core" +``` + + Move the import to the import block. + +- [ ] **Step 2: Run them and watch them fail.** + - Run: `uv run pytest tests/unit/test_patterns_yaml.py -q -k "every_pack or core_has"` + - Expected: FAIL, with `packs without a drift domain ...: ['python', 'typescript']` and `KeyError: 'core'`. + +- [ ] **Step 3: Replace `extraction`.** + 1. In `patterns.yaml`, replace the whole `extraction` entry, comments included, with the entries below. Keep the history in one line: `# Was the single "extraction" domain over cgis.extractors / cgis.languages (#116, #183, steps 1–5).` + +```yaml + # Language packs (spec: Drift gates, Domains) — one domain per pack, one shared + # bar (ruling G2), so a new pack is measured against it from its first commit; + # tests/unit/test_patterns_yaml.py fails for a pack without one. + # Measured at step 6 (main ca32430): python layered_dag 0.393 (best fit funnel + # 0.328), typescript layered_dag 0.184. Shared tolerance = loosest + headroom. + - name: "languages.python" + fqn_prefix: "cgis.languages.python" + expected_pattern: layered_dag + profile: python + drift_tolerance: 0.42 + hygiene_baseline: + unresolved_ratio: 0.37 # measured 0.356 at step 6 (the old extraction baseline) + + - name: "languages.typescript" + fqn_prefix: "cgis.languages.typescript" + expected_pattern: layered_dag + profile: python + drift_tolerance: 0.42 + hygiene_baseline: + unresolved_ratio: 0.31 # measured 0.295 at step 6 + + # The pack contract and the model (spec: Core). layered_dag, not the spec's + # pure_utility — measured 0.215 vs 0.554 at step 6: the contract is layered + # (extractor/index/resolution/project -> models) since steps 1-4 (ruling G1). + - name: "core" + fqn_prefix: "cgis.core" + expected_pattern: layered_dag + profile: python + drift_tolerance: 0.25 + hygiene_baseline: + unresolved_ratio: 0.21 # measured 0.191 at step 6 +``` + + If the user chose an alternative to G1 or G2 at plan review, use those values instead and ledger them. + 2. In `tolerances.lock`, replace `extraction: 0.40` with `languages.python: 0.42`, `languages.typescript: 0.42` and `core: 0.25`, in `project_domains` order. + 3. In `test_drift.py`, set `_PLANES["build"]` to `("pipeline", "languages.python", "languages.typescript", "resolution", "storage")`. + +- [ ] **Step 4: Run the tests.** + - Run: `uv run pytest tests/unit/test_patterns_yaml.py tests/self_parsing/test_drift.py -q -s` + - Expected: + - all pass; + - `test_py_self_drift_within_tolerance` passes with the new domains, and their measured drift matches the table above within ±0.01, because no code changed; + - the plane report prints. + - If the quotient breaks on the dotted names (`a domain matched nothing`, or the plane node count is off), apply G4's fallback names `languages-python` / `languages-typescript` in the yaml, the lock, `_PLANES` and the test's `f"languages.{m.name}"`, and ledger it. + +- [ ] **Step 5: Mutation-check the pack test.** + - Delete the `languages.typescript` entry from the yaml for one run: the test must fail naming `typescript`. + - Set its tolerance to 0.43 for another run: the test must fail on "shared". + - Restore the file and confirm with `git diff --stat`. + +- [ ] **Step 6: Gate and commit.** + - Message: `chore(ontology): one drift domain per language pack, and core`. + - Body: the measurements table and rulings G1 and G2 (or the user's alternative). Both are "set for a new domain with the measurement", which the ratchet allows. + +--- + +### Task 3: Docs, review, PR, close #146 + +- [ ] **Step 1: Spec note.** In "Drift gates", add `> **Implementation note (step 6):** …` covering: + - `project_level` retired for this repository (the feature stays); + - the plane report owns its binding; + - the per-pack domains with the measured table and G2's shared 0.42; + - `core` on `layered_dag` and why (G1). + + Then run `git diff --stat main -- src`. Expected: empty. + +- [ ] **Step 2: Gate and commit.** + - Message: `docs: step 6 implementation note`. + +- [ ] **Step 3: Independent review.** Dispatch a reviewer with execution tools. Give it: + - the branch diff, this plan, the spec and the ledger; + - the Review Focus; + - the disk warning. + + Ask it to re-measure the three domains on main and the branch, and to mutation-check both new tests. Fix Critical and Important findings test-first. + +- [ ] **Step 4: PR.** + - Title: `chore(ontology): step 6 — gates`. + - Body: + - what was retired and what replaced it; + - the table; + - G1–G4; + - "closes #146" wording **only in prose**. Close #146 by hand after merge, with a comment summarising steps 0–6. Step 7, the Go pilot, does not block it. + - Check the Gemini and Guardian comments and Sonar, and reply in every thread. +- [ ] **Step 5: After merge, close #146** with a comment: + - the enforced gate (forbidden edges, all pack-related allowlists empty); + - the plane quotients observe-only; + - the per-pack domains; + - links #596–#602 and the follow-ups #603 and #604. From 0630f01f752b9670c04b15ffb9c88ffb82e0291e Mon Sep 17 00:00:00 2001 From: zaebee Date: Wed, 14 Oct 2026 20:36:04 +0000 Subject: [PATCH 2/5] chore(ontology): retire cgis's project_level quotient binding (#146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The observe-only `cgis-project` binding (pipeline_stage, never enforced) is replaced by what the language-packs migration built: the forbidden-edge gate with exact shrinking allowlists (test_architecture.py) as the enforced topology check, and the build/read plane quotients printed observe-only. The plane report now owns its binding (QUOTIENT_PREFIX) instead of borrowing the deleted one — same numbers (build 0.236, read 0.676). `project_level` stays a supported feature for other repositories; only this one stops using it. Co-Authored-By: Claude Opus 5.5 --- docs/ontology/patterns.yaml | 18 +++++-------- docs/ontology/tolerances.lock | 3 --- tests/self_parsing/test_drift.py | 44 +++++--------------------------- tests/unit/test_patterns_yaml.py | 17 ++++++------ 4 files changed, 21 insertions(+), 61 deletions(-) diff --git a/docs/ontology/patterns.yaml b/docs/ontology/patterns.yaml index 2f08eb8e..fb739091 100644 --- a/docs/ontology/patterns.yaml +++ b/docs/ontology/patterns.yaml @@ -259,15 +259,9 @@ project_domains: hygiene_baseline: unresolved_ratio: 0.79 # measured 0.7857 when the gate came alive (#149) -# ── Project-level quotient binding (spec §3.4) — OBSERVE-ONLY burn-in ──────── -# Measured quotient IMPORTS census {021D .4, 021U .4, 030T .2}: the domain -# graph is an orchestrator star out of `pipeline`, NOT the chain the spec -# example claimed. enforce stays false until the burn-in settles whether to -# rebind to orchestrator or restructure; flipping to true makes it a ratchet. -project_level: - - name: "cgis-project" - fqn_prefix: "quotient" - expected_pattern: pipeline_stage - profile: python - drift_tolerance: 0.15 - enforce: false +# ── Project-level quotient ─────────────────────────────────────────────────── +# Retired in language-packs step 6 (#146). cgis's topology is enforced by the +# forbidden-edge gate (tests/self_parsing/test_architecture.py, exact shrinking +# allowlists); the build/read plane quotients are printed observe-only by +# tests/self_parsing/test_drift.py. The `project_level` section remains a +# supported feature for other repositories. diff --git a/docs/ontology/tolerances.lock b/docs/ontology/tolerances.lock index f8eeda93..fcd3ed6c 100644 --- a/docs/ontology/tolerances.lock +++ b/docs/ontology/tolerances.lock @@ -25,6 +25,3 @@ project_domains: store: 0.15 providers: 0.17 utils: 0.20 - -project_level: - cgis-project: 0.15 diff --git a/tests/self_parsing/test_drift.py b/tests/self_parsing/test_drift.py index 027b6cca..09d812d8 100644 --- a/tests/self_parsing/test_drift.py +++ b/tests/self_parsing/test_drift.py @@ -12,7 +12,7 @@ from cgis.core.models import Edge, Node from cgis.query.drift.drift import DomainConfig, DriftScorer from cgis.query.drift.fingerprint import FingerprintExtractor -from cgis.query.drift.quotient import build_quotient, quotient_call_tally +from cgis.query.drift.quotient import QUOTIENT_PREFIX, build_quotient, quotient_call_tally from cgis.storage.sqlite_store import SQLiteStore from .conftest import ONTOLOGY_DIR, skip_if_no_ui @@ -130,41 +130,6 @@ def test_ts_self_drift_within_tolerance( # --------------------------------------------------------------------------- -def test_py_quotient_drift_observed_not_enforced( - root_graph_data: tuple[SQLiteStore, list[Node], list[Edge]], -) -> None: - """Score the domain-quotient graph against project_level; report, never fail. - - enforce is false for the burn-in milestone: the assertion here is only - that the machinery runs end-to-end and the binding stays observe-only. - The k=1 CALLS layer is discounted by the members' unresolved share, carried - over through quotient_call_tally (#149) — the precondition for enforcing. - """ - store, _, _ = root_graph_data - scorer = DriftScorer(_PATTERNS) - bindings = scorer.load_project_level() - assert bindings, "project_level binding missing from patterns.yaml" - - domains = _selected_domains(scorer, graph="python") - nodes, edges = store.get_all_nodes(), store.get_all_edges() - qnodes, qedges = build_quotient(nodes, edges, domains) - extractor = FingerprintExtractor.from_graph( - qnodes, qedges, quotient_call_tally(nodes, edges, domains) - ) - - for binding in bindings: - assert binding.enforce is False, ( - "project_level flipped to enforce: true — this test must become a " - "tolerance assertion like _assert_within_tolerance (see spec §3.4)" - ) - report = scorer.score(extractor.extract(binding.fqn_prefix), binding) - print( - f"\n[quotient observe-only] {binding.name}: drift={report.drift_score:.3f} " - f"(tolerance {binding.drift_tolerance:.2f}, tv_imports={report.tv_imports}, " - f"tv_calls={report.tv_calls})\n violations: {report.violations}" - ) - - _PLANES = { "build": (("pipeline", "extraction", "resolution", "storage"), "orchestrator"), "read": (("storage", "query", "guardian", "cli", "api"), "layered_dag"), @@ -181,7 +146,12 @@ def test_plane_quotients_observed_not_enforced( """ store, _, _ = root_graph_data scorer = DriftScorer(_PATTERNS) - binding = scorer.load_project_level()[0] + # The plane report owns its binding: the project_level one it borrowed was + # retired in step 6 (#146). expected_pattern is set per plane below. + binding = DomainConfig( + name="plane", fqn_prefix=QUOTIENT_PREFIX, expected_pattern=None, profile="python", + drift_tolerance=1.0, enforce=False, + ) # fmt: skip by_name = {d.name: d for d in _selected_domains(scorer, graph="python")} nodes, edges = store.get_all_nodes(), store.get_all_edges() for plane, (names, template) in _PLANES.items(): diff --git a/tests/unit/test_patterns_yaml.py b/tests/unit/test_patterns_yaml.py index 460c984f..b65e6fb9 100644 --- a/tests/unit/test_patterns_yaml.py +++ b/tests/unit/test_patterns_yaml.py @@ -198,15 +198,14 @@ def test_profiles_declare_layers_summing_to_one() -> None: assert abs(sum(layers.values()) - 1.0) < 1e-9, name -def test_project_level_binding_observe_only() -> None: - """project_level exists, matches the quotient prefix, and is enforce: false.""" - data = _load() - bindings = data["project_level"] - assert len(bindings) == 1 - b = bindings[0] - assert b["fqn_prefix"] == "quotient" - assert b["enforce"] is False - assert b["expected_pattern"] in data["patterns"] +def test_this_repository_binds_no_project_level_quotient() -> None: + """Retired in language-packs step 6 (#146). + + The enforced topology gate is the forbidden-edge test in test_architecture.py; + plane quotients are reported, not bound. The feature itself stays for other + repositories' patterns.yaml. + """ + assert "project_level" not in _load() # --------------------------------------------------------------------------- From 4a7100bea3e97acc5c42366a15ef59740e4b2f57 Mon Sep 17 00:00:00 2001 From: zaebee Date: Wed, 14 Oct 2026 20:39:12 +0000 Subject: [PATCH 3/5] chore(ontology): one drift domain per language pack, and core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `extraction` (one domain over every pack) becomes languages.python and languages.typescript, both layered_dag at one shared tolerance, and core gets a domain. All three are new domains, set at their measurement plus headroom (main ca32430, fresh ingest of src/): | domain | layered_dag | best fit | unresolved | | languages.python | 0.393 | funnel 0.328 | 0.356 | | languages.typescript | 0.184 | layered_dag | 0.295 | | core | 0.215 | layered_dag (pure_utility 0.554) | 0.191 | Rulings (user-decided at plan review): G1 core is layered_dag 0.25, not the spec's pure_utility — since steps 1-4 the pack contract in core is layered. G2 the packs share 0.42, the loosest pack plus headroom, so a new pack faces the same bar; tests/unit/test_patterns_yaml.py fails for a registered pack with no domain or a different tolerance (both mutation-checked). The build plane's quotient now lists the packs: drift 0.236 -> 0.016, the pipeline-centred star. Co-Authored-By: Claude Opus 5.5 --- docs/ontology/patterns.yaml | 45 +++++++++++++++++++++----------- docs/ontology/tolerances.lock | 4 ++- tests/self_parsing/test_drift.py | 5 +++- tests/unit/test_patterns_yaml.py | 25 ++++++++++++++++++ 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/docs/ontology/patterns.yaml b/docs/ontology/patterns.yaml index fb739091..48ef6f84 100644 --- a/docs/ontology/patterns.yaml +++ b/docs/ontology/patterns.yaml @@ -114,24 +114,39 @@ patterns: # measurement (see plan: docs/specs/plans/2026-06-10-motif-basis-part-b.md). project_domains: # Python graph (ingest root: src/ → FQNs carry the cgis. prefix) - # Re-pointed in language-packs step 1: the extractors moved into cgis.languages.; - # the contract (BaseExtractor) moved to cgis.core. - - name: "extraction" - fqn_prefix: "cgis.languages" + # Language packs (spec: Drift gates, Domains) — one domain per pack, one shared + # bar (ruling G2), so a new pack is measured against it from its first commit; + # tests/unit/test_patterns_yaml.py fails for a pack without one. + # Was the single "extraction" domain over cgis.extractors / cgis.languages + # (#116, #183, steps 1-5). Measured at step 6 (main ca32430): python layered_dag + # 0.393 (best fit funnel 0.328), typescript layered_dag 0.184. Shared + # tolerance = loosest + headroom. + - name: "languages.python" + fqn_prefix: "cgis.languages.python" expected_pattern: layered_dag profile: python - # Re-negotiated in #116: PythonExtractor was decomposed from a God Object into a - # layered package (orchestrator python_extractor → handlers → _python_ast core), - # so the domain is no longer pure_utility. Best-fit is now layered_dag (same - # pattern as cgis.query). - # Re-baselined in #183 (≈0.32 → 0.37): the resolver stopped fabricating phantom - # `module.list.append`-style internal FQNs for builtin-method calls (e.g. `edges.append` - # where `edges: list[Edge]`). Those fake intra-domain CALLS edges had been artificially - # deflating this domain's drift; 0.37 is the honest census of the real structure (no real - # edges lost — verified by edge-diff). Correctness-driven re-baseline, not ratchet loosening. - drift_tolerance: 0.40 + drift_tolerance: 0.42 hygiene_baseline: - unresolved_ratio: 0.37 # measured 0.3604 when the gate came alive (#149) + unresolved_ratio: 0.37 # measured 0.356 at step 6 (the old extraction baseline) + + - name: "languages.typescript" + fqn_prefix: "cgis.languages.typescript" + expected_pattern: layered_dag + profile: python + drift_tolerance: 0.42 + hygiene_baseline: + unresolved_ratio: 0.31 # measured 0.295 at step 6 + + # The pack contract and the model (spec: Core). layered_dag, not the spec's + # pure_utility — measured 0.215 vs 0.554 at step 6: since steps 1-4 the contract + # is layered (extractor/index/resolution/project -> models) (ruling G1). + - name: "core" + fqn_prefix: "cgis.core" + expected_pattern: layered_dag + profile: python + drift_tolerance: 0.25 + hygiene_baseline: + unresolved_ratio: 0.21 # measured 0.191 at step 6 - name: "resolution" fqn_prefix: "cgis.resolver" diff --git a/docs/ontology/tolerances.lock b/docs/ontology/tolerances.lock index fcd3ed6c..d27253a4 100644 --- a/docs/ontology/tolerances.lock +++ b/docs/ontology/tolerances.lock @@ -11,7 +11,9 @@ # update the lock and justify it in the PR description. project_domains: - extraction: 0.40 + languages.python: 0.42 + languages.typescript: 0.42 + core: 0.25 resolution: 0.40 pipeline: 0.25 storage: 0.20 diff --git a/tests/self_parsing/test_drift.py b/tests/self_parsing/test_drift.py index 09d812d8..53f33f26 100644 --- a/tests/self_parsing/test_drift.py +++ b/tests/self_parsing/test_drift.py @@ -131,7 +131,10 @@ def test_ts_self_drift_within_tolerance( _PLANES = { - "build": (("pipeline", "extraction", "resolution", "storage"), "orchestrator"), + "build": ( + ("pipeline", "languages.python", "languages.typescript", "resolution", "storage"), + "orchestrator", + ), "read": (("storage", "query", "guardian", "cli", "api"), "layered_dag"), } diff --git a/tests/unit/test_patterns_yaml.py b/tests/unit/test_patterns_yaml.py index b65e6fb9..32fe8535 100644 --- a/tests/unit/test_patterns_yaml.py +++ b/tests/unit/test_patterns_yaml.py @@ -4,6 +4,8 @@ import yaml +from cgis.languages import MANIFESTS + PATTERNS_PATH = Path(__file__).parent.parent.parent / "docs" / "ontology" / "patterns.yaml" PATTERNS_UI_PATH = PATTERNS_PATH.parent / "patterns_ui.yaml" @@ -285,3 +287,26 @@ def test_tolerances_never_exceed_the_lock() -> None: + f"\nIf this is a deliberate re-baseline, update {TOLERANCES_LOCK_PATH.name} " "and justify it in the PR description." ) + + +#: One bar for every language pack (spec: Drift gates, Domains) — set by the +#: loosest pack at step 6 (ruling G2); a new pack meets it or the suite says so. +_PACK_PATTERN = "layered_dag" + + +def test_every_pack_has_a_domain_at_the_shared_tolerance() -> None: + domains = {d["name"]: d for d in _load()["project_domains"]} + packs = {m.name: domains.get(f"languages.{m.name}") for m in MANIFESTS} + missing = sorted(name for name, domain in packs.items() if domain is None) + assert not missing, f"packs without a drift domain in patterns.yaml: {missing}" + tolerances = {name: domain["drift_tolerance"] for name, domain in packs.items()} + assert len(set(tolerances.values())) == 1, f"pack tolerances must be shared: {tolerances}" + for name, domain in packs.items(): + assert domain["fqn_prefix"] == f"cgis.languages.{name}" + assert domain["expected_pattern"] == _PACK_PATTERN + assert domain["profile"] == "python" + + +def test_core_has_a_domain() -> None: + domains = {d["name"]: d for d in _load()["project_domains"]} + assert domains["core"]["fqn_prefix"] == "cgis.core" From 6e8473ce7d55332e837a613f33f2b441a92c3a47 Mon Sep 17 00:00:00 2001 From: zaebee Date: Wed, 14 Oct 2026 20:39:34 +0000 Subject: [PATCH 4/5] docs: step 6 implementation note Co-Authored-By: Claude Opus 5.5 --- docs/specs/2026-10-14-language-packs-design.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/specs/2026-10-14-language-packs-design.md b/docs/specs/2026-10-14-language-packs-design.md index ceae86a5..ffa410d0 100644 --- a/docs/specs/2026-10-14-language-packs-design.md +++ b/docs/specs/2026-10-14-language-packs-design.md @@ -352,6 +352,16 @@ forbidden-edge gate. review. `resolution` is re-measured after uplift leaves it. `core` gets `pure_utility`. The other domains are unchanged. +> **Implementation note (step 6):** this repository's `project_level` binding is +> retired (the feature stays for other repositories); the plane report builds +> its own binding from `QUOTIENT_PREFIX`. The packs share `layered_dag` at +> **0.42** — the loosest pack plus headroom: measured python 0.393 (its best fit +> is `funnel`, 0.328), typescript 0.184 — and `tests/unit/test_patterns_yaml.py` +> fails for a registered pack with no domain or a different tolerance. `core` is +> `layered_dag` at 0.25, not `pure_utility`: it measured 0.215 against 0.554, +> because since steps 1–4 it holds the layered pack contract. With the packs as +> quotient nodes the build plane reads 0.016 against `orchestrator` (was 0.236). + ## Migration **Safety net, written first.** The input must stay fixed while the code From f2a53a5920d7f7e0a3d1155ad50e6acc1f9bdbdc Mon Sep 17 00:00:00 2001 From: zaebee Date: Wed, 14 Oct 2026 20:46:44 +0000 Subject: [PATCH 5/5] fix(drift): the build plane counts the registry; isolated plane nodes fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 6's build-plane report read 0.016 against orchestrator — not an improvement but an artifact: `cgis.languages` (the registry) belongs to no drift domain, every build-plane edge into the languages area targets it, and the two pack nodes sat isolated in the quotient (final review, Important). The report now adds the registry as a plane-local node (longest-prefix match keeps the packs to their own domains) and fails when any plane node has no edge — the check that would have caught it. The build plane reads 0.351 (main 0.236, when `extraction` was one node): pipeline and resolver reach the packs only through the registry. Spec note corrected. Also pins core's layered_dag (ruling G1) in test_core_has_a_domain. Co-Authored-By: Claude Opus 5.5 --- .../specs/2026-10-14-language-packs-design.md | 10 +++++++-- tests/self_parsing/test_drift.py | 21 ++++++++++++++++++- tests/unit/test_patterns_yaml.py | 1 + 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/specs/2026-10-14-language-packs-design.md b/docs/specs/2026-10-14-language-packs-design.md index ffa410d0..c76feb20 100644 --- a/docs/specs/2026-10-14-language-packs-design.md +++ b/docs/specs/2026-10-14-language-packs-design.md @@ -359,8 +359,14 @@ review. `resolution` is re-measured after uplift leaves it. `core` gets > is `funnel`, 0.328), typescript 0.184 — and `tests/unit/test_patterns_yaml.py` > fails for a registered pack with no domain or a different tolerance. `core` is > `layered_dag` at 0.25, not `pure_utility`: it measured 0.215 against 0.554, -> because since steps 1–4 it holds the layered pack contract. With the packs as -> quotient nodes the build plane reads 0.016 against `orchestrator` (was 0.236). +> because since steps 1–4 it holds the layered pack contract. The registry module +> `cgis.languages` belongs to no drift domain — a domain on its prefix would take +> the packs too — so the build-plane report adds it as a plane-local node; without +> it the packs sat isolated and the plane read a false 0.016. With it the build +> plane reads 0.351 against `orchestrator` (main 0.236, when `extraction` was one +> node): pipeline and resolver reach the packs only through the registry, a +> chain the star template penalises. The report now fails if a plane node has +> no edge. Observe-only, as before. ## Migration diff --git a/tests/self_parsing/test_drift.py b/tests/self_parsing/test_drift.py index 53f33f26..413e64f8 100644 --- a/tests/self_parsing/test_drift.py +++ b/tests/self_parsing/test_drift.py @@ -132,7 +132,14 @@ def test_ts_self_drift_within_tolerance( _PLANES = { "build": ( - ("pipeline", "languages.python", "languages.typescript", "resolution", "storage"), + ( + "pipeline", + "languages", + "languages.python", + "languages.typescript", + "resolution", + "storage", + ), "orchestrator", ), "read": (("storage", "query", "guardian", "cli", "api"), "layered_dag"), @@ -156,6 +163,12 @@ def test_plane_quotients_observed_not_enforced( drift_tolerance=1.0, enforce=False, ) # fmt: skip by_name = {d.name: d for d in _selected_domains(scorer, graph="python")} + # The registry module `cgis.languages` is in no drift domain (a domain on its + # prefix would take the packs too); for the plane it is a node of its own, the + # longest-prefix match leaving it only the registry's nodes. + by_name["languages"] = DomainConfig( + name="languages", fqn_prefix="cgis.languages", expected_pattern=None + ) nodes, edges = store.get_all_nodes(), store.get_all_edges() for plane, (names, template) in _PLANES.items(): domains = [by_name[n] for n in names] @@ -165,5 +178,11 @@ def test_plane_quotients_observed_not_enforced( ) fingerprint = extractor.extract(binding.fqn_prefix) assert fingerprint.node_count == len(domains), f"{plane}: a domain matched nothing" + # A domain with no edge in its plane is a hole, not a better score: step 6 + # left the packs isolated (their only link, the registry, was in no domain) + # and the build plane read 0.016 instead of an honest number. + linked = {e.source for e in qedges} | {e.target for e in qedges} + isolated = sorted(n.id for n in qnodes if n.id not in linked) + assert not isolated, f"{plane}: quotient nodes with no edge: {isolated}" report = scorer.score(fingerprint, dataclasses.replace(binding, expected_pattern=template)) print(f"\n[plane observe-only] {plane} vs {template}: drift={report.drift_score:.3f}") diff --git a/tests/unit/test_patterns_yaml.py b/tests/unit/test_patterns_yaml.py index 32fe8535..a739086e 100644 --- a/tests/unit/test_patterns_yaml.py +++ b/tests/unit/test_patterns_yaml.py @@ -310,3 +310,4 @@ def test_every_pack_has_a_domain_at_the_shared_tolerance() -> None: def test_core_has_a_domain() -> None: domains = {d["name"]: d for d in _load()["project_domains"]} assert domains["core"]["fqn_prefix"] == "cgis.core" + assert domains["core"]["expected_pattern"] == "layered_dag" # ruling G1, not pure_utility