Skip to content
Merged
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
8 changes: 5 additions & 3 deletions 00-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,12 @@ read defaults, links, uniqueness, and path policy.
Types can implement exact, versioned data contracts stored under
`_contracts/`. A contract uses JSON Schema for its normalized record interface
and optional binding. The type's `implements` entry maps contract fields to
record fields.
record fields. Record contracts normally describe compact application
semantics rather than storage layouts or external wire formats.

Several types can implement one contract, and several applications can consume
the same type. Discovery returns the complete implementation set; it never
Several types can implement one contract, one type can implement several
contracts, and several applications can consume the same contract view.
Discovery returns the complete implementation set; it never
silently picks a provider. Data contracts are passive interoperability and do
not grant access.

Expand Down
6 changes: 4 additions & 2 deletions 01-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ providers and workflows supply executable behavior.

A data contract is a versioned `mdbase.contract` control file that defines a
portable record interface and optional implementation binding using JSON Schema
2020-12.
2020-12. A record contract normally names shared application semantics; event
and action contracts describe complete messages.

A type implements a data contract through its top-level `implements` section.
The contract does not replace the type, select a provider, assign ownership, or
grant access. Several types can implement one contract, and several
applications can consume the same implementing type.
applications can consume the same implementing type. One type can contain
separate implementations of several contracts.

## Schema

Expand Down
35 changes: 35 additions & 0 deletions 05-data-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,35 @@ executable, instance-specific implementations rather than record types.
Requirements, authorization grants, and transports are not collection
contract artifacts.

## Designing Record Contracts

A record contract SHOULD describe a compact, application-facing semantic
interface. Its properties name values that independent consumers can rely on,
while an implementing type remains free to choose local field names, matching
rules, additional fields, and presentation.

Record contracts SHOULD:

- keep the unconditionally required surface as small as the shared behavior
permits
- use optional properties for semantics that not every implementing type can
provide
- use `binding_schema` for implementation-specific vocabularies and behavior,
such as which local task statuses count as completed
- be shared by applications that need the same semantics rather than duplicated
under application-specific IDs

A record contract SHOULD NOT reproduce an external interchange or storage
format merely so applications can request that serialization. An importer,
exporter, or application adapter can translate a semantic record view to
JSContact, vCard, or another wire format. A wire-format-shaped record contract
remains valid when the implementing type intentionally stores and exposes that
exact shape.

Event and action contracts are different: their schemas describe complete
messages at an interoperability boundary, so a transport-shaped schema is
usually appropriate.

## Contract Files

Contract files are Markdown files under the configured contracts folder,
Expand Down Expand Up @@ -156,6 +185,12 @@ contract view. The right side addresses effective record frontmatter. Mapping
is direct: core does not rename values, coerce values, run expressions, or
apply hidden transforms.

The `binding` object does not transform projected record values. It supplies
validated semantic policy that a contract-aware application can interpret. For
example, a task implementation can expose the local status value unchanged
while declaring several values in `binding.completed_values`. This preserves
the user's vocabulary and avoids inventing an ambiguous reverse mapping.

