feat(data): DataClass (OntoDT/OntoDQ) + TF-Lattice classifier — data-side governance (task #14) - #251
Conversation
…side governance (task #14) The data-side counterpart to the glossary (biz-side, #250). A DataClass binds, fail-closed: OntoDT datatype-ontology class (capture) + business GlossaryTerm (biz↔data) + ValidValues domain (enum/range/regex) + an optional TF-Lattice wide-and-deep classifier that ASSIGNS the class by inference (OntoDQ) and is a first-class CATALOGED model — modelRef→ModelManifest, runRef→RunRecord, compute on Ray/TritFabric, monotonicFeatures lattice constraint, labels that are GlossaryTerm URNs (assigned in the glossary). EntityField gains an optional dataClassRef. Drift-guard (validate_data_class_examples.py, -resolved via a referencing registry): ontological typing + glossary link + domain required; classifier labels are glossary terms, model is cataloged, compute is ray/tritfabric; a field bound to a DataClass must share its domain kind. 4 negative vectors; teeth-verified (bad-label, kind-mismatch). make validate ok; dup-$id 346. Ties biz-glossary #250 ↔ DataClass ↔ EntityField ↔ model catalog ↔ Ray/TritFabric runs.
There was a problem hiding this comment.
Pull request overview
Introduces a new DataClass schema and supporting fixtures/examples to formalize the data-side governance node (OntoDT/OntoDQ) and connect it to GlossaryTerm and EntityField, plus a validator + Makefile target to enforce conformance and negative controls.
Changes:
- Added
schemas/DataClass.jsonplus example + negative vectors, and a validator (tools/validate_data_class_examples.py) wired intomake validate. - Extended
schemas/EntityField.jsonwith optionaldataClassRefand added anEntityFieldexample that binds to a DataClass. - Added initial contract documentation (
specs/data-class-contract.md) and a changelog entry.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/validate_data_class_examples.py | New validator for DataClass/EntityField examples + negative vectors and cross-binding checks. |
| specs/data-class-contract.md | New short contract doc describing DataClass and its intended invariants. |
| schemas/EntityField.json | Adds optional dataClassRef for binding fields to DataClass. |
| schemas/DataClass.json | New DataClass JSON Schema including optional TF-Lattice classifier block. |
| Makefile | Adds validate-data-class-examples and includes it in validate. |
| fixtures/data-class/conformance.json | Adds negative vectors for DataClass schema validation. |
| examples/entity_field.revenue.json | Adds example EntityField bound to a DataClass. |
| examples/data_class.currency.json | Adds example DataClass with classifier metadata. |
| CHANGELOG.md | Documents the new DataClass feature in Unreleased. |
Suppressed comments (1)
tools/validate_data_class_examples.py:98
- check_field_conformance() is currently fail-open when an EntityField sets dataClassRef but omits validValues: field_kind becomes None and the code records the check as passing. This contradicts the contract text (“field’s validValues must conform…”) and prevents the drift-guard from enforcing domain conformance for such fields.
field_kind = (fld.get("validValues") or {}).get("kind")
class_kind = (dc.get("domain") or {}).get("kind")
if field_kind and class_kind and field_kind != class_kind:
FAILURES.append(f"{name}: validValues.kind {field_kind!r} does not conform to DataClass {ref} "
f"domain kind {class_kind!r}")
else:
CHECKS[f"field-conforms:{name}"] = True
| def check_conformance(dataclass_schema, field_schema, dcs, fields) -> None: | ||
| for name, d in {**dcs, **fields}.items(): | ||
| schema = dataclass_schema if d.get("type") == "DataClass" else field_schema | ||
| errs = sorted(validator_for(schema).iter_errors(d), key=str) | ||
| if errs: | ||
| for e in errs: | ||
| FAILURES.append(f"{name}: {e.message}") | ||
| else: | ||
| CHECKS[f"schema:{name}"] = True |
| "required": [ | ||
| "kind", | ||
| "modelRef", | ||
| "version", | ||
| "labels" | ||
| ], |
| The business vocabulary (`GlossaryTerm`) says what a thing MEANS; the `DataClass` says what | ||
| its DATA is, ontologically — so data quality can be inferred and enforced, not asserted. | ||
|
|
||
| A `DataClass` binds four things and is fail-closed if any is missing: |
| "dataClassRef": { | ||
| "type": [ | ||
| "string", | ||
| "null" | ||
| ], | ||
| "pattern": "^urn:srcos:data-class:[A-Za-z0-9._~:-]+$", | ||
| "description": "Optional binding to the OntoDQ DataClass this field is an instance of. When set, the field's validValues must conform to the DataClass domain (validator)." | ||
| } | ||
| } | ||
| } |
| def check_classifier(dcs) -> None: | ||
| for name, dc in dcs.items(): | ||
| clf = dc.get("classifier") | ||
| if not clf: | ||
| CHECKS[f"classifier:{name}:none"] = True | ||
| continue | ||
| labels = clf.get("labels") or [] | ||
| if not all(l.startswith("urn:srcos:glossary:") for l in labels): | ||
| FAILURES.append(f"{name}: classifier labels must all be GlossaryTerm URNs (assigned in the glossary)") | ||
| elif not clf.get("modelRef", "").startswith("urn:srcos:model-manifest:"): | ||
| FAILURES.append(f"{name}: classifier.modelRef must be a ModelManifest (cataloged model)") | ||
| elif (clf.get("compute") or {}).get("platform") not in ("ray", "tritfabric"): | ||
| FAILURES.append(f"{name}: classifier compute must run on ray|tritfabric") | ||
| else: | ||
| CHECKS[f"classifier:{name}:cataloged-on-compute-with-glossary-labels"] = True |
…TMAX (LSA + doc2vec) Per Charles: each class needs an individually-testable classifier, and each table needs a softmax aligning a LSA bag-of-words embedding and a doc2vec sentence-encoder (the n-ary logit→class examples). - DataClass.classifier is now the PER-CLASS head: head=logistic (one-vs-rest, binary) + a REQUIRED evalRunRef (the individual per-class test) + threshold. A class with no eval run is untestable and refused. - New TableClassifier: per-table head=softmax (n-ary), embeddings MUST include BOTH lsa-bag-of-words AND doc2vec-sentence-encoder, a cataloged ModelManifest with a run on Ray/TritFabric, assignsClasses = the N DataClasses. - Validator enforces both + 2 new negative vectors. Teeth-verified (missing-doc2vec, missing-evalRunRef). make validate ok; dup-$id 347.
|
Extended per your review with the two-level classifier architecture: (a) |
- DataClass.classifier now REQUIRES runRef + compute and both are non-null — a classifier is a fully cataloged model with a run on ray/tritfabric, not a partial block (fail-closed intent). - EntityField: allOf if/then — dataClassRef present ⇒ validValues required, so schema-only validators also catch a field bound to a class without a declared domain. - validate_data_class_examples.py: validate each collection against its INTENDED schema explicitly (never pick schema from the instance's own type — a mistyped doc could validate against the wrong schema). - spec: reword 'binds four things, fail-closed if any missing' → three required + an optional fourth (assignment), matching the schema/validator. make validate ok; dup-$id 347.
|
Addressed all 4 Copilot findings: (1) |
…ype-guards (Copilot #251) check_classifier now verifies classifier.runRef is a RunRecord URN independently (not just via the schema pattern), consistent with how it checks modelRef, and guards against a non-dict classifier/compute so a prior schema failure can't crash the validator. check_table_classifiers skips non-dict embedding entries. The other four review items were already resolved in earlier remediation (validate-by-intended-schema, schema requires runRef+compute, EntityField if/then dataClassRef=>validValues, spec reworded 'three + optional fourth'). Teeth-verified.
What (data-side of the vocabulary/governance program; AMG 2.0 deck)
The data-side counterpart to the glossary (#250, biz-side). Where a
GlossaryTermsays what a thing means, aDataClasssays what its data is — ontologically — so data quality can be inferred and enforced, not asserted.A
DataClassbinds four things, fail-closed:ontologyClassRef: the datatype-ontology class (capture).glossaryTermRef: the businessGlossaryTermit realizes.domain($refValidValues): enum / range / regex.classifierthat assigns the class by inference (OntoDQ), a first-class cataloged model:modelRef→ModelManifest,runRef→RunRecord,compute.platform∈ {ray, tritfabric},monotonicFeatures(lattice shape constraint),labelsthat areGlossaryTermURNs (labels assigned in the glossary).EntityFieldgains an optionaldataClassRef; the drift-guard enforces field↔class domain conformance (a field can't claim a class it doesn't fit), classifier integrity (glossary labels · cataloged model · Ray/TritFabric compute), and ontological typing. 4 negative vectors; teeth-verified (bad-label, kind-mismatch).make validateok; dup-$id 346.This closes the loop your last messages drew: biz glossary ↔ DataClass ↔ EntityField ↔ model catalog ↔ Ray/TritFabric runs ↔ glossary labels. Not auto-merging — for review.