Context-aware bindings
Model the same business concept across multiple source systems, standards, and jurisdictions — with provenance, transforms, and confidence on every projected value.
This is the authoring reference (how to build it with the SDK). For the concept — what a contextual ontology is and when you need one — start with Contextual ontologies. This guide also extends the Ontologies authoring guide: context-aware bindings are additive fields on the same defineBinding, defineObjectType, and defineOntology helpers.
Why context-aware bindings exist
A canonical ontology says Payment has a monetaryAmount property. But the same business concept looks different across every source system that feeds it:
| What you call it | Where it lives | Difference |
|---|---|---|
amount | Core banking (pacs.008, BE) | Integer cents, outgoing, value-date semantics |
trxAmt | Payments hub (legacy ISO 8583) | Decimal string, acquiring, settlement-date semantics |
gross_amount | Finance reporting (IFRS lens) | Net + fees, accounting-date semantics |
If you flatten all three into one monetaryAmount column without capturing that context, your AI, reporting, and compliance automation operate on ambiguous data. A compliance query for "all cross-border payments over €10 000" returns different answers depending on which binding ran last — and you cannot explain which one.
Context-aware bindings solve this by recording on every projected value:
- Which source system and message standard produced it.
- What transforms were applied (currency unit, timezone, divide-by-100, …).
- Whether those transforms were lossless.
- Confidence the mapping is correct.
- A validity window (
validFrom/validTo).
Reads return all of this as a provenance envelope alongside { id, properties }, so callers can see and explain divergence between sources rather than hiding it behind a silent golden record.
bindingContext — describing your source system
Add a bindingContext block to any defineBinding(...) call. All fields are optional — supply only what is meaningful for your source.
import { defineBinding } from '@scrydon/sdk-authoring/ontologies'
defineBinding({
objectTypeSlug: 'Payment',
kind: 'silver_table',
source: { table: 'silver_payments_outgoing' },
identitySpec: { keys: ['msg_id'] },
columnMap: {
monetary_amount: 'amount_cents',
value_date: 'trade_ts_local',
counterparty_lei: 'customer_id',
},
bindingContext: {
sourceSystem: 'core_banking_x',
integrationVersion: '3.2',
standard: 'ISO20022',
messageType: 'pacs.008',
direction: 'outgoing',
jurisdiction: 'BE',
dataResidency: 'eu',
businessProcess: 'credit_transfer',
timestampSemantics: 'value_date',
currencySemantics: 'transaction_currency',
aggregationLevel: 'transaction_level',
confidence: 0.96,
validFrom: '2025-01-01',
validTo: null,
},
})Field reference
| Field | Type | Notes |
|---|---|---|
sourceSystem | string | Slug identifying the enterprise system (e.g. "core_banking_x", "salesforce_crm"). |
integrationVersion | string | API or schema version of the source (e.g. "3.2", "v2024-01"). |
standard | string | Message standard, if applicable (e.g. "ISO20022", "FIX42", "HL7_FHIR"). |
messageType | string | Standard-specific message type (e.g. "pacs.008", "MT103", "Claim"). |
direction | string | Flow direction — "inbound", "outgoing", or any free-form value. |
jurisdiction | string | ISO country or region code (e.g. "BE", "EU", "US-NY"). |
dataResidency | string | Data-residency zone (e.g. "eu", "us", "on-prem"). |
businessProcess | string | High-level business process (e.g. "credit_transfer", "claims_adjudication"). |
timestampSemantics | string | What timestamps mean — "value_date", "settlement_date", "accounting_date", or any free-form value. |
currencySemantics | string | Which currency view — "transaction_currency", "reporting_currency", etc. |
aggregationLevel | string | Grain of the data — "transaction_level", "position_level", "daily_snapshot", etc. |
confidence | number (0–1) | Mapping confidence for this source. Flows into the provenance envelope on every projected instance. |
validFrom | string | ISO 8601 date string for when this mapping became valid. |
validTo | string | null | ISO 8601 date string for when this mapping expires, or null for open-ended. |
transformMap — per-property transforms
The flat columnMap maps property slugs to source column names. transformMap layers richer transform descriptors on top. When a property appears in transformMap, the projector uses entry.source as the source column and applies entry.transform; otherwise it falls back to columnMap. Both can coexist on the same binding.
defineBinding({
objectTypeSlug: 'Payment',
kind: 'silver_table',
source: { table: 'silver_payments_outgoing' },
identitySpec: { keys: ['msg_id'] },
columnMap: {}, // transformMap entries take precedence
bindingContext: {
sourceSystem: 'core_banking_x',
standard: 'ISO20022',
messageType: 'pacs.008',
jurisdiction: 'BE',
confidence: 0.96,
},
transformMap: {
monetary_amount: {
source: 'amount_cents',
transform: 'divide_by_100',
unit: 'EUR',
lossless: true,
},
value_date: {
source: 'trade_ts_local',
transform: 'timezone_normalize',
tz: 'Europe/Brussels',
lossless: false,
notes: 'DST ambiguity on clock-change dates',
},
counterparty_lei: {
source: 'customer_id',
transform: 'resolve_via_mdm',
lossless: false,
confidence: 0.88,
},
},
})Transform vocabulary
The allowed transforms are a closed, allow-listed set. Passing a string not in this list causes the property to project null and be marked as a lossy property — the projector never silently mis-types a value.
| Transform ID | What it does | Lossless? |
|---|---|---|
identity | Passes the value through unchanged. | Yes |
divide_by_100 | Divides the numeric value by 100 (e.g. cents → currency unit). | Yes |
multiply_by_100 | Multiplies the numeric value by 100. | Yes |
regex_extract | Applies a regex capture group to the string value. Use args: { pattern: "..." } to supply the expression. | Depends |
timezone_normalize | Normalises a local timestamp to UTC using the tz field. | No (DST ambiguity) |
currency_normalize | Records the currency unit from unit. Value is passed through unchanged until an FX provider is configured. | No |
resolve_via_mdm | Looks up the value in a Master Data Management system. Passes the value through until an MDM resolver is configured. | No |
currency_normalize and resolve_via_mdm are metadata-only stubs today — they record that the transform should occur and mark the property lossless: false, but they do not rewrite the value. When an FX provider or MDM resolver is configured on your deployment, the platform will automatically use it. Until then, treat the projected value as the raw source value.
TransformDescriptor field reference
| Field | Type | Notes |
|---|---|---|
source | string | Source column expression (overrides columnMap[propertySlug] when present). Required. |
transform | TransformId | One of the allow-listed transform IDs above. Optional — omit to pass the value through as-is. |
unit | string | Currency code or other unit (e.g. "EUR", "USD"). Used by currency_normalize and informational for divide_by_100. |
tz | string | IANA timezone name (e.g. "Europe/Brussels"). Used by timezone_normalize. |
lossless | boolean | Whether the transform is lossless. When false, the property appears in provenance.lossyProperties. |
confidence | number (0–1) | Per-property mapping confidence, overrides the binding-level confidence for this property. |
notes | string | Free-form note for documentation and audit purposes. |
args | Record<string, unknown> | Additional per-transform arguments (e.g. { pattern: "^([A-Z]{2}\\d+)" } for regex_extract). |
Standards anchors — linking to FIBO, ISO 20022, CDM, and LEI
Object Types and Properties can carry optional anchors that link a canonical concept to an external standard. Anchors are purely informational — they do not change how the platform stores or projects data, but they are surfaced in the ontology workbench and in audit provenance.
import {
defineObjectType,
defineProperty,
} from '@scrydon/sdk-authoring/ontologies'
defineObjectType({
slug: 'Payment',
displayName: 'Payment',
kind: 'analytical',
anchors: [
{
standard: 'FIBO',
conceptId: 'fibo-fbc-fct-fse:Payment',
uri: 'https://spec.edmcouncil.org/fibo/ontology/FBC/FunctionalEntities/FinancialServicesEntities/',
label: 'Payment',
},
{
standard: 'ISO20022',
conceptId: 'pacs.008.CreditTransfer',
label: 'Credit Transfer Initiation',
},
],
properties: [
defineProperty({
slug: 'monetary_amount',
displayName: 'Monetary amount',
scalarType: 'float',
anchors: [
{ standard: 'ISO20022', conceptId: 'Pacs.008.IntrBkSttlmAmt', label: 'Interbank Settlement Amount' },
{ standard: 'FINOS_CDM', conceptId: 'rosetta:Money', label: 'Money' },
],
}),
defineProperty({
slug: 'value_date',
displayName: 'Value date',
scalarType: 'date',
anchors: [
{ standard: 'ISO20022', conceptId: 'Pacs.008.IntrBkSttlmDt', label: 'Interbank Settlement Date' },
],
}),
],
})Supported standards
| Value | Description |
|---|---|
FIBO | EDMC Financial Industry Business Ontology (OWL 2 DL) |
ISO20022 | ISO 20022 Business Model concepts and message components |
FINOS_CDM | FINOS Common Domain Model (Rosetta) |
LEI | Legal Entity Identifier (GLEIF) |
These are free-form strings — the platform validates that standard, conceptId, and optionally uri and label are present, but it does not resolve URIs or validate concept IDs against the external standards themselves. Local-language concept names take precedence; anchors are an optional layer for interoperability and audit traceability.
Context-scoped aliases — alias + context → concept
Different source systems and standards use different local terms for the same canonical concept. The aliases array at the top level of defineOntology(...) lets you register those term-to-concept mappings — scoped to a context so the same term can map to different concepts depending on where it appears.
Aliases are always context-scoped. There is no global alias table. The same local term ("Debtor") can map to Counterparty under one standard and to LegalEntity under another — by design.
Use defineAlias(...) from @scrydon/sdk-authoring/ontologies:
import {
defineOntology,
defineAlias,
} from '@scrydon/sdk-authoring/ontologies'
defineOntology({
id: 'acme-payments',
// ... objectTypes, bindings, etc.
aliases: [
// "Debtor" in pacs.008 outgoing payments → the canonical Counterparty object type
defineAlias({
alias: 'Debtor',
objectTypeSlug: 'Counterparty',
contextSelector: {
standard: 'ISO20022',
messageType: 'pacs.008',
direction: 'outgoing',
},
confidence: 0.97,
}),
// "Beneficiary" in pacs.008 outgoing payments → also Counterparty (different role, same type)
defineAlias({
alias: 'Beneficiary',
objectTypeSlug: 'Counterparty',
contextSelector: {
standard: 'ISO20022',
messageType: 'pacs.008',
direction: 'outgoing',
},
confidence: 0.95,
}),
// "Client" in a CRM context → Counterparty (context-free fallback, lowest rank)
defineAlias({
alias: 'Client',
objectTypeSlug: 'Counterparty',
// no contextSelector = context-free fallback
confidence: 0.75,
}),
// Property alias: "amount_cents" in outgoing pacs.008 → the monetary_amount property
defineAlias({
alias: 'amount_cents',
objectTypeSlug: 'Counterparty',
propertyTypeSlug: 'monetary_amount',
contextSelector: {
standard: 'ISO20022',
messageType: 'pacs.008',
direction: 'outgoing',
},
confidence: 0.99,
}),
],
})ConceptAlias field reference
| Field | Type | Notes |
|---|---|---|
alias | string | The local or source term (e.g. "Debtor", "Client", "Obligor"). |
objectTypeSlug | Slug | The canonical Object Type this alias resolves to. Required. |
propertyTypeSlug | Slug | When set, the alias targets a specific property on the object type rather than the type itself. |
contextSelector | object | A subset of context keys that must match for this alias to apply. Omit to create a context-free fallback (lowest resolution priority). |
contextSelector.sourceSystem | string | Match by source system slug. |
contextSelector.standard | string | Match by message standard (e.g. "ISO20022"). |
contextSelector.messageType | string | Match by message type (e.g. "pacs.008"). |
contextSelector.direction | string | Match by direction ("inbound" / "outgoing"). |
contextSelector.jurisdiction | string | Match by jurisdiction code. |
contextSelector.businessProcess | string | Match by business process slug. |
confidence | number (0–1) | Confidence that this alias is correct in the given context. Candidates are ranked by context specificity first, then by confidence. |
Resolution priority: aliases whose contextSelector matches more keys in the query context rank above looser matches. A context-free alias (no contextSelector) is always the lowest-priority fallback.
dataContract — enforceable upstream contract
A data contract documents the SLA and quality expectations between a binding and its upstream source. It is stored on the binding and surfaced in governance tooling.
defineBinding({
objectTypeSlug: 'Payment',
kind: 'silver_table',
source: { table: 'silver_payments_outgoing' },
identitySpec: { keys: ['msg_id'] },
columnMap: { monetary_amount: 'amount_cents' },
dataContract: {
version: '1.0.0',
owner: 'data-platform@example.com',
requiredConcepts: ['monetary_amount', 'value_date', 'counterparty_lei'],
qualityRules: [
{ kind: 'not_null', on: 'counterparty_lei' },
{ kind: 'range', on: 'monetary_amount', min: 0 },
],
sla: { freshnessMinutes: 60 },
lineage: { upstream: 'core_banking_x.payments.outgoing' },
},
})| Field | Type | Notes |
|---|---|---|
version | semver | Contract version — increment when SLA or required concepts change. |
owner | string | Contact or team responsible for this contract. |
requiredConcepts | string[] | Property slugs that must project non-null for a row to satisfy the contract. |
qualityRules | array | Each rule has a kind ("not_null", "range", "regex", etc.) and an on field naming the property slug. Additional rule-specific fields are allowed. |
sla.freshnessMinutes | number | Maximum acceptable age of the latest row in minutes. |
lineage.upstream | string | Upstream source table or feed identifier (free-form, for documentation and audit). |
sourceRef | string | Forward-compat pointer to a defineDataSource() definition. When the platform's declarative data-source work lands, the binding will be able to derive its contract from the data-source definition automatically. |
Provenance on reads
When your bindings include bindingContext and transformMap, every projected instance carries a provenance envelope alongside its properties:
{
"id": "payment-abc123",
"properties": {
"monetary_amount": 1234.56,
"value_date": "2025-04-15T00:00:00Z",
"counterparty_lei": "213800ZOHLGEF2K5DX57"
},
"provenance": {
"bindingId": "...",
"bindingVersion": 3,
"sourceRef": "silver_payments_outgoing",
"materializedAt": "2025-04-15T08:12:00Z",
"confidence": 0.96,
"bindingContext": {
"sourceSystem": "core_banking_x",
"standard": "ISO20022",
"messageType": "pacs.008",
"direction": "outgoing",
"jurisdiction": "BE"
},
"lossyProperties": ["value_date", "counterparty_lei"],
"dlpLabels": []
}
}lossyProperties lists the slugs of any properties whose transform was flagged lossless: false or whose transform was unknown (failed closed to null). Callers that need to trust a value can check whether it appears in lossyProperties before acting on it.
When two source systems project instances of the same canonical type and disagree on a value, both instances are returned with their individual provenance — the platform does not silently pick a winner. If you want a survivorship policy, configure identityRule.survivorship: "highest_confidence" on the Object Type; this is opt-in and uses the confidence values from the provenance envelope.
Complete example
A payment ontology pack that uses all of the above:
import {
defineOntology,
defineObjectType,
defineProperty,
defineBinding,
defineIdentityRule,
defineAlias,
} from '@scrydon/sdk-authoring/ontologies'
export default defineOntology({
id: 'acme-iso20022-payments',
version: '1.0.0',
displayName: 'ISO 20022 Payments',
description: 'Payment ontology grounded in ISO 20022 pacs.008 — context-aware bindings from two core-banking feeds.',
maturity: 'preview',
author: { name: 'Acme Data Platform Team' },
objectTypes: [
defineObjectType({
slug: 'Counterparty',
displayName: 'Counterparty',
kind: 'analytical',
anchors: [
{ standard: 'FIBO', conceptId: 'fibo-fbc-fct-fse:Counterparty', label: 'Counterparty' },
{ standard: 'ISO20022', conceptId: 'pacs.008.Cdtr', label: 'Creditor' },
],
properties: [
defineProperty({ slug: 'name', displayName: 'Legal name', scalarType: 'string', cardinality: 'required' }),
defineProperty({ slug: 'lei', displayName: 'LEI', scalarType: 'string', anchors: [
{ standard: 'LEI', conceptId: 'gleif:LEI', label: 'Legal Entity Identifier' },
]}),
],
}),
defineObjectType({
slug: 'Payment',
displayName: 'Payment',
kind: 'analytical',
anchors: [
{ standard: 'ISO20022', conceptId: 'pacs.008.CreditTransfer', label: 'Credit Transfer Initiation' },
{ standard: 'FINOS_CDM', conceptId: 'rosetta:Transfer', label: 'Transfer' },
],
properties: [
defineProperty({ slug: 'monetary_amount', displayName: 'Monetary amount', scalarType: 'float', cardinality: 'required',
anchors: [{ standard: 'ISO20022', conceptId: 'Pacs.008.IntrBkSttlmAmt', label: 'Interbank Settlement Amount' }],
}),
defineProperty({ slug: 'value_date', displayName: 'Value date', scalarType: 'date',
anchors: [{ standard: 'ISO20022', conceptId: 'Pacs.008.IntrBkSttlmDt', label: 'Settlement Date' }],
}),
defineProperty({ slug: 'currency', displayName: 'Currency', scalarType: 'string' }),
],
}),
],
linkTypes: [],
actionTypes: [],
identityRules: [
defineIdentityRule({
objectTypeSlug: 'Payment',
deterministicKeys: ['msg_id'],
}),
defineIdentityRule({
objectTypeSlug: 'Counterparty',
deterministicKeys: ['lei'],
probabilistic: {
blockingKeys: ['name'],
signals: [{ name: 'name_levenshtein', weight: 0.8, on: 'name' }],
thresholds: { auto: 0.92, review: 0.75 },
},
}),
],
bindings: [
// Binding A — core banking X (ISO 20022 pacs.008, outgoing, Belgium)
defineBinding({
objectTypeSlug: 'Payment',
kind: 'silver_table',
source: { table: 'silver_payments_cbx_out' },
identitySpec: { keys: ['msg_id'] },
columnMap: { currency: 'ccy' },
bindingContext: {
sourceSystem: 'core_banking_x',
integrationVersion: '3.2',
standard: 'ISO20022',
messageType: 'pacs.008',
direction: 'outgoing',
jurisdiction: 'BE',
dataResidency: 'eu',
businessProcess: 'credit_transfer',
timestampSemantics: 'value_date',
currencySemantics: 'transaction_currency',
confidence: 0.96,
validFrom: '2025-01-01',
},
transformMap: {
monetary_amount: {
source: 'amount_cents',
transform: 'divide_by_100',
unit: 'EUR',
lossless: true,
},
value_date: {
source: 'trade_ts_local',
transform: 'timezone_normalize',
tz: 'Europe/Brussels',
lossless: false,
notes: 'DST ambiguity on clock-change dates',
},
},
dataContract: {
version: '1.0.0',
owner: 'data-platform@acme.example',
requiredConcepts: ['monetary_amount', 'value_date', 'currency'],
qualityRules: [
{ kind: 'not_null', on: 'currency' },
{ kind: 'range', on: 'monetary_amount', min: 0 },
],
sla: { freshnessMinutes: 60 },
lineage: { upstream: 'core_banking_x.payments.outgoing' },
},
}),
],
aliases: [
defineAlias({
alias: 'Debtor',
objectTypeSlug: 'Counterparty',
contextSelector: { standard: 'ISO20022', messageType: 'pacs.008', direction: 'outgoing' },
confidence: 0.97,
}),
defineAlias({
alias: 'Beneficiary',
objectTypeSlug: 'Counterparty',
contextSelector: { standard: 'ISO20022', messageType: 'pacs.008', direction: 'outgoing' },
confidence: 0.95,
}),
defineAlias({
alias: 'Client',
objectTypeSlug: 'Counterparty',
confidence: 0.75,
}),
],
})Try it — the Contextual Payments example pack
Everything in this guide ships as a single downloadable pack you can install and click through. The Contextual Payments example bundles this exact ISO 20022 ontology plus its own demo data (two declarative data sources), so after install — with no CSV upload — you can watch the binding auto-resolve, inspect the transforms and confidence, and resolve "Counterparty" to a different concept per direction in the workbench Aliases tab.