ossie-guard
🇹🇷 Türkçesi: README.tr.md
An honesty & safety linter for Apache Ossie semantic models.
Apache Ossie (the reference implementation of the Open Semantic Interchange) lets one
metric carry a SQL expression for several dialects — ANSI, Snowflake, BigQuery,
Databricks — so the same measure travels across warehouses. Ossie's own
validation/validate.py checks that each expression parses. It does not check
the two things that actually make a metric trustworthy:
- Do the dialects agree? A metric that is
SUM(amount)on ANSI butAVG(amount)on Snowflake parses perfectly and silently returns a different number on every engine. Schema validation never sees it. - Is the expression a pure, reproducible read? A "metric" that calls
pg_read_file(...), or that depends onNOW()/RANDOM(), parses fine too — but it is either a side-effect or non-reproducible.
ossie-guard is the layer that catches both. It is complementary to
validate.py: run the schema validator first, then run ossie-guard.
$ ossie-guard model.yaml
ossie-guard 0.3.1 - model.yaml
ERROR AGGREGATE_DRIFT - revenue
aggregate functions differ across dialects: ANSI_SQL=['SUM']; SNOWFLAKE=['AVG']
at model.yaml:8
WARNING COLUMN_DRIFT - gross_sales
referenced columns differ across dialects; not shared by all: ss_ext_sales_price, ss_sales_price
at model.yaml:16
WARNING LITERAL_DRIFT - revenue_with_tax
numeric constants differ across dialects: ANSI_SQL=['1.08']; SNOWFLAKE=['1.18']
at model.yaml:24
1 error, 2 warnings
(That is the verbatim output for tests/fixtures/drift.yaml
— every example in this README is real tool output, not a mock-up.)
Install
Works today, straight from the repo:
pip install git+https://github.com/gulmezeren2-byte/ossie-guard
A PyPI release (pip install ossie-guard) is on the way — the publish workflow
is wired for Trusted Publishing and fires on the first tagged release.
Dependencies are exactly Ossie's own: pyyaml and sqlglot, nothing else.
Use
ossie-guard model.yaml # human-readable report
ossie-guard models/*.yaml # several models in one run
ossie-guard model.yaml --format json # machine-readable
ossie-guard model.yaml --format sarif # SARIF 2.1.0 for GitHub code scanning
ossie-guard model.yaml -o report.sarif # write to a file instead of stdout
ossie-guard model.yaml --fail-level warning # warnings fail too (default: error)
ossie-guard model.yaml --no-determinism # turn a check off
ossie-guard models/*.yaml --write-baseline .ossie-guard-baseline.json # adopt on an existing model
ossie-guard models/*.yaml --baseline .ossie-guard-baseline.json # fail only on NEW findings
Exit code is 0 when clean, 1 when a finding at or above --fail-level is
present, 2 on a usage/file error. --fail-level none never fails the run
(useful when you only want the report), and --strict / --exit-zero remain as
shorthands for warning / none.
GitHub Action (findings annotated on the pull request)
ossie-guard ships as a composite action that lints your models and uploads
SARIF to code scanning, so each finding appears inline on the exact line of
the offending expression:
# .github/workflows/semantic-model.yml
name: semantic-model
on: [push, pull_request]
permissions:
contents: read
security-events: write # required to upload SARIF
# actions: read # additionally required on PRIVATE repositories
jobs:
ossie-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: gulmezeren2-byte/ossie-guard@v0.3.1
with:
path: models # a file, a directory, or several paths
fail-level: error # error | warning | note | none
Run it after Ossie's own validator, which answers a different question:
- run: python validation/validate.py models/model.yaml # does it parse?
- uses: gulmezeren2-byte/ossie-guard@v0.3.1 # does it agree, and is it pure?
Prefer plain steps? The CLI is just as CI-friendly:
- run: pip install git+https://github.com/gulmezeren2-byte/ossie-guard
- run: ossie-guard models/*.yaml
pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gulmezeren2-byte/ossie-guard
rev: v0.3.1
hooks:
- id: ossie-guard
files: ^models/.*\.ya?ml$ # narrow it to your model directory
As a library
from ossie_guard import lint_file, Severity
findings = lint_file("model.yaml")
for f in findings:
print(f"{f.severity.value} {f.code} {f.entity} (line {f.line}) - {f.message}")
if any(f.severity is Severity.ERROR for f in findings):
raise SystemExit(1)
Need SARIF from Python?
import json
from ossie_guard import __version__, lint_file
from ossie_guard.sarif import to_sarif
doc = to_sarif([("model.yaml", lint_file("model.yaml"))], tool_version=__version__)
json.dump(doc, open("report.sarif", "w"), indent=2)
What it checks
| Code | Severity | What it means |
|---|---|---|
AGGREGATE_DRIFT |
error | The aggregate class differs across a metric's dialects (SUM vs AVG, COUNT vs COUNT DISTINCT). Almost always a bug. |
UNSAFE_FUNCTION |
error | An expression calls a side-effecting function (file/socket/shell/executor): pg_read_file, dblink, xp_cmdshell, load_file, load_extension, … |
COLUMN_DRIFT |
warning | The set of referenced columns differs across dialects — often a copy-paste that left one dialect on the wrong column. |
LITERAL_DRIFT |
warning | A constant used in arithmetic differs across dialects (a tax rate that drifted from * 1.08 to * 1.18). |
PREDICATE_DRIFT |
warning | The filter conditions differ across dialects — a drifted string constant (region = 'EU' vs 'US') or operator (> 100 vs >= 100) changes which rows are counted. |
NONDETERMINISTIC |
warning | The expression uses NOW(), CURRENT_DATE, RANDOM(), UUID(), … — the same run can return different numbers. |
PARSE_ERROR |
info | An expression a parser could not read; deeper checks were skipped for it. |
What it does — and does not — catch
ossie-guard is honest about its own limits, because a linter that overclaims is
worse than none.
The drift checks are a heuristic, not an equivalence prover. True SQL
equivalence is undecidable, and the whole point of multi-dialect expressions is
that they legitimately differ (COALESCE on one engine, NVL on another). So
ossie-guard deliberately compares only a structural signature — aggregate
classes, referenced columns, arithmetic constants, and filter predicates — and
ignores benign dialect spelling. Concretely:
-
✅ It catches
SUMvsAVG, a wrong column, a drifted arithmetic constant,COUNTvsCOUNT DISTINCT, and a drifted filter (a string constant or an operator). -
✅ It does not flag expressions that differ only in idiom. All of these compare equal:
one dialect the other why it's not drift AVG(COALESCE(price, 0))AVG(NVL(price, 0))same signature, different spelling SUM(CASE WHEN s = 1 THEN amt ELSE 0 END)SUM(amt) FILTER (WHERE s = 1)same filter, different construct SUM(CASE WHEN s = 1 THEN amt ELSE 0 END)SUM(IF(s = 1, amt, 0))same filter, BigQuery idiom is_active = TRUEis_active = 1engines spell booleans differently amt > 100100 < amtoperands written the other way round status IN (1, 2)status IN (2, 1)order in an INlist carries no meaningDATE_FORMAT(d, '%Y-%m')FORMAT_DATE('%Y-%m', d)a format string is not a filter (Verified against the official
flightsandtpcdsexample models: zero findings.) -
⚠️ It will not catch a semantic difference that leaves the signature identical — e.g. a join grain that changes the meaning, a different
GROUP BYcontext, or a filter whose columns, operators and values all match but whose boolean structure differs (A AND BvsA OR B). Those need a human or an empirical test.
Treat the errors as high-confidence and the warnings as "a human should look."
Adopting it on a model that already has findings
Run it once, record what is already there, and let CI fail only on new findings:
ossie-guard models/*.yaml --write-baseline .ossie-guard-baseline.json
git add .ossie-guard-baseline.json
- uses: gulmezeren2-byte/ossie-guard@v0.3.1
with:
path: models
baseline: .ossie-guard-baseline.json
A baselined finding is identified without its line number, so reformatting a model will not resurrect it, and entries that no longer occur are reported so the file can be pruned. It is a ratchet, not a mute button.
How it's verified
A linter that claims low false positives should prove it. Every push runs:
| Check | What it proves |
|---|---|
| 62 tests on Python 3.9 / 3.11 / 3.12 / 3.13 / 3.14 | the checks behave the same on every supported runtime |
Zero findings on Apache Ossie's own flights + tpcds examples |
it is not noisy on valid, real-world models |
SARIF validated against the official OASIS 2.1.0 schema (vendored, offline — plus an independent check-jsonschema pass in CI) |
the report GitHub ingests is real SARIF, not "probably valid" |
| The composite action is dogfooded in CI — it must produce a report, fail on a drifting model, and pass on a clean one | the action works as documented, not just in theory |
Why this exists
It comes from the same place as its sibling library readonly-sql-guard — the side-effecting-function denylist here is ported from it — and from erp-report-engine: the belief that "read-only," "reproducible," and "the same on every engine" should be properties a tool measures, not adjectives a model claims. A semantic layer is the one place a wrong number propagates to every dashboard downstream; it deserves a check that looks past "does it parse."
Development
pip install -e ".[dev]" # adds pytest + jsonschema (for the SARIF schema test)
python -m pytest -q
The package itself depends only on pyyaml and sqlglot; everything in [dev]
is test-only.
License
Apache-2.0 — the same license as Apache Ossie, so this can live comfortably beside it. Not an official Apache project; "Apache Ossie" is referenced for interoperability.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ossie_guard-0.3.1.tar.gz.
File metadata
- Download URL: ossie_guard-0.3.1.tar.gz
- Upload date:
- Size: 49.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd0453ee66d8414f6fb98f230db6948a18750b9c288b6c44f6a06571fda2ef2b
|
|
| MD5 |
70d8c0d192ae912b41101d987f6a1499
|
|
| BLAKE2b-256 |
3aa5dd275c31d7498f4d724bce1a5f3e5e02bc3219b3a92c690386e915b35102
|
Provenance
The following attestation bundles were made for ossie_guard-0.3.1.tar.gz:
Publisher:
publish.yml on gulmezeren2-byte/ossie-guard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ossie_guard-0.3.1.tar.gz -
Subject digest:
dd0453ee66d8414f6fb98f230db6948a18750b9c288b6c44f6a06571fda2ef2b - Sigstore transparency entry: 2279680073
- Sigstore integration time:
-
Permalink:
gulmezeren2-byte/ossie-guard@0d2275859c9115b3fb5e25b4daf885b47f7c0f3e -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/gulmezeren2-byte
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0d2275859c9115b3fb5e25b4daf885b47f7c0f3e -
Trigger Event:
release
-
Statement type:
File details
Details for the file ossie_guard-0.3.1-py3-none-any.whl.
File metadata
- Download URL: ossie_guard-0.3.1-py3-none-any.whl
- Upload date:
- Size: 29.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57e477faae5498df5e97e0d4c65afe7d947ba7cc17e8ff1ec080a8867cd9291e
|
|
| MD5 |
6b2c03476574e4fa91592ab86dc12e4a
|
|
| BLAKE2b-256 |
a8bf02334ae65ca404bec37757a5ea6178e68d8a9683e2b48ee9a28ab4443e3f
|
Provenance
The following attestation bundles were made for ossie_guard-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on gulmezeren2-byte/ossie-guard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ossie_guard-0.3.1-py3-none-any.whl -
Subject digest:
57e477faae5498df5e97e0d4c65afe7d947ba7cc17e8ff1ec080a8867cd9291e - Sigstore transparency entry: 2279680090
- Sigstore integration time:
-
Permalink:
gulmezeren2-byte/ossie-guard@0d2275859c9115b3fb5e25b4daf885b47f7c0f3e -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/gulmezeren2-byte
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0d2275859c9115b3fb5e25b4daf885b47f7c0f3e -
Trigger Event:
release
-
Statement type: