From a39a8244b8fe00694d54b6690b1e9ac646a3eb2d Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:02:42 -0400 Subject: [PATCH 1/2] feat(vocab): LIVE dogfood of the currency loop on the estate's own vocab (task #13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools/dogfood_vocab_currency.py runs the governed loop over THIS repo's real vocabulary — every approved GlossaryTerm in examples/ (fixed/LSA) vs specs/*.md (open/LDA) — answering honestly whether our approved vocab is current with our specs. It is not: 2 approved terms vs 14 specs, divergence ~0.98, and the governed loop escalates-human (can't reach currency in its 8-iteration bound) with concrete draft proposals rather than pretending currency. Writes each proposed draft GlossaryTerm to build/vocab-currency-proposals/.json (gitignored) — the artifacts ontogenesis ingests. Informational (make vocab-currency-dogfood, exit 0, NOT in the validate gate); corpus is Markdown-cleaned + generic-prose stoplisted so domain terms surface (contract/runtime/schema/ policy/device/mpcc/slot). Enforcement stays on fixtures in validate-vocab-currency-loop. --- .gitignore | 1 + CHANGELOG.md | 1 + Makefile | 8 ++- specs/vocab-currency-loop.md | 12 ++++ tools/dogfood_vocab_currency.py | 111 ++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 tools/dogfood_vocab_currency.py diff --git a/.gitignore b/.gitignore index 8277c39..a7ed681 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ build/ # GitHub API artifacts accidentally fetched locally pulls/ +build/vocab-currency-proposals/ diff --git a/CHANGELOG.md b/CHANGELOG.md index dd78142..be66f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Thi ## [Unreleased] ### Added +- Vocab-currency loop — LIVE dogfood on the estate's own vocab (`make vocab-currency-dogfood`): `tools/dogfood_vocab_currency.py` runs the governed loop over this repo's approved `GlossaryTerm`s (fixed/LSA) vs `specs/*.md` (open/LDA). On today's estate (2 approved terms vs 14 specs) divergence starts ~0.98 and the governed loop escalates-human (can't reach currency in its bound), writing real draft `GlossaryTerm` proposals to `build/vocab-currency-proposals/` — the artifacts ontogenesis ingests. Informational (exit 0); Markdown-cleaned + generic-word stoplisted so domain terms surface. Enforcement stays on fixtures in `validate-vocab-currency-loop`. - Vocab-currency loop — draft-term emission (closes the currency→proposal lifecycle): each remediation step now emits a conformant status:**draft** `GlossaryTerm` artifact (`result.proposedTerms`) for the token it connects — the loop PROPOSES drafts for ontogenesis's 3-method alignment pass and never self-approves (`partOfSpeech`/`alignment` are left for downstream; status stays `draft`). The CI harness now REFUSES any proposed term that doesn't conform to `GlossaryTerm.json` or that isn't status:draft, so the loop can't push malformed or self-approved vocab into the glossary. 6 teeth total. - Vocab-currency loop v0.1 — the vocabulary-currency GovernedLoop made LIVE (task #13): `tools/vocab_currency_loop.py` instantiates the merged `GovernedLoop` contract and is governed BY it (reads bound + tolerance + `onNonConvergence` + admission from the document). The approved glossary is the FIXED set (LSA), the corpus token distribution is the OPEN set (LDA), and uncovered probability mass is the currency signal (the LSI drive: new fields that became vocab but aren't connected). Remediation connects the highest-mass uncovered term (a proposed `GlossaryTerm`), strictly decreasing divergence. Fail-closed: runs at most `maxIterations` (never spins); escalates-human (non-zero exit) if it can't reach tolerance within the bound rather than declaring currency; refuses to run unadmitted (no `superconsciousRef`). `validate-vocab-currency-loop` asserts all three teeth (converges / escalates / refuses). Emits `candidateNewVocab` for routing into ontogenesis/prophet-ontology. Dependency-free. - DataClass v0.1 (OntoDT/OntoDQ data-side governance, task #14; AMG 2.0 deck): a `DataClass` binds a datatype-ontology class (OntoDT), the business `GlossaryTerm` (biz↔data), and a `ValidValues` domain (enum/range/regex), with an optional **TF-Lattice wide-and-deep** classifier that is a first-class CATALOGED model — `modelRef`→`ModelManifest`, `runRef`→`RunRecord`, `compute` on **Ray/TritFabric**, `monotonicFeatures` lattice constraint, and `labels` that are `GlossaryTerm` URNs (assigned in the glossary). `EntityField` gains an optional `dataClassRef`; the drift-guard enforces field↔class domain conformance + classifier integrity (labels are glossary terms, model is cataloged, compute is ray/tritfabric) + ontological typing. 4 negative vectors, teeth-verified. Backward-compatible. Two-level classifier: per-class LOGISTIC head (`DataClass.classifier`, one-vs-rest, required `evalRunRef` — individually testable per class/glossary term) + a per-table SOFTMAX `TableClassifier` aligning an LSA bag-of-words embedding AND a doc2vec sentence-encoder (the n-ary logit→class assignment), a cataloged model on Ray/TritFabric assigning N DataClasses. Drift-guard enforces both. diff --git a/Makefile b/Makefile index 8873845..5843511 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: validate validate-resource-contract validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-ingestion-pipeline-examples validate-data-acquisition-examples validate-glossary-alignment-examples validate-data-class-examples validate-table-keys validate-dag-loop validate-vocab-currency-loop +.PHONY: validate validate-resource-contract validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-ingestion-pipeline-examples validate-data-acquisition-examples validate-glossary-alignment-examples validate-data-class-examples validate-table-keys validate-dag-loop validate-vocab-currency-loop vocab-currency-dogfood validate: validate-data-class-examples validate-glossary-alignment-examples validate-data-acquisition-examples validate-ingestion-pipeline-examples validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-resource-contract validate-table-keys validate-dag-loop validate-vocab-currency-loop @echo "OK: validate" @@ -23,6 +23,12 @@ validate-vocab-currency-loop: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_vocab_currency_loop.py +# On-demand LIVE currency scan of THIS repo's approved glossary vs specs/*.md. +# Informational (always exit 0) — writes draft proposals to build/vocab-currency-proposals/. +vocab-currency-dogfood: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/dogfood_vocab_currency.py + validate-dag-loop: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_dag_loop.py diff --git a/specs/vocab-currency-loop.md b/specs/vocab-currency-loop.md index bfce254..46b5754 100644 --- a/specs/vocab-currency-loop.md +++ b/specs/vocab-currency-loop.md @@ -28,3 +28,15 @@ ontogenesis / the prophet-ontology as draft terms for the 3-method alignment pas ## Remediation artifacts Each connected token is emitted as a conformant status:`draft` `GlossaryTerm` in `result.proposedTerms` — the loop's output is directly ingestible by ontogenesis for the 3-method alignment pass. The loop only proposes: `partOfSpeech`, `alignment`, and approval are downstream. `make validate-vocab-currency-loop` refuses any proposed term that fails `GlossaryTerm.json` or that is not `draft` (the loop may not self-approve). + +## Live dogfood (`make vocab-currency-dogfood`) + +`tools/dogfood_vocab_currency.py` runs the governed loop over THIS repo's real vocabulary — every +approved `GlossaryTerm` in `examples/` (fixed/LSA) vs `specs/*.md` (open/LDA) — answering honestly +whether our own approved vocab is current with our own specs. On today's estate (2 approved terms +vs 14 specs) it is not: divergence starts ~0.98 and the governed loop, unable to reach currency in +its 8-iteration bound, **escalates-human** with concrete draft proposals rather than pretending +currency. It writes each proposed draft `GlossaryTerm` to `build/vocab-currency-proposals/.json` +— the exact artifacts ontogenesis ingests. Informational (exit 0), corpus-configured (Markdown +cleaned, generic prose words stoplisted so DOMAIN terms surface); the ENFORCEMENT stays in +`validate-vocab-currency-loop` on known-good fixtures. diff --git a/tools/dogfood_vocab_currency.py b/tools/dogfood_vocab_currency.py new file mode 100644 index 0000000..ec01f73 --- /dev/null +++ b/tools/dogfood_vocab_currency.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Dogfood the vocab-currency GovernedLoop on THIS repo's own vocabulary (task #13). + +Runs the governed loop over the estate's real material — every APPROVED GlossaryTerm in +`examples/` is the fixed set (LSA); `specs/*.md` is the corpus (LDA) — to answer, honestly: is +our own approved vocabulary current with our own specifications? On real incomplete vocab the +governed loop does exactly what it should: it connects what it can within the bound, then +ESCALATES-human with concrete proposals rather than pretending currency. + +This is a REPORT, not a gate (it always exits 0): the estate vocab is legitimately incomplete +today, so failing CI on it would be wrong. It writes each proposed draft `GlossaryTerm` to +`build/vocab-currency-proposals/.json` — those are the artifacts ontogenesis ingests for +the 3-method alignment pass. The ENFORCEMENT lives in `validate_vocab_currency_loop.py` (synthetic +fixtures with a known-good outcome); this shows the same loop on live data. +""" +from __future__ import annotations + +import glob +import json +import re +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import vocab_currency_loop as vcl # noqa: E402 + +ROOT = Path(__file__).resolve().parents[1] +OUT = ROOT / "build" / "vocab-currency-proposals" +LOOP = ROOT / "examples" / "governed_loop.vocab_currency.json" + +# Real prose carries generic words that aren't estate vocabulary. The core loop stays general; +# for a live scan of Markdown specs we extend the tokenizer's stoplist here so the surfaced +# candidates are DOMAIN terms (contract, runtime, attestation) rather than filler (one, every, +# same). This is corpus configuration, not a change to the loop's semantics. +GENERIC = { + "one", "two", "three", "every", "only", "same", "may", "what", "each", "also", "both", + "here", "there", "when", "where", "which", "these", "those", "such", "more", "most", + "some", "other", "another", "using", "used", "use", "via", "per", "yet", "still", "now", + "does", "done", "make", "makes", "made", "way", "ways", "like", "just", "even", "them", + "they", "you", "your", "who", "how", "why", "but", "all", "not", "has", "had", "have", +} + + +def clean_markdown(text: str) -> str: + text = re.sub(r"```.*?```", " ", text, flags=re.S) # fenced code + text = re.sub(r"`[^`]*`", " ", text) # inline code + text = re.sub(r"https?://\S+", " ", text) # urls + text = re.sub(r"[#>*\-_|]", " ", text) # md punctuation + return text + + +def real_glossary() -> dict: + """Every GlossaryTerm found in examples/ (bundle `terms` arrays + standalone term docs), + deduped by id. run_loop counts only status==approved as coverage.""" + by_id: dict[str, dict] = {} + for f in sorted(glob.glob(str(ROOT / "examples" / "*.json"))): + try: + doc = json.loads(Path(f).read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + continue + items = doc["terms"] if isinstance(doc, dict) and isinstance(doc.get("terms"), list) else [doc] + for it in items: + if isinstance(it, dict) and str(it.get("id", "")).startswith("urn:srcos:glossary:"): + by_id[it["id"]] = it + return {"terms": list(by_id.values())} + + +def real_corpus() -> dict: + docs = [] + for f in sorted(glob.glob(str(ROOT / "specs" / "*.md"))): + docs.append({"id": Path(f).stem, "text": clean_markdown(Path(f).read_text(encoding="utf-8"))}) + return {"documents": docs} + + +def main() -> int: + vcl.STOP |= GENERIC # configure the tokenizer for prose (domain terms surface, not filler) + glossary = real_glossary() + corpus = real_corpus() + approved = [t["id"] for t in glossary["terms"] if t.get("status") == "approved"] + + loop = vcl.load(LOOP) + result = vcl.run_loop(loop, glossary, corpus) + + # Persist the proposed draft terms — the real artifacts for ontogenesis ingestion. + OUT.mkdir(parents=True, exist_ok=True) + written = [] + for term in result.get("proposedTerms", []): + slug = term["id"].split(":")[-1] + (OUT / f"{slug}.json").write_text(json.dumps(term, indent=2) + "\n", encoding="utf-8") + written.append(slug) + + print(json.dumps({ + "scan": "vocab-currency dogfood (specs/*.md vs approved glossary)", + "corpusDocs": len(corpus["documents"]), + "approvedTerms": approved, + "governedBy": result.get("admittedBy"), + "initialDivergence": result.get("trace", [{}])[0].get("divergence"), + "finalDivergence": result.get("finalDivergence"), + "iterations": result.get("iterations"), + "maxIterations": result.get("maxIterations"), + "outcome": ("current" if result.get("ok") else result.get("escalated") or result.get("refused")), + "detail": result.get("detail"), + "proposalsWritten": written, + "topRemainingCandidates": [c["term"] for c in result.get("candidateNewVocab", [])], + }, indent=2)) + # Always exit 0 — this is an informational currency scan of live vocab, not a gate. + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From be981e5f74a86406d1652726dbf7f44d94c9a2b0 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:09:14 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(vocab):=20remediate=20Copilot=20review?= =?UTF-8?q?=20on=20#257=20=E2=80=94=20robustness=20+=20de-hardcode=20metri?= =?UTF-8?q?cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - real_corpus() now skips an unreadable/undecodable spec with a warning instead of crashing. - real_glossary() requires type==GlossaryTerm (or untyped bundle entry), so a doc merely carrying a glossary-namespaced id but typed otherwise isn't miscounted as a term. - reworded the 'always exits 0' claim to its honest meaning: it doesn't gate on the currency OUTCOME (exit 0 whether current or escalated); a genuine IO error still surfaces. - softened volatile hard-coded metrics (term/spec counts, ~0.98 divergence) in the spec + CHANGELOG to qualitative statements marked point-in-time. --- CHANGELOG.md | 2 +- specs/vocab-currency-loop.md | 9 +++++---- tools/dogfood_vocab_currency.py | 21 ++++++++++++++++----- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be66f9c..946a586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Thi ## [Unreleased] ### Added -- Vocab-currency loop — LIVE dogfood on the estate's own vocab (`make vocab-currency-dogfood`): `tools/dogfood_vocab_currency.py` runs the governed loop over this repo's approved `GlossaryTerm`s (fixed/LSA) vs `specs/*.md` (open/LDA). On today's estate (2 approved terms vs 14 specs) divergence starts ~0.98 and the governed loop escalates-human (can't reach currency in its bound), writing real draft `GlossaryTerm` proposals to `build/vocab-currency-proposals/` — the artifacts ontogenesis ingests. Informational (exit 0); Markdown-cleaned + generic-word stoplisted so domain terms surface. Enforcement stays on fixtures in `validate-vocab-currency-loop`. +- Vocab-currency loop — LIVE dogfood on the estate's own vocab (`make vocab-currency-dogfood`): `tools/dogfood_vocab_currency.py` runs the governed loop over this repo's approved `GlossaryTerm`s (fixed/LSA) vs `specs/*.md` (open/LDA). Today only a handful of terms are approved against the full spec corpus, so the governed loop escalates-human (can't reach currency in its bound), writing real draft `GlossaryTerm` proposals to `build/vocab-currency-proposals/` — the artifacts ontogenesis ingests. Reports the outcome (does not gate on it, exit 0); Markdown-cleaned + generic-word stoplisted so domain terms surface. Enforcement stays on fixtures in `validate-vocab-currency-loop`. - Vocab-currency loop — draft-term emission (closes the currency→proposal lifecycle): each remediation step now emits a conformant status:**draft** `GlossaryTerm` artifact (`result.proposedTerms`) for the token it connects — the loop PROPOSES drafts for ontogenesis's 3-method alignment pass and never self-approves (`partOfSpeech`/`alignment` are left for downstream; status stays `draft`). The CI harness now REFUSES any proposed term that doesn't conform to `GlossaryTerm.json` or that isn't status:draft, so the loop can't push malformed or self-approved vocab into the glossary. 6 teeth total. - Vocab-currency loop v0.1 — the vocabulary-currency GovernedLoop made LIVE (task #13): `tools/vocab_currency_loop.py` instantiates the merged `GovernedLoop` contract and is governed BY it (reads bound + tolerance + `onNonConvergence` + admission from the document). The approved glossary is the FIXED set (LSA), the corpus token distribution is the OPEN set (LDA), and uncovered probability mass is the currency signal (the LSI drive: new fields that became vocab but aren't connected). Remediation connects the highest-mass uncovered term (a proposed `GlossaryTerm`), strictly decreasing divergence. Fail-closed: runs at most `maxIterations` (never spins); escalates-human (non-zero exit) if it can't reach tolerance within the bound rather than declaring currency; refuses to run unadmitted (no `superconsciousRef`). `validate-vocab-currency-loop` asserts all three teeth (converges / escalates / refuses). Emits `candidateNewVocab` for routing into ontogenesis/prophet-ontology. Dependency-free. - DataClass v0.1 (OntoDT/OntoDQ data-side governance, task #14; AMG 2.0 deck): a `DataClass` binds a datatype-ontology class (OntoDT), the business `GlossaryTerm` (biz↔data), and a `ValidValues` domain (enum/range/regex), with an optional **TF-Lattice wide-and-deep** classifier that is a first-class CATALOGED model — `modelRef`→`ModelManifest`, `runRef`→`RunRecord`, `compute` on **Ray/TritFabric**, `monotonicFeatures` lattice constraint, and `labels` that are `GlossaryTerm` URNs (assigned in the glossary). `EntityField` gains an optional `dataClassRef`; the drift-guard enforces field↔class domain conformance + classifier integrity (labels are glossary terms, model is cataloged, compute is ray/tritfabric) + ontological typing. 4 negative vectors, teeth-verified. Backward-compatible. Two-level classifier: per-class LOGISTIC head (`DataClass.classifier`, one-vs-rest, required `evalRunRef` — individually testable per class/glossary term) + a per-table SOFTMAX `TableClassifier` aligning an LSA bag-of-words embedding AND a doc2vec sentence-encoder (the n-ary logit→class assignment), a cataloged model on Ray/TritFabric assigning N DataClasses. Drift-guard enforces both. diff --git a/specs/vocab-currency-loop.md b/specs/vocab-currency-loop.md index 46b5754..f9b9e69 100644 --- a/specs/vocab-currency-loop.md +++ b/specs/vocab-currency-loop.md @@ -33,10 +33,11 @@ Each connected token is emitted as a conformant status:`draft` `GlossaryTerm` in `tools/dogfood_vocab_currency.py` runs the governed loop over THIS repo's real vocabulary — every approved `GlossaryTerm` in `examples/` (fixed/LSA) vs `specs/*.md` (open/LDA) — answering honestly -whether our own approved vocab is current with our own specs. On today's estate (2 approved terms -vs 14 specs) it is not: divergence starts ~0.98 and the governed loop, unable to reach currency in -its 8-iteration bound, **escalates-human** with concrete draft proposals rather than pretending -currency. It writes each proposed draft `GlossaryTerm` to `build/vocab-currency-proposals/.json` +whether our own approved vocab is current with our own specs. Today it is not — only a handful of +terms are approved against the full spec corpus, so divergence starts high and the governed loop, +unable to reach currency within its iteration bound, **escalates-human** with concrete draft +proposals rather than pretending currency. (Exact counts/divergence are point-in-time; run it to +see the current numbers.) It writes each proposed draft `GlossaryTerm` to `build/vocab-currency-proposals/.json` — the exact artifacts ontogenesis ingests. Informational (exit 0), corpus-configured (Markdown cleaned, generic prose words stoplisted so DOMAIN terms surface); the ENFORCEMENT stays in `validate-vocab-currency-loop` on known-good fixtures. diff --git a/tools/dogfood_vocab_currency.py b/tools/dogfood_vocab_currency.py index ec01f73..3ebe91d 100644 --- a/tools/dogfood_vocab_currency.py +++ b/tools/dogfood_vocab_currency.py @@ -7,8 +7,10 @@ governed loop does exactly what it should: it connects what it can within the bound, then ESCALATES-human with concrete proposals rather than pretending currency. -This is a REPORT, not a gate (it always exits 0): the estate vocab is legitimately incomplete -today, so failing CI on it would be wrong. It writes each proposed draft `GlossaryTerm` to +This is a REPORT, not a gate: it does not fail on the currency OUTCOME (the estate vocab is +legitimately incomplete today, so failing CI on a non-current result would be wrong) — it exits 0 +whether the loop reaches currency or escalates. (A genuine IO error still surfaces normally.) +It writes each proposed draft `GlossaryTerm` to `build/vocab-currency-proposals/.json` — those are the artifacts ontogenesis ingests for the 3-method alignment pass. The ENFORCEMENT lives in `validate_vocab_currency_loop.py` (synthetic fixtures with a known-good outcome); this shows the same loop on live data. @@ -60,7 +62,10 @@ def real_glossary() -> dict: continue items = doc["terms"] if isinstance(doc, dict) and isinstance(doc.get("terms"), list) else [doc] for it in items: - if isinstance(it, dict) and str(it.get("id", "")).startswith("urn:srcos:glossary:"): + # A glossary-namespaced id AND (untyped bundle entry OR explicitly a GlossaryTerm) — + # so a doc merely carrying such an id but typed as something else isn't miscounted. + if (isinstance(it, dict) and str(it.get("id", "")).startswith("urn:srcos:glossary:") + and it.get("type") in (None, "GlossaryTerm")): by_id[it["id"]] = it return {"terms": list(by_id.values())} @@ -68,7 +73,12 @@ def real_glossary() -> dict: def real_corpus() -> dict: docs = [] for f in sorted(glob.glob(str(ROOT / "specs" / "*.md"))): - docs.append({"id": Path(f).stem, "text": clean_markdown(Path(f).read_text(encoding="utf-8"))}) + try: + text = Path(f).read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as exc: + print(f"skipping unreadable spec {f}: {exc}", file=sys.stderr) + continue + docs.append({"id": Path(f).stem, "text": clean_markdown(text)}) return {"documents": docs} @@ -103,7 +113,8 @@ def main() -> int: "proposalsWritten": written, "topRemainingCandidates": [c["term"] for c in result.get("candidateNewVocab", [])], }, indent=2)) - # Always exit 0 — this is an informational currency scan of live vocab, not a gate. + # Exit 0 regardless of the currency OUTCOME — this reports the state of live vocab, it does + # not gate on whether the vocab happens to be current (a genuine IO error still surfaces above). return 0