Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core-spec/osi-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
73 changes: 67 additions & 6 deletions core-spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

---

Expand Down Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions core-spec/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +61 to +73

# 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
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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`. |
Expand Down
2 changes: 2 additions & 0 deletions python/src/ossie/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines 211 to +216

def to_osi_yaml(self, **kwargs: Any) -> str:
"""Serialize to Ossie-compliant YAML (uses field aliases and excludes None values)."""
Expand Down
75 changes: 75 additions & 0 deletions python/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
[
Expand Down