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
65 changes: 59 additions & 6 deletions 05a-data-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,49 @@ match the installed digest. A `seed` resource is created only when its target
is absent and becomes user-owned immediately; later pack versions neither
replace nor delete it unless an explicit seed-type upgrade is declared below.

A seed **type** resource MAY declare `upgrade_from: { digest, document }`.
The document is an exact previous publisher baseline, pinned by SHA-256, not
an assertion that the user's current document is unchanged. This declaration
is part of the reviewed manifest and its digest. Ordinary seeds are unaffected.
A seed **type** resource MAY declare `upgrade_from`: one baseline, or a
non-empty list of baselines. A baseline is `{ digest, document }` with an
optional integer `version`. `document` is the exact bytes of a starter the
publisher previously shipped for this resource, pinned by SHA-256 in `digest`;
it is not an assertion that the user's current document is unchanged. `version`,
when present, is the `version` that document declares and is only for
presentation. A single baseline is equivalent to a list containing it. Listing
every previously shipped starter that remains supported lets a collection
upgrade from any of them, not only from the most recent. This declaration is
part of the reviewed manifest and its digest. Ordinary seeds are unaffected.
Engines that do not support this member MUST reject the manifest.

An upgrade performs a conservative three-way merge of baseline, live type,
and desired type. The type kind and name MUST match. Unchanged publisher
The manifest is invalid (`invalid_type_pack`) when `upgrade_from` appears on a
resource that is not a seed type, when a baseline's digest is not the SHA-256 of
its document, when two baselines share a digest, when a baseline's digest equals
the resource's own digest, when a baseline document's frontmatter `kind` or
`name` differs from the desired document's, or when a baseline's `version`
differs from the `version` its document's frontmatter declares.

A seed's **origin** is the publisher document its live target descends from.
The lock records it (see Pack Identity And Portable Provenance). When the target
exists, is not an intentionally preserved seed target, and the resource declares
`upgrade_from`, the engine plans exactly one of these, in order:

1. The live bytes equal the desired document: `preserve`.
2. The live bytes equal a baseline's document: `update`, writing the desired
document byte-for-byte. Byte equality proves the origin, whatever the lock
records.
3. The origin is the desired document: `preserve`. The seed was already upgraded
and has been edited since.
4. The origin is a baseline: `update` by a three-way merge of that baseline, the
live type, and the desired type.
5. Otherwise, because the origin is unknown or is not a listed baseline:
`preserve`, with a `reason` stating that no upgrade baseline applies. The
type is left as it is; it does not make the pack conflict.

An engine MUST NOT choose a merge baseline any other way. In particular, it MUST
NOT merge against a baseline the seed is not known to descend from, because the
differences between that baseline and the seed's actual origin would be applied
as if they were the user's edits. The assessment reports, for every seed
`update`, the baseline used as `upgrade_baseline: { digest, version? }`.

A three-way merge is conservative. The type kind and name MUST match. Unchanged publisher
settings retain live customizations; unchanged live settings accept publisher
changes. Object settings merge recursively. Contract implementations merge by
contract ID only when unambiguous, retaining customized field mappings and
Expand Down Expand Up @@ -428,6 +463,24 @@ mode, canonical source, resolved target, and installed digest of every resource.
deterministically and users MAY inspect or version it. Tools MUST NOT infer
ownership from filenames, application names, or `x-*` metadata.

A seed resource's entry also records `origin_digest`, the digest of the
publisher document its target descends from, when that is known. Because a seed
becomes user-owned, the installed digest of a seed describes the pack, not the
target, and MUST NOT be used as its origin. An apply sets `origin_digest` to the
desired resource digest when it creates the seed, when it upgrades it (by exact
replacement or by merge), and whenever the target's live bytes equal the desired
document, whatever else applies. Otherwise it carries the previous entry's
`origin_digest` for that target forward unchanged, or omits it when there is
none. A seed target that existed before the pack was installed, and an
intentionally preserved seed target, therefore have no origin unless their bytes
equal the desired document. A lock entry without `origin_digest` means the
origin is unknown. Locks written before `origin_digest` existed carry none, so
an edited seed under such a lock is preserved with a reason rather than merged
until it is upgraded or recreated; an unedited seed still upgrades, because its
bytes prove its origin. `origin_digest` is defined only for seed resources and
MUST NOT appear on a managed resource. An apply that changes only seed origins leaves the pack's
status `current`; it is not a reconfiguration.

Full collection snapshots, authority transfers, and unscoped synchronization
MUST carry `mdbase.lock.yaml` when it exists. A scoped application projection
MAY omit it to avoid disclosing unrelated pack metadata. A storage provider
Expand Down
23 changes: 18 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@ All notable changes to this specification and conformance suite are documented h
through `x-obsidian.bases.include` and the saved-view source operations for
Bases become transitional.

- A seed type resource may declare `upgrade_from: { digest, document }`, a
digest-pinned previous publisher baseline. The engine performs a conservative
three-way merge of baseline, live type and desired type, fails closed on
competing changes, and publishes atomically. Engines that do not support the
member must reject the manifest. Records are never migrated this way.
- A seed type resource may declare `upgrade_from`: one baseline or a list of
baselines, each a digest-pinned starter the publisher previously shipped
(`{ digest, document, version? }`), so a collection can upgrade from any
supported starter, not only the latest. The engine chooses the merge
baseline from the seed's origin and never guesses: an unedited seed equal to
any baseline is replaced with the exact desired bytes; an edited seed merges
against the baseline it descends from; a seed whose origin is unknown or not
listed is preserved with a reason rather than merged or made to conflict.
Competing changes still fail closed, publication is atomic, and the
assessment reports the baseline used. Engines that do not support the member
must reject the manifest. Records are never migrated this way.
- The type-pack lock records a seed's `origin_digest`, the publisher document
its target descends from, set when the seed is created, upgraded, or already
matches the desired document, and otherwise carried forward. A seed's
installed digest describes the pack, not the target, and is never its origin.
- Type-pack conformance adds seed-upgrade fixtures (`examples/v0.3/seed-upgrades`)
and `input.history` steps, with an executable model in
`scripts/type_pack_model.py`.

### Saved views are identified through the `mdbase.view` contract

Expand Down
14 changes: 14 additions & 0 deletions examples/v0.3/seed-upgrades/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Seed upgrade examples

Versions of one pack, `example.seed-notes`, whose seed `note` type changes from
v1 (`title`) to v2 (adds `created`) to v3 (adds `tags`). The type-pack
conformance fixtures install them in sequence to exercise `upgrade_from`
baselines, seed origins in the lock, and manifest validation (05A).

- `v1`, `v2`, `v3`: the starter at each version; v2 declares v1 as a single baseline object and v3 lists v2 and v1.
- `v1.5-plain`: ships the v2 starter without `upgrade_from`, so an installed
seed is preserved and keeps its origin.
- `v3-only-v2`: v3 supporting upgrades only from v2.
- `invalid-*`: manifests (one rule each) that must be rejected with `invalid_type_pack`.

Pack sources are byte-exact; regenerate digests if a document changes.
22 changes: 22 additions & 0 deletions examples/v0.3/seed-upgrades/invalid-baseline-digest/_types/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
kind: mdbase.type
name: note
version: 3
description: A note with a creation date and tags.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
tags: { type: array, items: { type: string } }
additionalProperties: true
---
# Note

A note in this collection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# The baseline digest does not match its document.
kind: mdbase.type-pack
id: example.seed-notes
version: 3.0.0
name: Seed note example
resources:
- kind: type
mode: seed
source: _types/note.md
target: _types/note.md
digest: sha256:57160cce297903e123f876d789d518ed83b1cf790edafae660f43f3d44744065
upgrade_from:
- digest: sha256:38887fbf68c97de99bc271209466bbf457a3635b3ed2ee93e6c5bf8213f53c10
version: 2
document: |
---
kind: mdbase.type
name: note
version: 2
description: A note with a creation date.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
additionalProperties: true
---
# Note

A note in this collection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
kind: mdbase.type
name: note
version: 3
description: A note with a creation date and tags.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
tags: { type: array, items: { type: string } }
additionalProperties: true
---
# Note

A note in this collection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
kind: mdbase.type-pack
id: example.seed-notes
version: 3.0.0
name: Seed note example
resources:
- kind: type
mode: seed
source: _types/note.md
target: _types/note.md
digest: sha256:57160cce297903e123f876d789d518ed83b1cf790edafae660f43f3d44744065
upgrade_from:
- digest: sha256:28887fbf68c97de99bc271209466bbf457a3635b3ed2ee93e6c5bf8213f53c10
version: 2
document: |
---
kind: mdbase.type
name: note
version: 2
description: A note with a creation date.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
additionalProperties: true
---
# Note

A note in this collection.
- digest: sha256:28887fbf68c97de99bc271209466bbf457a3635b3ed2ee93e6c5bf8213f53c10
version: 2
document: |
---
kind: mdbase.type
name: note
version: 2
description: A note with a creation date.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
additionalProperties: true
---
# Note

A note in this collection.
22 changes: 22 additions & 0 deletions examples/v0.3/seed-upgrades/invalid-managed-upgrade/_types/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
kind: mdbase.type
name: note
version: 3
description: A note with a creation date and tags.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
tags: { type: array, items: { type: string } }
additionalProperties: true
---
# Note

A note in this collection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
kind: mdbase.type-pack
id: example.seed-notes
version: 3.0.0
name: Seed note example
resources:
- kind: type
mode: managed
source: _types/note.md
target: _types/note.md
digest: sha256:57160cce297903e123f876d789d518ed83b1cf790edafae660f43f3d44744065
upgrade_from:
- digest: sha256:28887fbf68c97de99bc271209466bbf457a3635b3ed2ee93e6c5bf8213f53c10
version: 2
document: |
---
kind: mdbase.type
name: note
version: 2
description: A note with a creation date.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
additionalProperties: true
---
# Note

A note in this collection.
22 changes: 22 additions & 0 deletions examples/v0.3/seed-upgrades/invalid-name-mismatch/_types/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
kind: mdbase.type
name: note
version: 3
description: A note with a creation date and tags.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
tags: { type: array, items: { type: string } }
additionalProperties: true
---
# Note

A note in this collection.
35 changes: 35 additions & 0 deletions examples/v0.3/seed-upgrades/invalid-name-mismatch/mdbase-pack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
kind: mdbase.type-pack
id: example.seed-notes
version: 3.0.0
name: Seed note example
resources:
- kind: type
mode: seed
source: _types/note.md
target: _types/note.md
digest: sha256:57160cce297903e123f876d789d518ed83b1cf790edafae660f43f3d44744065
upgrade_from:
- digest: sha256:1e75d64c466f1945a689d17be1baff0851f9ef09e2e2ec03396b6b66fc713579
version: 2
document: |
---
kind: mdbase.type
name: memo
version: 2
description: A note with a creation date.
match:
where:
type: note
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: { type: string }
created: { type: string, format: date-time }
additionalProperties: true
---
# Note

A note in this collection.
Loading
Loading