Skip to content

Repository files navigation

mdbase contracts

First-party, versioned data contracts and transactional type packs for mdbase collections.

Record contracts describe compact, application-facing semantics rather than storage layouts or external interchange formats. Their field names and schemas should say what an application can rely on while leaving collections free to choose local field names through explicit type mappings. Contract-specific behavior such as workflow vocabularies belongs in a validated binding_schema.

Published contract schemas are immutable interoperability boundaries. A catalog-listed starter type contains an inline snapshot of its starting schema instead of inheriting that boundary. Once installed, the type belongs to the collection: users can edit its fields and update the explicit contract mapping without changing the published contract. Packs authored with expand_local_refs: true expand every non-recursive local JSON Schema reference so nested fields remain directly editable. Recursive references remain explicit because they cannot be finitely expanded.

The featured mdbase.runtime.standard pack supplies the durable runtime 0.2 standard library: ten ordinary record contracts and canonical implementing types, the four canonical record-change events, and inspectable timer-event and cancellation-action artifacts. Installing it is passive and grants no execution authority.

The mdbase.contact 1.2.0 pack offers one Person starter with portable IDs, editable issuer/subject account associations, and optional contact details. It implements both mdbase.person and mdbase.contact, so apps use the same notes. The pack keeps the existing resource owner, but no longer adds a separate Contact type to fresh collections. Older Contact types, notes, and customized Person types are preserved; there is no automatic migration. Older pack artifacts remain byte-identical at their versioned paths, but only the current pack is offered in the catalog. Person starter v2 adds field-level descriptions and usage guidance without changing validation or contract mappings. Starter types leave the type key to the collection: apps name a type when they create a record and the engine records it under the collection's configured explicit_type_keys (type by default, mdbase_type in collections where type is data). Person v3, Comment v2 and View v2 therefore no longer require or pin type, and View v2 accepts unknown top-level fields; each keeps its match rule for hand-written records. mdbase.contact 1.3.0, mdbase.comment 1.0.1 and mdbase.view 1.0.1 offer them as reviewed upgrades of the unmodified previous starter; mdbase.contact 1.4.0 upgrades both earlier Person starters (v1 from 1.1.0 and v2 from 1.2.0) to v3. Pack resources explicitly use managed for schemas/contracts and seed for editable starter types; install tests exercise the exact generated payload without supplying missing modes. Associations are ordinary collection data, never authentication or membership authority. See mdbase.person 1.0.0 for matching, ambiguity, privacy, and lifecycle semantics.

The mdbase.comment pack defines comments, replies and suggested edits as records of their own, anchored to a quote of the commented record's Markdown body and linked to mdbase.person authors. Apps that comment embed the published provision unchanged, as mdbase writer does.

The tasknotes.task pack is the canonical application-provisioned TaskNotes contract bundle. TaskNotes clients embed the published provision byte-for-byte and pin its catalog digest so independently deployed clients cannot drift onto different managed-pack versions.

This repository is the canonical source. Its deterministic dist/ output is published at https://mdbase.dev/contracts/. A catalog entry is only a discovery aid: every installable pack contains an exact manifest, embedded source documents, and SHA-256 digests.

Repository layout

catalog.yaml                       catalog identity and presentation
contracts/<id>/<version>.md        contract source documents
types/<name>/<version>.md          default implementing types
schemas/<id>/<version>.json        referenced JSON Schemas
packs/<id>/<version>.pack.yaml     readable pack definitions
dist/                              deterministic publication artifact

The runtime pack is generated in mdbase-spec. Contract and schema artifacts are imported byte-for-byte; its referenced canonical types are materialized as editable inline schema snapshots for the catalog:

MDBASE_SPEC_DIR=../mdbase-spec npm run sync:runtime

Build and verify

Requires Node.js 22+ and a built checkout of @callumalpass/mdbase (mdbase-ts) 0.3.0-rc.9 or later (seed upgrade baseline lists). A sibling ../mdbase checkout is used by default; set MDBASE_TS_DIR to override it. CI checks out and builds the tag pinned in sources.json (typescript_implementation.ref), which is the single source of truth for the mdbase-ts version the catalog is verified against.

npm install
npm run build
npm run verify

When authoring a starter from an existing inline schema, expand its local references before adding it to a listed pack:

npm run expand:type -- types/example/1.md types/example/2.md

Verification checks the catalog schema, every resource digest, a transactional dry run, a real install, idempotent reinstallation, and the declared contract implementations.

The TaskNotes rc.14 candidate explicitly upgrades the rc.12 starter using a digest-pinned baseline. It requires an engine with seed-upgrade support; older engines reject it rather than silently skipping the upgrade. Published rc.12 and rc.13 bytes remain unchanged. The rc.15 candidate upgrades the same rc.12 starter to contract rc.5, where assignees are links to mdbase.person 2.0.0 records declared in collection.links; Person 2.0.0 drops the separate id. The People pack 1.2.0 keeps shipping mdbase.person 1.0.0 so Person types customised under 1.1.0 continue to validate.

Packs are installed through one of two engines. By default every check (catalog verification, the People pack tests, and the TaskNotes upgrade and plugin-collection tests) installs through mdbase-ts. To run the same checks through a Rust-engine mdbase CLI instead:

MDBASE_VERIFY_CLI=/absolute/path/to/mdbase npm test

Installed collections are reopened with mdbase-ts either way. CI runs npm test once per engine, with the CLI built from the commits pinned in sources.json (rust_cli). The CLI's engine must include mdbase-rs 056db73 (seed upgrade baseline lists); rust_cli.ref is the mdbase-connect commit that pins that engine revision.

Seed upgrade baselines

A seed type is the collection's to edit once installed, so a new pack version replaces it only through an explicit seed-type upgrade (mdbase spec 05A). A pack definition names the starters it upgrades with upgrade_from, either one path or a list:

  - kind: type
    mode: seed
    source: types/tasknotes-task/5.md
    target: _types/task.md
    upgrade_from:
      - types/tasknotes-task/4.md
      - types/tasknotes-task/3.md
      - types/tasknotes-task/2.md
      - types/tasknotes-task/1.md

npm run build emits a list as [{ digest, version, document }], ordered newest first, with version read from each baseline's frontmatter; the single form keeps emitting one { digest, document } so published packs stay byte-identical. The build rejects baselines on anything but a seed type, duplicate or self baselines, and baselines whose type kind or name differs from the desired starter.

Engines replace a starter equal to any listed baseline with the exact new starter, and merge an edited starter only against the baseline its lock records as its origin_digest; an edited type with no recorded origin (installed by an engine before 05A) or an unlisted origin is preserved with a reason. An unlisted starter is therefore never upgraded, so the catalog keeps an invariant, checked by scripts/seed-baselines.test.mjs: for every offered (non-hidden) pack and seed target, upgrade_from lists every starter that any earlier version of the pack shipped at that target, including unlisted (catalog: false) versions. When a pack's list changes, publish a new version and hide the previous one (installation.visibility: hidden). scripts/seed-upgrade.test.mjs installs each earlier version through the selected engine and upgrades it, unedited and customised.

TaskNotes assignments

The rc.17 TaskNotes pack introduces optional assignees through the rc.5 task contract and task type v4 (starter revision 5): links to records implementing mdbase.person 2.0.0, declared as links so engines resolve them. It upgrades collections that seeded rc.12's task type 1 with a digest-pinned seed-type upgrade, without rewriting published rc.3 resources. Type v4 is rc.12's type v1 plus the assignees field, link, mapping and contract version only, so an upgrade keeps every other setting a collection has. rc.15 (type v3) was regenerated from TaskNotes model defaults and also dropped the cancelled status and changed colours and profiles; it, and the superseded person-ID candidates rc.13 and rc.14, remain available at their immutable URLs but are not listed (catalog: false).

rc.17's starter is rc.16's with the generator bookkeeping (x-tasknotes-generator.managed_fields) left as rc.12 published it: each collection's list follows its own field mapping, so an upgrade that changed it conflicted wherever that mapping was customized. rc.16 is not listed.

rc.18 ships rc.17's starter unchanged and lists every earlier starter as an upgrade baseline (revisions 4, 3, 2 and 1, from rc.16, rc.15, rc.13/rc.14 and rc.12), so a collection seeded by any earlier pack upgrades to it. rc.17 is hidden. A customised task type whose lock records no origin is preserved, and because it still implements the replaced contract the upgrade is blocked for review rather than merged against a guessed starter.

Starter files are named by revision (types/tasknotes-task/<revision>.md); a revision that does not change the task data keeps the type version, so collections already at that version upgrade without a version conflict.

The starter is defined by @tasknotes/model/starter, which reproduces the published starter byte for byte; scripts/sync-tasknotes-pack.mjs generates it from there. The upgrade tests check that a new starter changes nothing beyond its declared changes.

To regenerate from a built sibling model:

node scripts/sync-tasknotes-pack.mjs
npm run build

The importer refuses to overwrite differing existing versioned artifacts. Publish this catalog before consumers request its new immutable URLs.

Publishing

mdbase.dev pins a reviewed commit of this repository, builds it, and copies dist/ into its own public/contracts/ directory. Published version URLs are immutable. Changing an existing artifact requires a new contract or pack version.

Profiles and standards

A standards-oriented contract must identify its normative references and state its profile scope. An mdbase contract is not presented as an official schema from the referenced standards body unless that body actually publishes it as such.

External interchange schemas should not be listed as general-purpose record contracts unless the installed type intentionally stores that exact shape. Converters and exporters can consume a smaller semantic record contract and produce the external format. Historical packs may set catalog: false to keep their immutable artifact URLs available without advertising them for new installations.

About

First-party data contracts and transactional type packs for mdbase

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages