Skip to content

fix(converters): emit schema-conformant OSI documents (drop non-spec root dialects/vendors)#148

Open
andreybavt wants to merge 1 commit into
apache:mainfrom
Kaelio:fix/schema-conformant-osi-documents
Open

fix(converters): emit schema-conformant OSI documents (drop non-spec root dialects/vendors)#148
andreybavt wants to merge 1 commit into
apache:mainfrom
Kaelio:fix/schema-conformant-osi-documents

Conversation

@andreybavt

@andreybavt andreybavt commented Jun 8, 2026

Copy link
Copy Markdown

Problem

Output from the dbt→OSI converter fails OSI's own validator (validation/validate.py)
against the published core schema:

[Schema] (root): Additional properties are not allowed ('dialects' was unexpected)

Because the CLI writes via to_osi_yaml(), every dbt→OSI conversion produces a
non-conformant document.

Root cause

OSIDocument (python/src/osi/models.py) declared optional root-level
dialects/vendors fields that aren't in core-spec/osi-schema.json, whose root is
additionalProperties: false (only version + semantic_model). The dbt converter
set dialects=[self._dialect], so dialects was emitted at the document root.

Fix

  • Remove the non-spec dialects/vendors fields from OSIDocument.
  • Stop the dbt converter from populating a document-root dialect.

Per-expression dialect tagging (OSIExpression.dialects) is unchanged and remains the
schema-valid home for dialects, so no information is lost. Dialect selection still
flows end-to-end; the two affected converter tests now assert it on the per-expression path.

Regression guard

Adds converters/dbt/tests/test_schema_conformance.py, which converts a representative
manifest and validates the emitted document (YAML and JSON, for ANSI_SQL and
SNOWFLAKE) against core-spec/osi-schema.json, reusing validation/validate.py.
CI now fails if a converter emits a non-conformant document root again.

Testing

  • validate.py on converter output: fails before ('dialects' was unexpected),
    passes after.
  • Full dbt converter test suite green.

Out of scope

Whether OSI should support a document-level (default) dialect is the open discussion in
#52 (one dialect per document) and #16 (default dialect at dataset level). This PR takes
no position and makes no schema change - it only aligns the reference model and
converter with the schema as published today.

Drop the non-spec root `dialects`/`vendors` fields from OSIDocument and stop the
dbt converter emitting a root dialect, so dbt->OSI output validates against
core-spec/osi-schema.json. Dialects remain per-expression (no information lost).
Add a regression test that schema-validates converter output.
@andreybavt

Copy link
Copy Markdown
Author

@khush-bhatia , let me know if this first PR follows the community guidelines. Happy to take bigger scope after this first one lands

@khush-bhatia
khush-bhatia requested a review from QMalcolm June 8, 2026 18:23

@khush-bhatia khush-bhatia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix.

@QMalcolm QMalcolm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Thank you for putting this together 🙂

@khush-bhatia

Copy link
Copy Markdown
Contributor

Thanks @andreybavt, We will merge this PR soon, we are waiting on some process to complete.

@andreybavt

Copy link
Copy Markdown
Author

Thanks @khush-bhatia and @QMalcolm ! Will continue with the other PRs then

ralfbecher pushed a commit to ralfbecher/OSI that referenced this pull request Jun 16, 2026
…a-conformant OSI

Addresses review feedback on this PR. The converter only emitted ANSI_SQL on the
OBML -> OSI path, so a third-party OSI model (Snowflake/Databricks authored, or a
non-SQL dialect) could lose metrics on import with only a warning, contradicting
the README's lossless roundtrip claim.

- Dialect catching: read ANSI_SQL, then SNOWFLAKE, then DATABRICKS expressions
  (SQL engines whose aggregations are ANSI-compatible); MDX/TABLEAU/MAQL are not
  parsed as SQL.
- No silent loss: metrics with no OBML representation are preserved verbatim
  under an OSI-vendor model-level customExtension (obml_unconverted_metrics) and
  re-emitted on OBML -> OSI, with a loud LOSSY: warning. OSI -> OBML -> OSI stays
  lossless.
- Schema conformance (aligns with PR apache#148): stop emitting root-level
  dialects/vendors. The published OSI core schema's root is
  additionalProperties:false (version + semantic_model only). Dialects live
  per-expression in expression.dialects[] and vendors per-entity in
  custom_extensions, the schema-valid homes, so no information is lost. Vendored
  schema updated to match published; a guard test asserts the root arrays are
  now rejected.
- Name-collision safety: a preserved metric whose name a converted metric now
  owns is skipped on export, so the output has no duplicate metric names.
- Idempotency: convert() resets per-run accumulators on both directions.
- Docs: scope the README fidelity claim; refresh stale v0.1.1 references and the
  primary_key entry in the mapping analysis.
- Tests: foreign-OSI fixtures covering both former drop paths, schema
  conformance, name collision, and idempotency.
ralfbecher pushed a commit to ralfbecher/OSI that referenced this pull request Jun 16, 2026
…a-conformant OSI

Addresses review feedback on this PR. The converter only emitted ANSI_SQL on the
OBML -> OSI path, so a third-party OSI model (Snowflake/Databricks authored, or a
non-SQL dialect) could lose metrics on import with only a warning, contradicting
the README's lossless roundtrip claim.

- Dialect catching: read ANSI_SQL, then SNOWFLAKE, then DATABRICKS expressions
  (SQL engines whose aggregations are ANSI-compatible); MDX/TABLEAU/MAQL are not
  parsed as SQL.
- Identifier resolution: parsed dataset.column references are resolved against
  the OSI dataset/field map case-insensitively and with SQL quoting stripped, so
  Snowflake forms like SUM(ORDERS.AMOUNT) or SUM("Orders"."amount") map to the
  real Orders.amount. Unmappable references preserve the metric verbatim instead
  of emitting a dangling reference.
- No silent loss: metrics with no OBML representation are preserved verbatim
  under an OSI-vendor model-level customExtension (obml_unconverted_metrics) and
  re-emitted on OBML -> OSI, with a loud LOSSY: warning. OSI -> OBML -> OSI stays
  lossless.
- Schema conformance (aligns with PR apache#148): stop emitting root-level
  dialects/vendors. The published OSI core schema's root is
  additionalProperties:false (version + semantic_model only). Dialects live
  per-expression in expression.dialects[] and vendors per-entity in
  custom_extensions, the schema-valid homes, so no information is lost. Vendored
  schema updated to match published; a guard test asserts the root arrays are
  now rejected.
- Name-collision safety: a preserved metric whose name a converted metric now
  owns is skipped on export, so the output has no duplicate metric names.
- Idempotency: convert() resets per-run accumulators on both directions.
- Docs: scope the README fidelity claim; refresh stale v0.1.1 references and the
  primary_key entry in the mapping analysis.
- Tests: foreign-OSI fixtures covering both former drop paths, identifier
  resolution, schema conformance, name collision, and idempotency.
@khush-bhatia

Copy link
Copy Markdown
Contributor

@andreybavt Because of the rename, there are some merge conflicts, could you please resolve the conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants