SMF Reference Specification v0.1¶
Status: Draft. Breaking changes are expected before v1.0.
Type: Normative for document shape and resolution states; the reference resolver in tools/ is non-normative.
Audience: technical teams implementing SMF practices in tools and AI systems. You do not need this to adopt SMF; the framework in docs/ is written for business and analytics leaders.
This reference gives SMF artifacts (concepts, perspectives, Metric Contracts, answer rules, checks) a common file format so tools, agents and tests can exchange them.
1. Documents¶
Every SMF document is a YAML (or JSON) object with two required envelope fields:
smf: "0.1" # spec version
kind: Concept # document kind
A file may contain several documents separated by ---.
A folder of SMF documents may hold other files. The reference CLI reads a file as SMF only when at least one of its documents carries the smf: key or names an SMF kind; any other YAML or JSON file (a CI workflow, a tool's configuration, a data contract in another standard) is skipped and counted, and folders whose name starts with a dot are not walked. A document that names an SMF kind and omits smf: is an error, never a skip. See where the files live.
| Kind | Purpose | Module | Schema |
|---|---|---|---|
Concept |
Stable identity for a business meaning | Core / Modeling | concept |
Term |
Label that maps to a concept | Core / Modeling | term |
Perspective |
A legitimate, owned meaning of a concept (e.g., Service Gross Margin) | Core | perspective |
Context |
The situation of a question; selects the perspective | Core | context |
Ownership |
Owner and scope for a subject | Core / Authority | ownership |
ClassificationRule |
What counts as an instance of a concept | Authority / Modeling | classification-rule |
Conflict |
Declared, unresolved disagreement | Authority | conflict |
ResolutionRule |
Default and contextual resolution for a term | Resolution | resolution-rule |
ResolutionResult |
Output of resolving a term in context | Resolution | resolution-result |
MetricContract |
Governed measurement for one perspective, including comparability | Measurement | metric-contract |
Binding |
Link from contract/concept to an implementation | Implementation | binding |
TestCase |
Expected behavior for a consumer or implementation | Assurance | test-case |
ClaimTrace |
Trace for a specific quantitative claim | Assurance | claim-trace |
Schemas use JSON Schema draft 2020-12. Schema IDs are URNs (urn:semantic-management-framework:0.1:schema:<name>) until a permanent home exists.
Semantic Contracts in machine form¶
- A Metric Contract is the
MetricContractdocument (plusBindingdocuments for where it's implemented). Field names align with those commonly used for metric contracts: expression (formula),grain,valid_dimensions,filters,time_semantics,additivity,owner, certification (certified_for),version. SMF addsperspective, approved uses,comparabilityandbuilt_on(links to data contracts). - Every row of the one-page Metric Contract has a field. Beyond those above:
plain_words("In plain words"),inclusionsandexclusions("Includes / excludes"),certified_forandnot_certified_for("Approved for" / "Not approved for"),effective_from,change_note, andsign_off {by, date}. "Maintained by" isOwnership.steward.Conceptcarriesplain_words,change_noteandsign_offfor the Definition Contract. All are optional. - A Definition Contract is a set of documents:
Concept, itsPerspectives,Terms,Ownership, anyClassificationRules and theResolutionRulefor the term. - To link from an ODCS data contract to an SMF contract, use ODCS
authoritativeDefinitionswith typebusinessDefinition. In the other direction, aMetricContractlists the data contracts it is built on inbuilt_on. For the business-level picture, see docs/contracts-in-context.md. - A
Bindingnames one implementation of a contract (implementation.platformandref). It may carry anidso aClaimTracecan name it. Two optional fields support Assurance:implementation.derived_fromrecords the implementation a converter generated this one from, andexpression_snapshotkeeps the executable logic (or its SHA-256 digest) as it was whenconformance_statuswas last assessed, so a tool can report drift instead of a reviewer re-reading the formula. - A
Perspectivemay setbroader_than(a list of perspective references) when it contains other perspectives, as Consolidated Gross Margin contains Product and Service. It records containment only. Comparability stays on theMetricContract.
Linking an ODCS data contract to an SMF contract (illustrative)¶
# Inside an ODCS data contract
properties:
- name: landed_cost_of_goods
authoritativeDefinitions:
- type: businessDefinition
url: https://example.org/semantic/metric-contracts/product-gross-margin
Overlap with ODCS 3.2 context and semanticType¶
ODCS 3.2 added an AI context block and a semanticType tag (column, measure, dimension) to data contracts. They describe one dataset. SMF documents describe meaning across datasets: perspectives, owners, comparability and the five resolution states. Use both, and do not duplicate one inside the other.
Sources:
- ODCS v3.2 schema (
authoritativeDefinitions): https://bitol-io.github.io/open-data-contract-standard/v3.2.0/schema/ - ODCS v3.2 / ODPS v1.1 release notes (AI
context,semanticType): https://www.entropy-data.com/news/2026-09-08-odcs-3-2-odps-1-1
Linking an ODPS data product to an SMF contract (illustrative)¶
An ODPS data product packages datasets behind output ports. Each port names the data contract it serves (contractId) and that contract's version. ODPS carries authoritativeDefinitions at the product level and on each port, so a data product points at a Metric Contract the same way an ODCS field does:
# Inside an ODPS data product
authoritativeDefinitions:
- type: businessDefinition
url: https://example.org/semantic/metric-contracts/product.gross_margin
outputPorts:
- name: order-lines
version: 1.2.0
contractId: sales_order_lines
In the other direction nothing new is needed: MetricContract.built_on names the data contract, and the data product is found from the port whose contractId matches. A MetricContract is built on data contracts, not on data products, so a product can be repackaged without changing the contract. ODPS describes no metrics, measures or calculations; it is a Data-column standard.
Sources:
- ODPS v1.1.0 schema: https://github.com/bitol-io/open-data-product-standard/blob/main/schema/odps-json-schema-v1.1.0.json
Crosswalk: one Metric Contract, four standards (non-normative)¶
Where each part of a MetricContract shows up in the standards around it. "—" means the standard does not carry it, which is the reason the contract exists. The worked files are in examples/gross-margin.
MetricContract field |
Apache Ossie metric | dbt MetricFlow metric | ODCS data contract | ODPS data product |
|---|---|---|---|---|
id, version |
— (name is unique in one model only) |
— (name is unique in one project only) |
— | — |
name |
name, description |
label, name |
— | — |
plain_words |
description |
description |
— | — |
measures.concept_ref, perspective |
— | — | — | — |
formula |
expression.dialects[].expression (executable SQL) |
agg + expr, or expr + input_metrics, or numerator / denominator (executable) |
— | — |
grain |
implicit in the dataset primary_key |
implicit in the primary entity |
schema[].dataGranularityDescription |
— |
valid_dimensions |
fields with dimension (everything reachable; no allow-list per metric) |
dimension columns (everything reachable; no allow-list per metric) |
— | — |
filters, inclusions, exclusions |
inside the SQL | filter |
— | — |
time_semantics |
dimension.is_time |
agg_time_dimension, column granularity |
— | — |
additivity |
— | non_additive_dimension (semi-additive only) |
— | — |
owner, sign_off |
— | config.meta (free-form), config.group |
team (of the dataset, not the number) |
team (of the product, not the number) |
status, effective_from, change_note |
— | — | status, version (of the dataset) |
status, version (of the product) |
certified_for, not_certified_for |
— | — | — | — |
comparability |
— | — | — | — |
built_on |
dataset source (a table, not a contract) |
ref() to a model (a table, not a contract) |
the contract's own id and version |
outputPorts[].contractId and version |
| pointer back to the contract | custom_extensions[], vendor_name: SMF (convention) |
config.meta.smf (convention) |
authoritativeDefinitions[], type: businessDefinition |
authoritativeDefinitions[], type: businessDefinition |
where it is implemented (Binding) |
platform: ossie |
platform: dbt |
— | — |
Reading the columns: Ossie and dbt carry the calculation and nothing about who agreed to it. ODCS and ODPS carry ownership, status and version of the data, and nothing about the number. The rows where all four are "—" (concept, perspective, approved uses, comparability) are what only the Metric Contract records.
Field mapping: dbt MetricFlow metric and MetricContract (non-normative)¶
Written against the dbt 1.12 YAML spec, where simple metrics sit on the model and other metric types sit under a top-level metrics: key. In dbt 1.11 and earlier the same information sits in semantic_models[].measures and metrics[].type_params; config.meta is available in both.
| dbt metric key | MetricContract field |
Note |
|---|---|---|
name |
id / name |
dbt names are unique within one project. SMF ids are namespaced and stable across projects; record the dbt name in a Binding, not as the SMF id |
label |
name |
Display name |
description |
plain_words |
Free text in both; dbt has no concept reference |
type (simple, ratio, derived, cumulative, conversion) |
— | How dbt builds the number. Stays in the implementation |
agg, expr, input_metrics, numerator, denominator |
formula |
dbt holds the executable definition; formula is the plain-words calculation that points at it |
filter |
filters, inclusions, exclusions |
One Jinja expression in dbt; three reviewable lists in SMF |
agg_time_dimension, column granularity |
time_semantics |
dbt names the column and its grain; the contract also says fiscal or calendar and which date counts |
non_additive_dimension |
additivity |
dbt marks semi-additive measures only. A ratio's non-additivity is implied by its type |
model columns[].dimension |
valid_dimensions |
dbt offers every dimension reachable through entities; the contract lists the ones that are approved |
model columns[].entity (primary) |
grain |
Implicit in dbt; stated in SMF |
config.group |
owner |
A dbt group owns code. The contract's owner is accountable for the meaning |
config.meta |
— | The slot for a pointer back to the SMF contract; see the example below |
| — | perspective, measures.concept_ref |
dbt has no perspective; two meanings of one metric are two unrelated metric names |
| — | certified_for, not_certified_for, status, effective_from, sign_off, version |
dbt carries no approval, status or change history for a metric beyond git |
| — | comparability |
Not representable in dbt |
| — | built_on |
A dbt model can carry its own data contract; a metric does not reference one |
Linking a dbt metric to an SMF contract (illustrative)¶
Use config.meta. The smf key and the keys inside it are a convention proposed here, not part of dbt. dbt writes meta into semantic_manifest.json, so a tool can read the pointer without parsing the project's YAML.
# Inside a dbt project (dbt 1.12 YAML spec)
metrics:
- name: product_gross_margin
description: Product Gross Margin (product perspective; see the SMF contract for approved uses and comparability)
type: derived
label: Product Gross Margin
expr: (product_revenue - landed_cost_of_goods) / product_revenue
input_metrics:
- name: product_revenue
- name: landed_cost_of_goods
config:
meta:
smf:
metric_contract: product.gross_margin@v1
perspective: perspective.gross_margin.product
In the other direction, a Binding records the dbt metric as one implementation of the contract:
smf: "0.1"
kind: Binding
subject: product.gross_margin@v1
implementation: { platform: dbt, ref: "metrics.product_gross_margin" }
expression_snapshot:
text: (product_revenue - landed_cost_of_goods) / product_revenue
sha256: 170037c59f088976a7416c969b1617527e13fcd1dc3a81019d3106fa4a7da814
captured_on: 2026-10-09
conformance_status: conformant
For a derived or ratio metric the snapshot holds the metric's own expression. Its input metrics have their own filters and aggregations, so a change to an input does not change this digest; snapshot the inputs too, or bind them to their own contracts, when that matters. Apache Ossie ships a dbt converter, so when a dbt metric was generated from an Ossie model (or the reverse), record that with derived_from.
Sources:
- dbt metrics (v1.12 spec): https://docs.getdbt.com/docs/build/metrics-overview
- dbt semantic models (v1.12 spec): https://docs.getdbt.com/docs/build/semantic-models
Field mapping: Apache Ossie metric and MetricContract (non-normative)¶
Apache Ossie is a draft interchange format (spec 0.2.0.dev0; one model per document, no bundle or cross-model references). Its metrics entries carry name, expression (SQL per dialect), description, datatype, ai_context and custom_extensions. The table shows how each lines up with a MetricContract.
| Ossie metric field | MetricContract field |
Note |
|---|---|---|
name |
id / name |
Ossie names are unique within one model only. SMF ids are namespaced and stable across models; record the Ossie name in a Binding, not as the SMF id |
expression.dialects[].expression |
formula |
Ossie holds executable SQL; formula is the plain-words calculation that points at it. Ossie is "where it computes"; the contract is "where it agrees" |
description |
name, measures.concept_ref |
Free text in Ossie; a concept reference in SMF |
datatype |
— | Not carried by SMF; stays in the implementation |
ai_context.synonyms |
Term documents |
Ossie synonyms are per metric per model; SMF terms resolve per context with a ResolutionRule and the five states |
ai_context.instructions, ai_context.examples |
ResolutionRule, TestCase |
Model-specific hints versus governed answer rules and checks that can be run |
custom_extensions[] (vendor_name, JSON data) |
— | The slot for a pointer back to the SMF contract; see the example below |
| — | perspective |
Ossie has no perspective; two meanings of one metric are two unrelated metric names |
| — | grain, valid_dimensions, filters, exclusions, time_semantics, additivity |
Implicit in the Ossie SQL and relationships; explicit and reviewable in SMF |
| — | owner, certified_for, status, effective_from, version |
Ossie carries no ownership, approval, status or change history |
| — | comparability |
Not representable in Ossie |
| — | built_on |
Ossie datasets have a source; they do not reference data contracts |
Ossie model- and dataset-level ai_context also overlaps with ODCS 3.2 context: both are hints for one artifact. SMF is the cross-artifact layer for both.
Linking an Apache Ossie metric to an SMF contract (illustrative)¶
Ossie has no equivalent of ODCS authoritativeDefinitions; use a custom_extensions entry. The vendor_name and the keys inside data are a convention proposed here, not part of the Ossie spec.
# Inside an Apache Ossie semantic model document
metrics:
- name: product_gross_margin
expression:
dialects:
- dialect: ANSI_SQL
expression: (SUM(order_lines.product_revenue) - SUM(order_lines.landed_cost_of_goods)) / SUM(order_lines.product_revenue)
description: Product Gross Margin (product perspective; see the SMF contract for approved uses and comparability)
ai_context:
instructions: "Governed by SMF contract product.gross_margin@v1. Not comparable with services.gross_margin."
custom_extensions:
- vendor_name: SMF
data: '{"metric_contract": "product.gross_margin@v1", "perspective": "perspective.gross_margin.product"}'
In the other direction, a Binding records the Ossie model and metric as one implementation of the contract, alongside any BI measure or semantic-layer metric:
smf: "0.1"
kind: Binding
subject: product.gross_margin@v1
implementation: { platform: ossie, ref: "sales_analytics/metrics/product_gross_margin" }
expression_snapshot:
text: (SUM(order_lines.product_revenue) - SUM(order_lines.landed_cost_of_goods)) / SUM(order_lines.product_revenue)
sha256: eb9475197f844c6cbf36b26528111818268eab92596a2f3fab3d149152768a9b
captured_on: 2026-10-08
conformance_status: untested
Because Ossie is a hub format, the same contract will often have one Ossie binding and several bindings that a converter produced from it. Record that with derived_from so Assurance can test the hub once and treat the spokes as generated:
smf: "0.1"
kind: Binding
subject: product.gross_margin@v1
implementation:
platform: snowflake
ref: "ANALYTICS.SEMANTIC.SALES_ANALYTICS / PRODUCT_GROSS_MARGIN"
derived_from: { platform: ossie, ref: "sales_analytics/metrics/product_gross_margin" }
conformance_status: untested
Assurance then treats drift between the Ossie expression and the snapshot on its binding as a finding, as it does for any other binding. Whether the Ossie expression still matches the contract's plain-words formula remains a reviewer's call; the snapshot makes the drift check mechanical, not the judgement.
Sources:
- Apache Ossie core specification (
0.2.0.dev0, draft): https://github.com/apache/ossie/blob/main/core-spec/spec.md
Relationships across standards¶
The framework's metamodel draws the connections between data contracts, SMF documents, implementations and decisions. This table gives the field that records each connection. Where two standards record the same connection from opposite ends, both rows are listed; they must agree, and neither restates the other.
| Connection (plain name) | From | To | Field that records it | Owning spec |
|---|---|---|---|---|
| built on | MetricContract |
data contract | MetricContract.built_on[] |
SMF |
| points to its business definition (same connection, other end) | ODCS field | MetricContract |
properties[].authoritativeDefinitions[] with type: businessDefinition |
ODCS 3.x |
| has | Concept |
Perspective |
Perspective.concept_ref |
SMF |
| served by (same connection, from the data product) | ODPS data product or output port | MetricContract |
authoritativeDefinitions[] with type: businessDefinition; outputPorts[].contractId and version name the data contract in built_on |
ODPS 1.x |
| contains | Perspective |
Perspective |
Perspective.broader_than[] |
SMF |
| measured by | Perspective |
MetricContract |
MetricContract.perspective (and MetricContract.measures.concept_ref to the concept) |
SMF |
| what counts | Concept |
ClassificationRule |
ClassificationRule.concept_ref; MetricContract.classification_rules[] |
SMF |
| comparable / not comparable with | MetricContract |
MetricContract |
comparability.comparable_with[], comparability.not_comparable_with[], comparability.can_be_combined |
SMF |
| implemented in | MetricContract (or Concept) |
implementation | Binding.subject → Binding.implementation {platform, ref} |
SMF |
| generated into | implementation | implementation | Binding.implementation.derived_from {platform, ref} |
SMF |
| as it was when last checked | Binding |
expression | Binding.expression_snapshot {text?, sha256, captured_on}; Binding.conformance_status |
SMF |
| points to its contract (same connection, other end) | Ossie metric | MetricContract |
metrics[].custom_extensions[] with vendor_name: SMF, data: {"metric_contract": "id@vN", ...} (convention, not part of the Ossie spec) |
Apache Ossie |
| points to its contract (same connection, other end) | dbt metric | MetricContract |
metrics[].config.meta.smf {metric_contract: "id@vN", ...} (convention, not part of dbt) |
dbt MetricFlow |
| selects | context | ResolutionRule clause |
ResolutionRule.contextual[].when matched against the query context; Context.conditions names the contexts |
SMF |
| applies to | ResolutionRule |
Concept / MetricContract |
default and contextual[].resolve_to {concept, measurement} |
SMF |
| one of five answers | ResolutionRule |
consumer | ResolutionResult.state with basis, options, conflict_ref, replaces |
SMF |
| owned by | Concept / Perspective / MetricContract |
person or role | Ownership.subject → owner, steward, scope; Perspective.owner; MetricContract.owner; approval in sign_off {by, date} on MetricContract and Concept |
SMF |
| undecided | term | candidates | Conflict.candidates[], owner, target_date; ResolutionRule.contextual[].conflict_ref |
SMF |
| replaced by | Concept / term |
Concept / MetricContract |
Concept.replaced_by; ResolutionRule.deprecated.replacement |
SMF |
| backed by | ClaimTrace |
MetricContract version |
ClaimTrace.measurement (id@vN), ClaimTrace.concept |
SMF |
| computed by | ClaimTrace |
implementation and run | ClaimTrace.execution {binding, query_hash, executed_at}, sources[]. execution.binding is a Binding id, or <platform>:<ref> matching a Binding in the set |
SMF |
| run lineage (same connection, other end) | ClaimTrace |
lineage run | ClaimTrace.provenance {format: openlineage, ref} |
OpenLineage |
| tests | TestCase |
consumer or resolver | TestCase.input / expected; consumer results keyed by test id |
SMF |
The plain-language gap catalogue lists what it means when one of these connections is missing. Most gaps can be found from the documents alone with the fields above; drift needs the implementation's current expression, and "check never run" needs a results file.
2. Identifiers¶
- Pattern: lowercase, dot-namespaced, at least two segments:
concept.customer,finance.net_revenue. - Version pin:
id@vN, e.g.finance.net_revenue@v3. - IDs are never derived from labels alone and never reused for a different meaning.
- A document may reference an ID defined elsewhere (another repo, a catalog). Validators warn about undefined references unless run in strict mode.
- A
ClaimTracenames the build record that computed it inexecution.binding, either by theBinding'sidor as<platform>:<ref>. A name that matches noBindingin the set is a warning, and an error in strict mode. A name that matches only build records of a different Metric Contract than the claim'smeasurementis an error. - A reference must point at the right kind of document: a metric field names a
MetricContract, a concept field names aConcept, a perspective field names aPerspective. A reference to a document of the wrong kind is an error, in strict mode or not. - A term has at most one
ResolutionRule, and oneTermper context. A second rule for the same term, or twoTermdocuments for the same term and context that map to different concepts, is a validation error. A resolver that finds more than one rule for a term returnsCONFLICTrather than choosing one.
3. Resolution states (the five answers)¶
Plain-language names used in the framework docs are shown in brackets.
A ResolutionResult has exactly one state:
| State | Required fields |
|---|---|
RESOLVED [use it] |
concept, basis |
RESOLVED_VIA_REPLACEMENT [use the replacement] |
concept, replaces |
NEEDS_CLARIFICATION [ask] |
options (2 or more) |
CONFLICT [flag the conflict] |
conflict_ref or conflict_owner |
UNGOVERNED [not governed] |
none |
Consumers that claim SMF conformance must not present a guess as RESOLVED.
4. Reference resolution algorithm¶
Implementations may resolve however they like, as long as their results match the states. The reference algorithm (reference/tools/smf.py resolve) is:
- No rule for the term (after checking aliases) →
UNGOVERNED. An alias is aTermthat maps to a concept whose preferred term has a rule. Candidates are tried preferred terms first, then alphabetically, never in file order, and rules for deprecated terms are skipped. - Rule is deprecated →
RESOLVED_VIA_REPLACEMENTtodeprecated.replacement. - Contextual clauses: a clause matches when every key in
whenequals the context value. Values are compared as text and are case-sensitive; a YAML boolean compares astrueorfalse. Context keys are free-form: the adopter chooses them (for examplebusiness_areaoraudience).- The most specific match (most keys) wins.
- Equally specific matches with different outcomes →
CONFLICT. - A clause either resolves (
resolve_to) or returns a state (NEEDS_CLARIFICATION,CONFLICT,UNGOVERNED).
- No clause matches → apply
default. - Resolved concept is superseded or retired with
replaced_by→RESOLVED_VIA_REPLACEMENT. If the replacement is itself superseded, follow the chain to the current concept. - Attach the
perspectiveof the selected metric contract,constraintsfrom the metric contract (exclusions, valid dimensions, time semantics, grain) andtrust(registration status, certification).
5. Conformance levels (draft)¶
| Level | A conforming implementation… |
|---|---|
| Reader | Parses and validates SMF documents |
| Resolver | Produces valid ResolutionResult documents using the five states |
| Consumer | Honors all five states: proceeds, discloses replacement, asks, discloses conflict, discloses ungoverned |
| Tested consumer | Publishes passing results for a declared set of TestCase documents |
How smf.py test checks a consumer's results file (a mapping of test id to ResolutionResult):
- The file may only contain ids that match a
TestCase; an unknown id is an error, so a misspelled id can never hide a failing case. - Each result must be a valid
ResolutionResult. statemust match.concept,measurementandperspectivemust match when the case names them, andoptionsmust be the same set.- A result must not use anything listed in
must_not. - Each name in
must_applymust appear in the result'sconstraints.exclusions, which is where a result reports the exclusions of its Metric Contract.
6. Relationship to other specs¶
SMF does not redefine what other open specs already carry. See docs/works-with.md.
- Dataset-level contracts, AI
contextblocks and field synonyms: ODCS (Bitol) - Data products, their ports and the data contract version behind each port: ODPS (Bitol)
- Portable semantic models and metric expressions: Apache Ossie
- Executable metric definitions: dbt MetricFlow and other semantic layers
- Lineage and run provenance: OpenLineage;
ClaimTrace.provenancecan point to an OpenLineage run - Glossary and metadata type system: Egeria
- Concept mapping relations: SKOS
- Registration statuses: ISO/IEC 11179-6
7. Try it¶
pip install -r reference/tools/requirements.txt
python reference/tools/smf.py validate reference/examples/gross-margin --strict
python reference/tools/smf.py resolve reference/examples/gross-margin --term "gross margin" --context audience=leadership
python reference/tools/smf.py test reference/examples/gross-margin
validate and test print text by default. Add --format json for one JSON object with the same errors, warnings and counts, for use by other tools.
8. Open questions¶
- Should
ResolutionRulesupport ordered precedence in addition to specificity? - Should
Contextsupport ranges (effective periods) natively? - Should resolution states be proposed upstream as an ODCS extension and an OpenLineage facet?
- Should Apache Ossie carry a native pointer to a governing definition (the equivalent of ODCS
authoritativeDefinitions) instead of thecustom_extensionsconvention above?