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
25 changes: 25 additions & 0 deletions 12-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,31 @@ Static JSON Schema defaults MAY be used by editor and create interfaces.
Validation-time mutation occurs when the create operation explicitly copies a
default into the draft.

### Selected types and membership representation

A request-level type selector identifies the type the caller intends to create;
it is not itself a persisted frontmatter declaration. Contract selection MUST
resolve and validate its designated implementing type before writing.

When explicit declaration keys are configured, implementations MUST persist a
selected type using those keys, preserving existing valid memberships. They
MUST NOT hard-code `type` or `types` when those keys are not configured. A field
outside the configured declaration keys remains ordinary application data.

When `settings.explicit_type_keys` is empty, implementations MUST NOT add a type
declaration field and MUST NOT reject a create solely because the list is empty.
The selected type guides schema, lifecycle, and path policy. Membership is
inferred from persisted frontmatter and the final canonical path. The selected
(or contract-designated) type MUST be included in the final inferred membership;
otherwise the operation MUST fail without writing a record. All applicable
types participate in validation, not only the requested type. Matching errors
MUST NOT be silently treated as non-matches.

The pre-lifecycle membership freeze and final membership check still apply.
Read defaults and projections are not persisted evidence of membership. A
successful create guarantees membership for the written record under the
current collection rules, not permanent membership after later user edits.

## Update

Update modifies an existing record.
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this specification and conformance suite are documented here.

## Unreleased

- Clarify that selected-type creation does not require explicit membership keys.
With inference-only configuration, final persisted fields and path must match
the selected type, and all applicable types remain subject to validation.
- Add shared creation fixtures for inferred membership, ordinary `type` metadata,
auxiliary schemas, derived paths, and serialization-policy membership loss.

## 2026-07-28 (standard field references)

### Added
Expand Down
19 changes: 8 additions & 11 deletions tests/level-5/references.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,11 @@ groups:
tasks/task-b.md: |
---
type: task
id: "task-b"
title: "Task B"
---
tasks/task-c.md: |
---
type: task
id: "task-c"
title: "Task C"
parent: "[[task-b]]"
---
Expand Down Expand Up @@ -202,7 +200,6 @@ groups:
notes/target.md: |
---
type: note
id: "target"
title: "Target Note"
---

Expand Down Expand Up @@ -284,7 +281,6 @@ groups:
notes/target-note.md: |
---
type: note
id: "target-note"
title: "Target Note"
---
Some body content here.
Expand Down Expand Up @@ -554,10 +550,10 @@ groups:
link: "[Detail](../archive/detail.md)"

# =============================================================================
# Group 6: Ambiguous link handling during rename
# Group 6: Deterministic duplicate-filename handling during rename
# =============================================================================

- name: "ambiguous link not updated during rename"
- name: "deterministically selected duplicate filename updated during rename"
spec_ref: "§12.5"

setup:
Expand Down Expand Up @@ -594,7 +590,7 @@ groups:
---

tests:
- name: "ambiguous link emits warning and is not updated"
- name: "deterministically selected duplicate filename is updated"
operation: rename
input:
from: "folder-a/shared-name.md"
Expand All @@ -603,17 +599,18 @@ groups:
expect:
from: "folder-a/shared-name.md"
to: "folder-a/renamed.md"
warnings:
references_updated:
- path: "notes/source.md"
message_contains: "ambiguous"
# The ambiguous link is left unchanged
field: "ref"
# Equal-depth duplicate filenames use lexical normalized path order,
# so the link identifies folder-a/shared-name.md before the rename.
verify_after:
operation: read
input:
path: "notes/source.md"
expect:
frontmatter:
ref: "[[shared-name]]"
ref: "[[renamed]]"

# =============================================================================
# Group 7: rename_ref_update_failed error code
Expand Down
169 changes: 169 additions & 0 deletions tests/v0.3/core/optional-membership.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: Selected create with inferred membership
spec_version: "0.3.0"
fixture_set: core_collection
category: core_collection
spec_ref: "v0.3/07, v0.3/12"
groups:
- name: No explicit membership keys
setup:
config: |
spec_version: "0.3.0"
settings:
explicit_type_keys: []
default_validation: error
types:
task.md: |
---
kind: mdbase.type
name: task
match:
where:
tags: {contains: task}
schema:
dialect: json-schema-2020-12
value:
type: object
required: [title]
properties:
title: {type: string}
type: {type: string}
tags: {type: array, items: {type: string}}
---
review.md: |
---
kind: mdbase.type
name: review
match:
fields_present: [reviewed]
schema:
dialect: json-schema-2020-12
value:
type: object
required: [reviewer]
---
tests:
- name: selected create succeeds through tag matching without adding keys
operation: create
input:
path: tasks/new.md
type: task
frontmatter: {title: Buy milk, tags: [task]}
expect:
valid: true
types: [task]
frontmatter: {title: Buy milk, tags: [task]}
frontmatter_not_contains: [type, types, mdbase_type]
- name: domain type is preserved rather than replaced with selected name
operation: create
input:
path: tasks/paper.md
type: task
frontmatter: {title: Read paper, tags: [task], type: article-journal}
expect:
valid: true
types: [task]
frontmatter: {type: article-journal}
frontmatter_not_contains: [types, mdbase_type]
- name: selected type must match the persisted document
operation: create
input:
path: tasks/not-matching.md
type: task
frontmatter: {title: Missing tag}
expect:
valid: false
- name: all inferred types validate alongside selected type
operation: create
input:
path: tasks/review.md
type: task
frontmatter: {title: Review paper, tags: [task], reviewed: true}
expect:
valid: false
- name: compatible auxiliary membership is retained
operation: create
input:
path: tasks/review-valid.md
type: task
frontmatter: {title: Review paper, tags: [task], reviewed: true, reviewer: Alice}
expect:
valid: true
types: [review, task]
frontmatter_not_contains: [type, types, mdbase_type]
- name: Final path establishes membership
setup:
config: |
spec_version: "0.3.0"
settings:
explicit_type_keys: []
default_validation: error
types:
task.md: |
---
kind: mdbase.type
name: task
match:
path_glob: "tasks/**/*.md"
schema:
dialect: json-schema-2020-12
value: {type: object, required: [title], properties: {title: {type: string}}}
collection:
path:
pattern: "tasks/{title}.md"
---
tests:
- name: selected type can derive its matching path
operation: create
input:
type: task
frontmatter: {title: Milk}
expect:
valid: true
path: tasks/Milk.md
types: [task]
frontmatter_not_contains: [type, types, mdbase_type]
- name: supplied matching path succeeds
operation: create
input:
type: task
path: tasks/Milk.md
frontmatter: {title: Milk}
expect:
valid: true
types: [task]
- name: supplied nonmatching path cannot satisfy selection
operation: create
input:
type: task
path: other/Milk.md
frontmatter: {title: Milk}
expect:
valid: false
- name: Serialization policy cannot erase membership
setup:
config: |
spec_version: "0.3.0"
settings:
explicit_type_keys: []
write_empty_lists: false
default_validation: error
types:
task.md: |
---
kind: mdbase.type
name: task
match:
fields_present: [markers]
schema:
dialect: json-schema-2020-12
value: {type: object}
---
tests:
- name: omitted empty list cannot establish persisted membership
operation: create
input:
type: task
path: no.md
frontmatter: {markers: []}
expect:
valid: false
1 change: 1 addition & 0 deletions tests/v0.3/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ fixture_sets:
coverage_targets: [core_read, collection_semantics, links, core_write]
files:
- core/core-collection.yaml
- core/optional-membership.yaml
- id: data_contracts
description: Data contracts, type implementations, projections, stable digests, and conflicts.
coverage_targets: [core_read]
Expand Down