From 967d5f404a5cb44dc50ef9298fab9bae69522ead Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:50:53 -0400 Subject: [PATCH] =?UTF-8?q?feat(vocab):=20GlossaryTerm=20alignment=20+=20t?= =?UTF-8?q?yped=20relations=20=E2=80=94=20vocabulary=20as=20governance=20s?= =?UTF-8?q?ubstrate=20(task=20#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Language regulates state, so an approved term must be usable to reason about governance: CAPTURED (ontology class) + VECTOR-ALIGNED (NP↔VP link in the sovereign 768 space) + IMPLEMENTED (estate entity/service). Extends the existing GlossaryTerm (optional, backward- compatible) with partOfSpeech (NP/VP, Tesnière), status, a 3-method alignment block, and typed relations over a CLOSED RDF/RDFS/SKOS/FOAF predicate set (is-type/has-type, is-a, has-a, is-member/has-member, skos:broader/narrower/related, foaf:member, has-datatype). Fail-closed drift-guard (validate_glossary_alignment_examples.py): an approved term missing any alignment method is a governance hole; NP→entity, VP→action/service; vector links reciprocal; relation inverse-pairs reciprocate; skos:related symmetric; is-a subsumption ACYCLIC (so a reasoner can derive dependencies+constraints). Teeth-verified (approve-unaligned, broken reciprocity, broken symmetry, is-a cycle all fail). Dogfood: the draft example is SP-GATE-004's release_gates.* new vocab, unaligned. make validate ok; dup-$id 345. Follow-ups: LSA↔LDA currency detection; agreement test vs the blast-radius graph + neurosymbolic domain. --- CHANGELOG.md | 1 + Makefile | 8 +- examples/glossary_term.draft_unaligned.json | 14 ++ examples/glossary_term.np_entity.json | 32 ++++ examples/glossary_term.vp_action.json | 32 ++++ fixtures/glossary-alignment/conformance.json | 150 ++++++++++++++++ schemas/GlossaryTerm.json | 127 +++++++++++++- specs/glossary-alignment-contract.md | 30 ++++ tools/validate_glossary_alignment_examples.py | 165 ++++++++++++++++++ 9 files changed, 556 insertions(+), 3 deletions(-) create mode 100644 examples/glossary_term.draft_unaligned.json create mode 100644 examples/glossary_term.np_entity.json create mode 100644 examples/glossary_term.vp_action.json create mode 100644 fixtures/glossary-alignment/conformance.json create mode 100644 specs/glossary-alignment-contract.md create mode 100644 tools/validate_glossary_alignment_examples.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7b4584..cf8ee6f8 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 +- Glossary alignment v0.1 (vocabulary as governance substrate, task #13): `GlossaryTerm` extended with `partOfSpeech` (NP/VP, Tesnière), `status`, a 3-method `alignment` (formal ontology class + NP↔VP vector link in the sovereign 768 space + estate entity/service binding), and typed `relations` over a closed RDF/RDFS/SKOS/FOAF predicate set (is-type/has-type, is-a, has-a, is-member/has-member, skos:broader/narrower/related, foaf:member, has-datatype). Fail-closed drift-guard (`validate_glossary_alignment_examples.py`): an `approved` term must be captured+vector-aligned+implemented (NP→entity, VP→action/service) or it's a governance hole; vector links must be reciprocal; relation inverse-pairs reciprocate; `skos:related` is symmetric; `is-a` subsumption is acyclic. Teeth-verified. Backward-compatible (optional). Follow-ups: LSA↔LDA currency detection + agreement test vs the blast-radius graph + neurosymbolic domain. - Receipt **epistemicLevel** (SP-GATE-003, closes audit G3): `ReleaseReceipt` and `SyncCycleReceipt` may now carry `epistemicLevel` (`Proved`/`Speculative`/`Refuted`) + an `attestation` block — the MEET of attestation ∧ verification. Optional + backward-compatible; `tools/validate_sync_cycle_receipts.py` RECOMPUTES the meet and rejects a declared value that disagrees, so a receipt can never claim `Proved` while unsigned or after a failed check (absence of a signature returns Speculative, not Proved). Consumes the explicit `attestation.signed` from SP-GATE-002 (source-os #312) and the SyncCycleReceipt attestation from SP-GATE-001. Examples: verified+signed→Proved, verified-but-unsigned→Speculative, failed→Refuted, dry-run→Speculative. Teeth-verified. - Data-Acquisition **citable commons** (WO-COMMONS-2): a promoted platform `CatalogEntry` may carry `promotion.doi` and the approving `DataAcquisitionRequest` records the same `mintedDoi` — a citable DOI (Zenodo/DataCite-style) minted when the governance ∧ IP/legal meet approves promotion. Invariants (schema `allOf` + validator): a DOI may appear only on a `promoted` entry, `mintedDoi` only on an `approved` DAR, and where both are present they must be equal (minted once). Grounds the governed platform tier as a citable open commons. - Data-Acquisition governance family v0.1 (auto-catalogue + gated promotion): every doc/dataset/collection is auto-catalogued as a `CatalogEntry` at `user`/`project`/`chat` scope (new optional `scope`/`ownerRef`/`promotion` fields, backward-compatible; absent scope = legacy `platform`), visible only in its owner's catalogue view. Promotion to **platform** scope is authorized ONLY by an approved `DataAcquisitionRequest`, whose approval is the fail-closed **meet** of two independent gates — a governance review AND an IP/legal rights review (`state:approved` ⇔ both `approved`; enforced by schema `allOf` and RECOMPUTED by the validator in both directions, so neither a rubber-stamp nor a single-gate approval survives). Binding invariant: a `scope:platform` entry must carry `promotion.state:promoted` + a `darRef` resolving to an approved DAR that names it — no object reaches platform without an approved request. Includes user/platform CatalogEntry + approved/in-flight DAR examples, six negative vectors with named `failValidator`, `validate-data-acquisition-examples` target, and a normative spec note (`specs/data-acquisition-contract.md`). Intended reference implementation: `catalog-gateway`. diff --git a/Makefile b/Makefile index 1f0ffdef..46fc3792 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,16 @@ -.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 +.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: 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: 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 @echo "OK: validate" validate-ingestion-pipeline-examples: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_ingestion_pipeline_examples.py +validate-glossary-alignment-examples: + python3 -m pip install --user jsonschema >/dev/null + python3 tools/validate_glossary_alignment_examples.py + validate-data-acquisition-examples: python3 -m pip install --user jsonschema >/dev/null python3 tools/validate_data_acquisition_examples.py diff --git a/examples/glossary_term.draft_unaligned.json b/examples/glossary_term.draft_unaligned.json new file mode 100644 index 00000000..92a06f7e --- /dev/null +++ b/examples/glossary_term.draft_unaligned.json @@ -0,0 +1,14 @@ +{ + "id": "urn:srcos:glossary:hermetic-launch-manifest", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "hermetic launch manifest", + "definition": "New field on service manifests (release_gates.hermetic_launch_manifest); not yet captured into the ontology.", + "partOfSpeech": "noun-phrase", + "status": "draft", + "alignment": { + "ontologyClassRef": null, + "vectorLink": null, + "estateBinding": null + } +} diff --git a/examples/glossary_term.np_entity.json b/examples/glossary_term.np_entity.json new file mode 100644 index 00000000..1bfe9ed4 --- /dev/null +++ b/examples/glossary_term.np_entity.json @@ -0,0 +1,32 @@ +{ + "id": "urn:srcos:glossary:release-gate", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "release gate", + "definition": "A fail-closed check a service must pass to be release-ready.", + "partOfSpeech": "noun-phrase", + "status": "approved", + "alignment": { + "ontologyClassRef": "urn:srcos:ontology-class:governance.ReleaseGate", + "vectorLink": { + "peerRef": "urn:srcos:glossary:promote-to-platform", + "model": "nomic-ai/nomic-embed-text-v1.5", + "dimension": 768, + "cosine": 0.71 + }, + "estateBinding": { + "kind": "entity", + "ref": "urn:srcos:service-graph:release-gate-audit" + } + }, + "relations": [ + { + "predicate": "is-a", + "target": "urn:srcos:ontology-class:governance.Control" + }, + { + "predicate": "skos:related", + "target": "urn:srcos:glossary:promote-to-platform" + } + ] +} diff --git a/examples/glossary_term.vp_action.json b/examples/glossary_term.vp_action.json new file mode 100644 index 00000000..b9881f3e --- /dev/null +++ b/examples/glossary_term.vp_action.json @@ -0,0 +1,32 @@ +{ + "id": "urn:srcos:glossary:promote-to-platform", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "promote to platform", + "definition": "Move a catalogued object from user/project/chat scope to platform scope via an approved DAR.", + "partOfSpeech": "verb-phrase", + "status": "approved", + "alignment": { + "ontologyClassRef": "urn:srcos:ontology-class:governance.PromoteToPlatform", + "vectorLink": { + "peerRef": "urn:srcos:glossary:release-gate", + "model": "nomic-ai/nomic-embed-text-v1.5", + "dimension": 768, + "cosine": 0.71 + }, + "estateBinding": { + "kind": "action", + "ref": "urn:srcos:data-acquisition-request:promote" + } + }, + "relations": [ + { + "predicate": "is-a", + "target": "urn:srcos:ontology-class:governance.Action" + }, + { + "predicate": "skos:related", + "target": "urn:srcos:glossary:release-gate" + } + ] +} diff --git a/fixtures/glossary-alignment/conformance.json b/fixtures/glossary-alignment/conformance.json new file mode 100644 index 00000000..b7b9fc0a --- /dev/null +++ b/fixtures/glossary-alignment/conformance.json @@ -0,0 +1,150 @@ +{ + "description": "Negative vectors for GlossaryTerm alignment v0.1. Each MUST fail on its named failValidator; validate_glossary_alignment_examples.py enforces both, plus the fail-closed invariant that an APPROVED term is fully aligned across all three methods and NP↔entity / VP↔action.", + "cases": [ + { + "schema": "GlossaryTerm.json", + "reason": "vectorLink must be in the sovereign space — a truncated dimension (512) must fail (const).", + "failValidator": "const", + "document": { + "id": "urn:srcos:glossary:release-gate", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "release gate", + "definition": "A fail-closed check a service must pass to be release-ready.", + "partOfSpeech": "noun-phrase", + "status": "approved", + "alignment": { + "ontologyClassRef": "urn:srcos:ontology-class:governance.ReleaseGate", + "vectorLink": { + "peerRef": "urn:srcos:glossary:promote-to-platform", + "model": "nomic-ai/nomic-embed-text-v1.5", + "dimension": 512, + "cosine": 0.71 + }, + "estateBinding": { + "kind": "entity", + "ref": "urn:srcos:service-graph:release-gate-audit" + } + }, + "relations": [ + { + "predicate": "is-a", + "target": "urn:srcos:ontology-class:governance.Control" + }, + { + "predicate": "skos:related", + "target": "urn:srcos:glossary:promote-to-platform" + } + ] + } + }, + { + "schema": "GlossaryTerm.json", + "reason": "estateBinding.kind is closed (entity|service|action).", + "failValidator": "enum", + "document": { + "id": "urn:srcos:glossary:release-gate", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "release gate", + "definition": "A fail-closed check a service must pass to be release-ready.", + "partOfSpeech": "noun-phrase", + "status": "approved", + "alignment": { + "ontologyClassRef": "urn:srcos:ontology-class:governance.ReleaseGate", + "vectorLink": { + "peerRef": "urn:srcos:glossary:promote-to-platform", + "model": "nomic-ai/nomic-embed-text-v1.5", + "dimension": 768, + "cosine": 0.71 + }, + "estateBinding": { + "kind": "gadget", + "ref": "urn:srcos:x:y" + } + }, + "relations": [ + { + "predicate": "is-a", + "target": "urn:srcos:ontology-class:governance.Control" + }, + { + "predicate": "skos:related", + "target": "urn:srcos:glossary:promote-to-platform" + } + ] + } + }, + { + "schema": "GlossaryTerm.json", + "reason": "alignment is a closed object — unknown method must be rejected.", + "failValidator": "additionalProperties", + "document": { + "id": "urn:srcos:glossary:release-gate", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "release gate", + "definition": "A fail-closed check a service must pass to be release-ready.", + "partOfSpeech": "noun-phrase", + "status": "approved", + "alignment": { + "ontologyClassRef": "urn:srcos:ontology-class:governance.ReleaseGate", + "vectorLink": { + "peerRef": "urn:srcos:glossary:promote-to-platform", + "model": "nomic-ai/nomic-embed-text-v1.5", + "dimension": 768, + "cosine": 0.71 + }, + "estateBinding": { + "kind": "entity", + "ref": "urn:srcos:service-graph:release-gate-audit" + }, + "telepathy": true + }, + "relations": [ + { + "predicate": "is-a", + "target": "urn:srcos:ontology-class:governance.Control" + }, + { + "predicate": "skos:related", + "target": "urn:srcos:glossary:promote-to-platform" + } + ] + } + }, + { + "schema": "GlossaryTerm.json", + "reason": "relation predicate is a closed RDF/RDFS/SKOS/FOAF set — an unknown predicate must fail.", + "failValidator": "enum", + "document": { + "id": "urn:srcos:glossary:release-gate", + "type": "GlossaryTerm", + "specVersion": "2.0.0", + "name": "release gate", + "definition": "A fail-closed check a service must pass to be release-ready.", + "partOfSpeech": "noun-phrase", + "status": "approved", + "alignment": { + "ontologyClassRef": "urn:srcos:ontology-class:governance.ReleaseGate", + "vectorLink": { + "peerRef": "urn:srcos:glossary:promote-to-platform", + "model": "nomic-ai/nomic-embed-text-v1.5", + "dimension": 768, + "cosine": 0.71 + }, + "estateBinding": { + "kind": "entity", + "ref": "urn:srcos:service-graph:release-gate-audit" + } + }, + "relations": [ + { + "predicate": "causes-vibes", + "target": "urn:srcos:x:y" + } + ] + } + } + ] +} diff --git a/schemas/GlossaryTerm.json b/schemas/GlossaryTerm.json index 4773a9d1..6235ba94 100644 --- a/schemas/GlossaryTerm.json +++ b/schemas/GlossaryTerm.json @@ -20,7 +20,7 @@ }, "type": { "const": "GlossaryTerm", - "description": "Discriminator constant \u2014 always \"GlossaryTerm\"." + "description": "Discriminator constant — always \"GlossaryTerm\"." }, "specVersion": { "type": "string", @@ -69,6 +69,131 @@ "$ref": "Link.json" }, "description": "Supplementary hyperlinks." + }, + "partOfSpeech": { + "type": "string", + "enum": [ + "noun-phrase", + "verb-phrase" + ], + "description": "Tesnière grain: noun-phrase (NP) = an estate ENTITY/actant; verb-phrase (VP) = an ACTION/service. Optional; required-in-effect once status is 'approved' (validator)." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "approved", + "deprecated" + ], + "default": "draft", + "description": "Lifecycle. A 'draft' term may be partially aligned (capture in flight); an 'approved' term MUST be fully aligned across all three methods (validator, fail-closed)." + }, + "alignment": { + "type": "object", + "additionalProperties": false, + "description": "Alignment across the three methods that make a term governance-usable: (1) formal ontology class; (2) NP↔VP vector link in the sovereign space; (3) estate entity/service binding.", + "properties": { + "ontologyClassRef": { + "type": [ + "string", + "null" + ], + "pattern": "^urn:srcos:ontology-class:[A-Za-z0-9._~:-]+$", + "description": "Method 1 — the formal ontology/KKO class this term is captured as." + }, + "vectorLink": { + "type": [ + "object", + "null" + ], + "additionalProperties": false, + "description": "Method 2 — the semantic vector link to a peer term (NP↔VP), in the SAME sovereign space as EmbeddingRequest (model + dimension pinned by const).", + "required": [ + "peerRef", + "model", + "dimension", + "cosine" + ], + "properties": { + "peerRef": { + "type": "string", + "pattern": "^urn:srcos:glossary:[A-Za-z0-9._~:-]+$", + "description": "The peer term (an NP links to a VP and vice versa)." + }, + "model": { + "const": "nomic-ai/nomic-embed-text-v1.5" + }, + "dimension": { + "const": 768 + }, + "cosine": { + "type": "number", + "minimum": -1, + "maximum": 1 + } + } + }, + "estateBinding": { + "type": [ + "object", + "null" + ], + "additionalProperties": false, + "description": "Method 3 — the real estate entity/service that IMPLEMENTS this term. NP→entity, VP→service|action (validator).", + "required": [ + "kind", + "ref" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "entity", + "service", + "action" + ] + }, + "ref": { + "type": "string", + "pattern": "^urn:srcos:[A-Za-z0-9._~:-]+$" + } + } + } + } + }, + "relations": { + "type": "array", + "description": "Typed relations to other terms / ontology classes / estate refs, over a CLOSED predicate set (RDF/RDFS/SKOS/FOAF patterns) so a reasoner can derive dependencies + constraints and cross-check them against the blast-radius graph and the neurosymbolic domain. Inverse pairs (is-type/has-type, is-member/has-member, skos:broader/skos:narrower) must reciprocate; skos:related is symmetric; is-a (subsumption) must be acyclic.", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "predicate", + "target" + ], + "properties": { + "predicate": { + "type": "string", + "enum": [ + "is-type", + "has-type", + "is-a", + "has-a", + "is-member", + "has-member", + "skos:broader", + "skos:narrower", + "skos:related", + "foaf:member", + "has-datatype" + ] + }, + "target": { + "type": "string", + "pattern": "^urn:srcos:[A-Za-z0-9._~:-]+$" + } + } + } } } } diff --git a/specs/glossary-alignment-contract.md b/specs/glossary-alignment-contract.md new file mode 100644 index 00000000..31fbd842 --- /dev/null +++ b/specs/glossary-alignment-contract.md @@ -0,0 +1,30 @@ +# Glossary Alignment Contract (v0.1) — vocabulary as governance substrate + +Language establishes meaning and regulates state. A term is only usable to reason about +governance when it is **captured, vector-aligned, and implemented** — so an `approved` +`GlossaryTerm` must, fail-closed, carry all three (validator): + +1. **Capture** — `alignment.ontologyClassRef`: a formal ontology/KKO class. +2. **Vector-align** — `alignment.vectorLink`: an NP↔VP link in the SOVEREIGN space + (model + dimension pinned to the `EmbeddingRequest` const, 768), reciprocated by the peer. +3. **Implement** — `alignment.estateBinding`: a real estate entity/service (NP→entity, VP→action|service, Tesnière). + +## Typed relations (reasoner substrate) + +`relations` carry a CLOSED predicate set (RDF/RDFS/SKOS/FOAF): `is-type`/`has-type`, +`is-a`, `has-a`, `is-member`/`has-member`, `skos:broader`/`skos:narrower`, `skos:related`, +`foaf:member`, `has-datatype`. The validator enforces the constraints a reasoner needs: +inverse pairs reciprocate, `skos:related` is symmetric, and `is-a` subsumption is **acyclic** +(a cycle is an unsatisfiable constraint). This is what lets a reasoner derive dependencies +and constraints from the vocabulary. + +## Currency (follow-up) & agreement (follow-up) + +- **Currency**: the glossary is a closed fixed set (LSA/LSI); the live estate generates an + open topic space (LDA). The LSA↔LDA divergence flags new/optimal vocab not yet captured. +- **Agreement**: a standard test cross-checks these relations against the **governed + blast-radius graph** (dependencies) and the **neurosymbolic agent's domain + symbols**, + with follow-up automation to remediate and keep the three consistent. + +## Conformance +`make validate-glossary-alignment-examples` (in `make validate`). diff --git a/tools/validate_glossary_alignment_examples.py b/tools/validate_glossary_alignment_examples.py new file mode 100644 index 00000000..0b05a5c1 --- /dev/null +++ b/tools/validate_glossary_alignment_examples.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Validate GlossaryTerm alignment — the vocabulary drift-guard (task #13). + +Language is the governance substrate. An APPROVED term must be usable to reason about +governance, which means all three methods must hold, fail-closed: + 1. capture — bound to a formal ontology class (alignment.ontologyClassRef); + 2. vector-align — an NP↔VP link in the sovereign 768 space (alignment.vectorLink), + and the peer must link back (a link is a relation, not a claim); + 3. implement — bound to a real estate entity/service (alignment.estateBinding), + with NP→entity and VP→action|service (Tesnière). +A 'draft' term may be partially aligned (capture in flight); an approved term may not. +Plus: negative vectors fail on their named JSON-Schema keyword. +""" +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = "GlossaryTerm.json" +EXAMPLES = [ + "glossary_term.np_entity.json", + "glossary_term.vp_action.json", + "glossary_term.draft_unaligned.json", +] + +FAILURES: list[str] = [] +CHECKS: dict[str, bool] = {} + + +def load(p: Path) -> dict: + return json.loads(p.read_text(encoding="utf-8")) + + +def check_conformance(schema: dict, terms: dict[str, dict]) -> None: + jsonschema.Draft202012Validator.check_schema(schema) + for name, t in terms.items(): + errs = sorted(jsonschema.Draft202012Validator(schema).iter_errors(t), key=str) + if errs: + for e in errs: + FAILURES.append(f"{name}: {e.message}") + else: + CHECKS[f"schema:{name}"] = True + + +def check_alignment(terms: dict[str, dict]) -> None: + by_id = {t["id"]: t for t in terms.values()} + for name, t in terms.items(): + a = t.get("alignment") or {} + status = t.get("status", "draft") + pos = t.get("partOfSpeech") + + if status == "approved": + missing = [k for k in ("ontologyClassRef", "vectorLink", "estateBinding") if not a.get(k)] + if missing: + FAILURES.append(f"{name}: approved term is a governance hole — unaligned method(s) {missing} " + f"(an approved term must be captured + vector-aligned + implemented)") + continue + CHECKS[f"aligned:{name}"] = True + # NP→entity, VP→action|service + kind = a["estateBinding"]["kind"] + if pos == "noun-phrase" and kind != "entity": + FAILURES.append(f"{name}: an NP term must bind to an entity, not {kind!r}") + elif pos == "verb-phrase" and kind not in ("action", "service"): + FAILURES.append(f"{name}: a VP term must bind to an action/service, not {kind!r}") + else: + CHECKS[f"pos-binding:{name}"] = True + # vector link must be reciprocal (a relation, not a one-sided claim) + peer = by_id.get(a["vectorLink"]["peerRef"]) + if peer is not None: + back = ((peer.get("alignment") or {}).get("vectorLink") or {}).get("peerRef") + if back != t["id"]: + FAILURES.append(f"{name}: vectorLink to {a['vectorLink']['peerRef']} is not reciprocated " + f"(peer links back to {back!r}) — a link is a relation, not a claim") + else: + CHECKS[f"vector-reciprocal:{name}"] = True + else: + CHECKS[f"draft-ok:{name}"] = True # a draft may be partial + + +INVERSE = {"is-type": "has-type", "has-type": "is-type", + "is-member": "has-member", "has-member": "is-member", + "skos:broader": "skos:narrower", "skos:narrower": "skos:broader"} +SYMMETRIC = {"skos:related"} + + +def check_relations(terms: dict[str, dict]) -> None: + """Reasoner-consistency over the typed relations: inverse pairs reciprocate, + skos:related is symmetric, and is-a (subsumption) is acyclic. This is what lets a + reasoner derive dependencies + constraints (and cross-check them against the + blast-radius graph + neurosymbolic domain — the follow-up standard test).""" + by_id = {t["id"]: t for t in terms.values()} + + def rels(t): + return [(r["predicate"], r["target"]) for r in (t.get("relations") or [])] + + for name, t in terms.items(): + for pred, tgt in rels(t): + peer = by_id.get(tgt) + if peer is None: + continue # external ontology-class / estate target — resolved elsewhere + peer_rels = rels(peer) + if pred in INVERSE and (INVERSE[pred], t["id"]) not in peer_rels: + FAILURES.append(f"{name}: {pred} → {tgt} not reciprocated by inverse {INVERSE[pred]} — " + f"an unreciprocated relation is a claim, not a fact the reasoner can use") + elif pred in SYMMETRIC and (pred, t["id"]) not in peer_rels: + FAILURES.append(f"{name}: {pred} → {tgt} is not symmetric (peer does not relate back)") + else: + CHECKS[f"relation:{name}:{pred}"] = True + + # is-a (subsumption) must be acyclic — a cycle is an unsatisfiable constraint. + isa = {t["id"]: [tg for pr, tg in rels(t) if pr == "is-a"] for t in terms.values()} + WHITE, GREY, BLACK = 0, 1, 2 + color = {k: WHITE for k in isa} + + def dfs(n: str) -> bool: + color[n] = GREY + for m in isa.get(n, []): + if m not in color: + continue + if color[m] == GREY or (color[m] == WHITE and dfs(m)): + return True + color[n] = BLACK + return False + + if any(color[n] == WHITE and dfs(n) for n in isa): + FAILURES.append("is-a subsumption graph has a cycle — an unsatisfiable constraint") + else: + CHECKS["is-a:acyclic"] = True + + +def check_negatives(schema: dict) -> None: + fx = load(ROOT / "fixtures" / "glossary-alignment" / "conformance.json") + for i, case in enumerate(fx["cases"]): + expected = case.get("failValidator") + try: + jsonschema.validate(case["document"], schema) + except jsonschema.ValidationError as exc: + if expected is not None and exc.validator != expected: + FAILURES.append(f"negative {i}: failed on {exc.validator!r}, not {expected!r}: {case['reason']}") + else: + CHECKS[f"negative:{i}:{exc.validator}"] = True + continue + FAILURES.append(f"negative {i} unexpectedly PASSED: {case['reason']}") + + +def main() -> int: + schema = load(ROOT / "schemas" / SCHEMA) + terms = {n: load(ROOT / "examples" / n) for n in EXAMPLES} + check_conformance(schema, terms) + check_alignment(terms) + check_relations(terms) + check_negatives(schema) + for m in FAILURES: + print(f"FAIL: {m}", file=sys.stderr) + ok = not FAILURES and all(CHECKS.values()) + print(json.dumps({"ok": ok, "checks": CHECKS}, indent=2, sort_keys=True)) + return 0 if ok else 1 + + +if __name__ == "__main__": + raise SystemExit(main())