From e8fae9652f109899c0f23b82cb8dd7497e5fdb2e Mon Sep 17 00:00:00 2001 From: Markus Cozowicz Date: Wed, 12 Aug 2026 21:53:24 +0200 Subject: [PATCH] Add document-wide ai_context and custom_extensions The root object is the only node in the spec with no ai_context and no custom_extensions: both appear on SemanticModel, Dataset, Field, Metric and Relationship, but the document itself is closed (additionalProperties: false) with only version and semantic_model. A document holds a LIST of semantic models, so guidance that governs all of them has nowhere to live. A producer emitting one model per data source must copy shared instructions into every model so no model can be read without them, and a consumer cannot tell those copies apart from genuinely model-specific instruction. Document-wide context is already an established shape in Ossie: an ontology document carries ai_context on its root and $refs this specification's AIContext definition. Core documents were the only root without it, so this follows the existing precedent rather than introducing a new concept, and a test pins the two roots to the same definition. Both keys are optional and the root stays closed, so existing documents validate unchanged. Refs: apache/ossie#322 --- core-spec/osi-schema.json | 11 ++++++ core-spec/spec.md | 73 +++++++++++++++++++++++++++++++++--- core-spec/spec.yaml | 20 ++++++++++ docs/index.md | 4 +- python/src/ossie/models.py | 2 + python/tests/test_models.py | 75 +++++++++++++++++++++++++++++++++++++ 6 files changed, 177 insertions(+), 8 deletions(-) diff --git a/core-spec/osi-schema.json b/core-spec/osi-schema.json index f24e45f1..39ada009 100644 --- a/core-spec/osi-schema.json +++ b/core-spec/osi-schema.json @@ -10,12 +10,23 @@ "const": "0.2.0.dev0", "description": "Apache Ossie specification version" }, + "ai_context": { + "$ref": "#/$defs/AIContext", + "description": "Document-wide context for AI tools. Applies to every semantic model in this document; model-level ai_context adds to it and takes precedence where the two conflict." + }, "semantic_model": { "type": "array", "description": "Collection of semantic model definitions", "items": { "$ref": "#/$defs/SemanticModel" } + }, + "custom_extensions": { + "type": "array", + "description": "Document-wide vendor-specific attributes for extensibility", + "items": { + "$ref": "#/$defs/CustomExtension" + } } }, "required": ["version", "semantic_model"], diff --git a/core-spec/spec.md b/core-spec/spec.md index 156cb1db..5df94b7a 100644 --- a/core-spec/spec.md +++ b/core-spec/spec.md @@ -32,12 +32,13 @@ ## Table of Contents 1. [Enumerations](#enumerations) -2. [Semantic Model](#semantic-model) -3. [Datasets](#datasets) -4. [Relationships](#relationships) -5. [Fields](#fields) -6. [Metrics](#metrics) -7. [Examples](#examples) +2. [Document](#document) +3. [Semantic Model](#semantic-model) +4. [Datasets](#datasets) +5. [Relationships](#relationships) +6. [Fields](#fields) +7. [Metrics](#metrics) +8. [Examples](#examples) --- @@ -79,6 +80,66 @@ ontology specification's built-in value types; `Time`, `DateTimeTz`, and | `DateTimeTz` | Date and time with sufficient offset or timezone context to identify an instant. Preservation of a named timezone identifier is not guaranteed. | | `Opaque` | Known type outside the portable vocabulary; use `custom_extensions` for vendor-specific refinement. Omit `datatype` when the type is unknown or unspecified. | +## Document + +The root object of an Ossie file. A document carries a specification `version` and one or +more semantic models. + +### Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `version` | string | Yes | Apache Ossie specification version | +| `ai_context` | string/object | No | Document-wide context for AI tools | +| `semantic_model` | array | Yes | Collection of semantic model definitions | +| `custom_extensions` | array | No | Document-wide vendor-specific attributes for extensibility | + +### Document-wide `ai_context` + +A document may hold several semantic models — for example one per data source, where each +model is queried in its own dialect. Guidance that governs all of them (domain conventions, +a shared glossary, rules the publisher considers mandatory) belongs to the document, not to +any one model. Without a document-level slot, a producer has to copy that guidance into +every model so that no model can be read without it, and a consumer has no way to tell the +copies apart from genuinely model-specific instruction. + +`ai_context` at the root states it once: + +```yaml +version: 0.2.0.dev0 +ai_context: + instructions: "Fiscal year starts in July. Never join across the finance and telemetry models." + synonyms: ["revenue = net_sales"] +semantic_model: + - name: finance + ai_context: + instructions: "Amounts are in USD." + datasets: + - name: gl_entries + source: finance.public.gl_entries + - name: telemetry + datasets: + - name: events + source: telemetry.public.events +``` + +Document-level context applies to every model in the document. Model-level `ai_context` adds +to it rather than replacing it, and takes precedence where the two conflict — so `finance` +above is read as "fiscal year starts in July" *and* "amounts are in USD". The root slot +carries no instruction that is specific to one model; that is what the model-level slot is +for. + +Consumers that do not understand document-level context still parse the document, and models +remain independently readable — but a consumer that ignores it will miss guidance the +publisher considered mandatory, exactly as it would by ignoring model-level `ai_context`. + +An ontology document already works this way: `ontology/ontology.json` carries `ai_context` on +its root and resolves it against this specification's `AIContext` definition, so document-wide +context is an established shape in Ossie rather than a new one. Core documents were the only +root without it. + +--- + ## Semantic Model The top-level container that represents a complete semantic model, including datasets, relationships, and metrics. diff --git a/core-spec/spec.yaml b/core-spec/spec.yaml index 32fbb3e1..b5873be8 100644 --- a/core-spec/spec.yaml +++ b/core-spec/spec.yaml @@ -58,6 +58,26 @@ datatypes: vendor_name: string +# Optional: Document-wide context for AI tools. +# Applies to every semantic model in this document. Model-level ai_context adds to +# it and takes precedence where the two conflict. +# Use this for guidance that governs the document as a whole — a model read in +# isolation would otherwise have to repeat it. +# Can be a simple string or a structured object with instructions, +# synonyms, examples, or vendor-specific keys. +ai_context: + instructions: string + synonyms: + - string + examples: + - string + +# Optional: Document-wide vendor-specific attributes for extensibility +custom_extensions: + - vendor_name: string # Free-form string identifying the vendor + data: string + + # Top-level semantic model definition semantic_model: # Required: Unique identifier for the semantic model diff --git a/docs/index.md b/docs/index.md index 738c5724..498996c1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -57,7 +57,7 @@ The Ossie core specification (current version: **0.2.0.dev0**, latest released: | **Relationships** | Foreign key connections between datasets, supporting both simple and composite keys. | | **Metrics** | Quantitative measures (sums, averages, ratios, etc.) defined at the model level, capable of spanning multiple datasets. | | **Custom Extensions** | Vendor-specific metadata stored as JSON, allowing platforms to carry additional information without breaking core compatibility. | -| **AI Context** | Optional annotations at every level (model, dataset, field, relationship, metric) to help AI tools understand business meaning — including instructions, synonyms, and example queries. | +| **AI Context** | Optional annotations at every level (document, model, dataset, field, relationship, metric) to help AI tools understand business meaning — including instructions, synonyms, and example queries. | The specification supports multiple SQL dialects (`ANSI_SQL`, `SNOWFLAKE`, `DATABRICKS`, `MDX`, `TABLEAU`) so that expressions can be tailored to each platform while maintaining a common model structure. @@ -322,7 +322,7 @@ A practical guide for organizations looking to adopt Ossie. | **Relationship** | A foreign key connection between two datasets, defining how they can be joined. Relationships are always many-to-one (from the referencing dataset to the referenced dataset). | | **Dialect** | A specific SQL or expression language variant (e.g., `ANSI_SQL`, `SNOWFLAKE`, `DATABRICKS`). Ossie supports multiple dialects so expressions can be tailored to each platform. | | **Custom Extension** | Vendor-specific metadata attached to any Ossie construct as a JSON string. Extensions allow platforms to carry additional information without modifying the core specification. | -| **AI Context** | Optional annotations on any Ossie construct (model, dataset, field, relationship, metric) that provide additional context for AI tools — including natural language instructions, synonyms, and example queries. | +| **AI Context** | Optional annotations on any Ossie construct (document, model, dataset, field, relationship, metric) that provide additional context for AI tools — including natural language instructions, synonyms, and example queries. Document-level context applies to every semantic model in the document; model-level context adds to it and takes precedence on conflict. | | **Converter** | A tool that translates between the Ossie format and a specific vendor's semantic model format. Converters come in pairs: import (vendor → Ossie) and export (Ossie → vendor). | | **Hub-and-Spoke** | The architectural pattern used by Ossie, where the specification acts as the central format (hub) and vendor converters act as spokes, avoiding the need for point-to-point integrations. | | **Round-Trip Fidelity** | The ability to convert a model from one format to Ossie and back without losing information. Achieved by preserving vendor-specific metadata in `custom_extensions`. | diff --git a/python/src/ossie/models.py b/python/src/ossie/models.py index 5406a743..55464e77 100644 --- a/python/src/ossie/models.py +++ b/python/src/ossie/models.py @@ -211,7 +211,9 @@ class OSIDocument(BaseModel): version: str = "0.2.0.dev0" dialects: Optional[list[OSIDialect]] = None vendors: Optional[list[OSIVendor]] = None + ai_context: Optional[OSIAIContext] = None semantic_model: list[OSISemanticModel] + custom_extensions: Optional[list[OSICustomExtension]] = None def to_osi_yaml(self, **kwargs: Any) -> str: """Serialize to Ossie-compliant YAML (uses field aliases and excludes None values).""" diff --git a/python/tests/test_models.py b/python/tests/test_models.py index 749a226c..a41840f1 100644 --- a/python/tests/test_models.py +++ b/python/tests/test_models.py @@ -111,6 +111,81 @@ def test_invalid_datatype_is_rejected() -> None: OSIDocument.model_validate(document) +def test_document_ai_context_matches_core_schema() -> None: + schema_path = Path(__file__).parents[2] / "core-spec" / "osi-schema.json" + schema = json.loads(schema_path.read_text()) + + assert schema["properties"]["ai_context"]["$ref"] == "#/$defs/AIContext" + assert schema["properties"]["custom_extensions"]["items"] == { + "$ref": "#/$defs/CustomExtension" + } + + # Document-wide context is optional: a document without it stays valid, so + # existing documents keep validating unchanged. + assert "ai_context" not in schema["required"] + assert "custom_extensions" not in schema["required"] + + # The root stays closed; the two new keys are the only additions. + assert schema["additionalProperties"] is False + + +def test_document_ai_context_agrees_with_the_ontology_root() -> None: + """Both document roots carry ai_context, resolved against the same definition. + + The ontology specification already puts ai_context on its root and $refs this + specification's AIContext. If that reference is ever repointed at a different + definition, the two document types would silently disagree about what + document-wide context means. + """ + core = json.loads( + (Path(__file__).parents[2] / "core-spec" / "osi-schema.json").read_text() + ) + ontology = json.loads( + (Path(__file__).parents[2] / "ontology" / "ontology.json").read_text() + ) + + assert ontology["properties"]["ai_context"]["$ref"].endswith( + "osi-schema.json#/$defs/AIContext" + ) + assert core["properties"]["ai_context"]["$ref"] == "#/$defs/AIContext" + + +def test_document_ai_context_survives_serialization() -> None: + document = _document() + document["ai_context"] = { + "instructions": "Fiscal year starts in July.", + "synonyms": ["revenue = net_sales"], + } + document["custom_extensions"] = [{"vendor_name": "COMMON", "data": "{}"}] + + parsed = OSIDocument.model_validate(document) + assert parsed.ai_context.instructions == "Fiscal year starts in July." + assert parsed.custom_extensions[0].vendor_name == "COMMON" + + for serialized in ( + json.loads(parsed.to_osi_json()), + yaml.safe_load(parsed.to_osi_yaml()), + ): + assert serialized["ai_context"]["instructions"] == "Fiscal year starts in July." + assert serialized["custom_extensions"][0]["vendor_name"] == "COMMON" + + +def test_document_ai_context_accepts_the_string_form() -> None: + document = _document() + document["ai_context"] = "Fiscal year starts in July." + + parsed = OSIDocument.model_validate(document) + assert parsed.ai_context == "Fiscal year starts in July." + + +def test_document_without_ai_context_omits_it() -> None: + parsed = OSIDocument.model_validate(_document()) + + assert parsed.ai_context is None + assert "ai_context" not in yaml.safe_load(parsed.to_osi_yaml()) + assert "custom_extensions" not in json.loads(parsed.to_osi_json()) + + @pytest.mark.parametrize( ("dimension", "datatype", "expected"), [