A type MUST NOT contain two implementations of the same contract ID and
version. Field mappings MUST address fields declared by the resolved
`record_schema` and the resolved type schema. Every unconditional top-level
Expand Down
62 changes: 61 additions & 1 deletion packages/testbed/assets/suite/fixtures/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@
}
}
},
"contract.example-summary": {
"kind": "contract",
"value": {
"kind": "mdbase.contract",
"contract_type": "record",
"id": "example.summary",
"version": "1.0.0",
"name": "Portable example summary",
"record_schema": {
"dialect": "json-schema-2020-12",
"value": {
"type": "object",
"required": ["label"],
"additionalProperties": false,
"properties": {
"label": { "type": "string", "minLength": 1 },
"category": { "type": "string", "minLength": 1 }
}
}
}
}
},
"type.shared-note": {
"kind": "type",
"value": {
Expand Down Expand Up @@ -51,11 +73,49 @@
]
}
},
"type.multi-contract-note": {
"kind": "type",
"value": {
"kind": "mdbase.type",
"name": "shared_note",
"version": 1,
"schema": {
"dialect": "json-schema-2020-12",
"value": {
"type": "object",
"required": ["type", "headline", "category"],
"properties": {
"type": { "const": "shared_note" },
"headline": { "type": "string", "minLength": 1 },
"category": { "type": "string", "minLength": 1 }
}
}
},
"implements": [
{
"contract": "example.note",
"version": "1.0.0",
"fields": {
"title": "headline"
}
},
{
"contract": "example.summary",
"version": "1.0.0",
"fields": {
"label": "headline",
"category": "category"
}
}
]
}
},
"record.shared-note": {
"kind": "record",
"value": {
"type": "shared_note",
"headline": "One record, two consumers"
"headline": "One record, two consumers",
"category": "reference"
}
},
"contract.record-changed": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"kind": "mdbase.testbed.scenario",
"protocol_version": "0.1",
"id": "core.shared-type-contracts",
"name": "One type exposes two independent contract views",
"description": "One local type implements two record contracts and each consumer observes only the normalized view it selected.",
"ring": "contract",
"profile": "core_read",
"roles": ["contract_store", "record_consumer"],
"operation": "contract.multiple-views",
"fixtures": [
"contract.example-note",
"contract.example-summary",
"type.multi-contract-note",
"record.shared-note"
],
"parameters": {
"consumers": ["note-consumer", "summary-consumer"]
},
"covers": [
"core_read.data_contract_discovery",
"core_read.data_contract_implementation_validation",
"core_read.data_contract_projection"
],
"expect": {
"entries": [
{
"sequence": 1,
"phase": "arrange",
"actor": "contract-store",
"operation": "contract.load",
"outcome": "succeeded",
"facts": {
"contracts": ["example.note", "example.summary"]
}
},
{
"sequence": 2,
"phase": "arrange",
"actor": "contract-store",
"operation": "type.load",
"outcome": "succeeded",
"facts": {
"type": "shared_note",
"implements": ["example.note", "example.summary"]
}
},
{
"sequence": 3,
"phase": "act",
"actor": "note-consumer",
"operation": "contract-view.read",
"outcome": "succeeded",
"facts": {
"contract": "example.note",
"view": {
"title": "One record, two consumers"
}
}
},
{
"sequence": 4,
"phase": "act",
"actor": "summary-consumer",
"operation": "contract-view.read",
"outcome": "succeeded",
"facts": {
"contract": "example.summary",
"view": {
"label": "One record, two consumers"
}
}
},
{
"sequence": 5,
"phase": "observe",
"actor": "testbed",
"operation": "contract-view.compare",
"outcome": "succeeded",
"facts": {
"same_record": true,
"views_are_distinct": true
}
}
]
}
}
4 changes: 2 additions & 2 deletions packages/testbed/test/testbed.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
test("validates the complete neutral scenario inventory", () => {
const result = validateTestbed();
assert.deepEqual(result, {
fixtures: 7,
scenarios: 15,
fixtures: 9,
scenarios: 16,
profiles: [
"core_read",
"event_action_interop/0.1",
Expand Down
62 changes: 61 additions & 1 deletion testbed/v0.1/fixtures/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@
}
}
},
"contract.example-summary": {
"kind": "contract",
"value": {
"kind": "mdbase.contract",
"contract_type": "record",
"id": "example.summary",
"version": "1.0.0",
"name": "Portable example summary",
"record_schema": {
"dialect": "json-schema-2020-12",
"value": {
"type": "object",
"required": ["label"],
"additionalProperties": false,
"properties": {
"label": { "type": "string", "minLength": 1 },
"category": { "type": "string", "minLength": 1 }
}
}
}
}
},
"type.shared-note": {
"kind": "type",
"value": {
Expand Down Expand Up @@ -51,11 +73,49 @@
]
}
},
"type.multi-contract-note": {
"kind": "type",
"value": {
"kind": "mdbase.type",
"name": "shared_note",
"version": 1,
"schema": {
"dialect": "json-schema-2020-12",
"value": {
"type": "object",
"required": ["type", "headline", "category"],
"properties": {
"type": { "const": "shared_note" },
"headline": { "type": "string", "minLength": 1 },
"category": { "type": "string", "minLength": 1 }
}
}
},
"implements": [
{
"contract": "example.note",
"version": "1.0.0",
"fields": {
"title": "headline"
}
},
{
"contract": "example.summary",
"version": "1.0.0",
"fields": {
"label": "headline",
"category": "category"
}
}
]
}
},
"record.shared-note": {
"kind": "record",
"value": {
"type": "shared_note",
"headline": "One record, two consumers"
"headline": "One record, two consumers",
"category": "reference"
}
},
"contract.record-changed": {
Expand Down
Loading