tasknotes-spec 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/00-overview.md +172 -0
  2. package/01-terminology.md +156 -0
  3. package/02-model-and-mapping.md +288 -0
  4. package/03-temporal-semantics.md +290 -0
  5. package/04-recurrence.md +398 -0
  6. package/05-operations.md +968 -0
  7. package/06-validation.md +267 -0
  8. package/07-conformance.md +292 -0
  9. package/08-compatibility-and-migrations.md +188 -0
  10. package/09-configuration.md +837 -0
  11. package/10-dependencies-and-reminders.md +266 -0
  12. package/11-links.md +373 -0
  13. package/CHANGELOG.md +25 -0
  14. package/README.md +80 -0
  15. package/conformance/README.md +31 -0
  16. package/conformance/adapters/mdbase-tasknotes.adapter.mjs +141 -0
  17. package/conformance/adapters/tasknotes-core/conformance.ts +2498 -0
  18. package/conformance/adapters/tasknotes-core/create-compat.ts +1 -0
  19. package/conformance/adapters/tasknotes-core/date.ts +12 -0
  20. package/conformance/adapters/tasknotes-core/field-mapping.ts +14 -0
  21. package/conformance/adapters/tasknotes-core/recurrence.ts +10 -0
  22. package/conformance/adapters/tasknotes-date-bridge.ts +20 -0
  23. package/conformance/adapters/tasknotes-runtime-bridge.ts +1107 -0
  24. package/conformance/adapters/tasknotes-runtime-obsidian-shim.ts +84 -0
  25. package/conformance/adapters/tasknotes-templating-bridge.ts +13 -0
  26. package/conformance/adapters/tasknotes.adapter.mjs +485 -0
  27. package/conformance/docs/ADAPTER_CONTRACT.md +245 -0
  28. package/conformance/docs/FIXTURE_FORMAT.md +247 -0
  29. package/conformance/docs/RUNNER_GUIDE.md +393 -0
  30. package/conformance/fixtures/config-schema.json +634 -0
  31. package/conformance/fixtures/config.json +18984 -0
  32. package/conformance/fixtures/conformance.json +444 -0
  33. package/conformance/fixtures/create-compat.json +18639 -0
  34. package/conformance/fixtures/date.json +25612 -0
  35. package/conformance/fixtures/dependencies.json +8647 -0
  36. package/conformance/fixtures/field-mapping.json +5406 -0
  37. package/conformance/fixtures/links.json +1127 -0
  38. package/conformance/fixtures/migrations.json +668 -0
  39. package/conformance/fixtures/operations.json +2761 -0
  40. package/conformance/fixtures/recurrence.json +22958 -0
  41. package/conformance/fixtures/reminders.json +13333 -0
  42. package/conformance/fixtures/templating.json +497 -0
  43. package/conformance/fixtures/validation.json +5308 -0
  44. package/conformance/lib/adapter-loader.mjs +23 -0
  45. package/conformance/lib/load-fixtures.mjs +43 -0
  46. package/conformance/lib/matchers.mjs +200 -0
  47. package/conformance/manifest.json +232 -0
  48. package/conformance/scripts/generate-fixtures.mjs +5239 -0
  49. package/conformance/scripts/package-fixtures.mjs +101 -0
  50. package/conformance/tests/coverage.test.mjs +213 -0
  51. package/conformance/tests/runner.test.mjs +102 -0
  52. package/conformance/tests/tasknotes-runtime-routing.test.mjs +64 -0
  53. package/package.json +49 -0
@@ -0,0 +1,245 @@
1
+ # Adapter Contract
2
+
3
+ Conformance fixtures are implementation-agnostic and live in `tasknotes-spec`. Each implementation provides an **adapter** — a callable component that bridges the conformance runner to the implementation under test.
4
+
5
+ ---
6
+
7
+ ## Conceptual interface
8
+
9
+ An adapter is any callable that satisfies this interface:
10
+
11
+ ```
12
+ execute(operation: string, input: object) → { ok: bool, result?: object, error?: string, error_details?: object }
13
+ ```
14
+
15
+ And a metadata query:
16
+
17
+ ```
18
+ metadata() → {
19
+ implementation: string,
20
+ version: string,
21
+ spec_version: string,
22
+ validation_modes: string[],
23
+ profiles: string[],
24
+ capabilities: string[]
25
+ }
26
+ ```
27
+
28
+ ### `execute`
29
+
30
+ - Accepts `operation` (a string operation name, e.g. `"date.parse_utc"`) and `input` (a plain object of arguments).
31
+ - Returns an **envelope** object:
32
+ - `ok` (boolean, required): `true` on success, `false` on failure
33
+ - `result` (object, optional): the operation's output, present when `ok` is `true`
34
+ - `error` (string, optional): a human-readable error message, present when `ok` is `false`
35
+ - `error_details` (object, optional): structured error fields (`operation`, `code`, `message`, optional `field/path`) when available
36
+ - Must never throw or panic for invalid inputs; all errors must be returned in the envelope.
37
+
38
+ ### `metadata` / `meta.claim`
39
+
40
+ The runner calls the `meta.claim` operation (with `input = {}`) to obtain adapter metadata. The result object must contain:
41
+
42
+ | Field | Type | Description |
43
+ |-------|------|-------------|
44
+ | `implementation` | string | Name of the implementation under test |
45
+ | `version` | string | Version of the implementation |
46
+ | `spec_version` | string | `tasknotes-spec` version targeted by the adapter |
47
+ | `validation_modes` | string[] | Supported validation modes; must include `strict` |
48
+ | `profiles` | string[] | Conformance profiles claimed (e.g. `["core-lite", "recurrence"]`) |
49
+ | `capabilities` | string[] | Optional capability tokens claimed (e.g. `["dependencies", "links"]`) |
50
+
51
+ Profile-token consistency requirements:
52
+
53
+ - If `profiles` includes `extended`, `capabilities` must include `dependencies`, `reminders`, `links`, and `time-tracking`.
54
+ - If `profiles` includes `templating`, `capabilities` must include `templating`.
55
+ - If `profiles` includes `materialized-occurrences`, `profiles` must also include `recurrence` and `capabilities` must include `materialized-occurrences`.
56
+
57
+ ---
58
+
59
+ ## Language bindings
60
+
61
+ ### JavaScript binding
62
+
63
+ For JavaScript implementations, an adapter is an **ESM module** that the runner imports directly. It must export:
64
+
65
+ - `metadata` — an object (not a function) with the fields above
66
+ - `execute(operation, input)` — an async function returning an envelope
67
+
68
+ The runner locates the adapter module via the `TASKNOTES_ADAPTER` environment variable (an absolute or relative path to the `.mjs` file).
69
+
70
+ **Minimal example**:
71
+
72
+ ```js
73
+ // my-adapter.mjs
74
+ export const metadata = {
75
+ implementation: "my-tasknotes",
76
+ version: "1.2.3",
77
+ spec_version: "0.2.0-draft",
78
+ validation_modes: ["strict"],
79
+ profiles: ["core-lite"],
80
+ capabilities: [],
81
+ };
82
+
83
+ export async function execute(operation, input) {
84
+ try {
85
+ const result = await myImplementation.run(operation, input);
86
+ return { ok: true, result };
87
+ } catch (err) {
88
+ return { ok: false, error: err.message };
89
+ }
90
+ }
91
+ ```
92
+
93
+ Run with:
94
+
95
+ ```bash
96
+ TASKNOTES_ADAPTER=./my-adapter.mjs npm run conformance:test
97
+ ```
98
+
99
+ ### Other language bindings
100
+
101
+ Any language may implement its own runner that calls the implementation directly, without the JS adapter module mechanism. The fixtures and this documentation are the normative source; the JS runner is the reference implementation.
102
+
103
+ A non-JS runner should:
104
+
105
+ 1. Load the fixture JSON files from `fixtures/` (see `manifest.json` for the file list)
106
+ 2. For each fixture, call the implementation's equivalent of `execute(operation, input)`
107
+ 3. Apply the assertions as specified in `FIXTURE_FORMAT.md`
108
+ 4. Skip fixtures whose `profile` is not satisfied by claimed profiles (including cumulative expansion rules from §7.3)
109
+ 5. Skip fixtures whose `requires[]` capabilities are not claimed by the implementation
110
+ 6. Report results (TAP output recommended; see `RUNNER_GUIDE.md`)
111
+
112
+ See `RUNNER_GUIDE.md` for a complete step-by-step guide to implementing a runner in any language.
113
+
114
+ ---
115
+
116
+ ## Supported operations
117
+
118
+ All operation names that appear in the fixture files are listed here. Operations marked "(requires capability)" are only exercised when the adapter claims that capability via `meta.claim`.
119
+
120
+ ### Core operations (no capability required)
121
+
122
+ - `date.parse_utc`
123
+ - `date.parse_local`
124
+ - `date.validate`
125
+ - `date.get_part`
126
+ - `date.has_time`
127
+ - `date.is_same`
128
+ - `date.is_before`
129
+ - `date.resolve_operation_target`
130
+ - `date.day_in_timezone`
131
+ - `field.default_mapping`
132
+ - `field.build_mapping`
133
+ - `field.normalize`
134
+ - `field.denormalize`
135
+ - `field.resolve_display_title`
136
+ - `field.is_completed_status`
137
+ - `field.default_completed_status`
138
+ - `recurrence.complete`
139
+ - `recurrence.recalculate`
140
+ - `recurrence.uncomplete_instance`
141
+ - `recurrence.skip_instance`
142
+ - `recurrence.unskip_instance`
143
+ - `recurrence.effective_state`
144
+ - `occurrence.materialize`
145
+ - `occurrence.complete`
146
+ - `occurrence.skip`
147
+ - `occurrence.uncomplete`
148
+ - `occurrence.unskip`
149
+ - `occurrence.unmaterialize`
150
+ - `create_compat.create`
151
+ - `meta.claim`
152
+ - `meta.has_capability`
153
+ - `meta.has_profile`
154
+ - `config.resolve_collection_path`
155
+ - `config.merge_top_level`
156
+ - `config.spec_version_effective`
157
+ - `config.map_tasknotes_plugin`
158
+ - `config.detect_task_file`
159
+ - `config.provider_behavior`
160
+ - `config.validate_schema`
161
+ - `validation.core_evaluate`
162
+ - `validation.time_entries`
163
+ - `op.mutate_with_validation`
164
+ - `op.atomic_write`
165
+ - `op.idempotency_check`
166
+ - `op.update_patch`
167
+ - `op.complete_nonrecurring`
168
+ - `op.uncomplete_nonrecurring`
169
+ - `op.error_shape`
170
+ - `delete.remove`
171
+
172
+ ### Capability-gated operations
173
+
174
+ | Operation | Required capability |
175
+ |-----------|-------------------|
176
+ | `dependency.validate_entry` | `dependencies` |
177
+ | `dependency.validate_set` | `dependencies` |
178
+ | `dependency.add` | `dependencies` |
179
+ | `dependency.remove` | `dependencies` |
180
+ | `dependency.replace` | `dependencies` |
181
+ | `dependency.missing_target_behavior` | `dependencies` |
182
+ | `reminder.validate_entry` | `reminders` |
183
+ | `reminder.validate_set` | `reminders` |
184
+ | `reminder.add` | `reminders` |
185
+ | `reminder.update` | `reminders` |
186
+ | `reminder.remove` | `reminders` |
187
+ | `link.parse` | `links` |
188
+ | `link.resolve` | `links` |
189
+ | `link.update_references_on_rename` | `links` + `rename` |
190
+ | `occurrence.materialize` | `materialized-occurrences` |
191
+ | `occurrence.complete` | `materialized-occurrences` |
192
+ | `occurrence.skip` | `materialized-occurrences` |
193
+ | `occurrence.uncomplete` | `materialized-occurrences` |
194
+ | `occurrence.unskip` | `materialized-occurrences` |
195
+ | `occurrence.unmaterialize` | `materialized-occurrences` |
196
+ | `templating.expand_variables` | `templating` |
197
+ | `templating.merge_frontmatter` | `templating` |
198
+ | `templating.handle_failure` | `templating` |
199
+ | `templating.parse_sections` | `templating` |
200
+ | `templating.tokenize` | `templating` |
201
+ | `templating.create_pipeline` | `templating` |
202
+ | `templating.config_defaults` | `templating` |
203
+ | `templating.profile_claim_requirements` | `templating` |
204
+ | `migration.normalize_aliases` | `migration` |
205
+ | `migration.compat_mode` | `migration` |
206
+ | `migration.plan` | `migration` |
207
+ | `migration.normalize_temporal` | `migration` |
208
+ | `migration.resolve_instance_overlap` | `migration` |
209
+ | `migration.normalize_dependencies` | `migration` + `dependencies` |
210
+ | `migration.normalize_reminders` | `migration` + `reminders` |
211
+ | `migration.normalize_links` | `migration` + `links` |
212
+ | `migration.report_summary` | `migration` |
213
+ | `migration.divergence_register` | `migration` |
214
+ | `migration.deprecation_policy` | `migration` |
215
+ | `migration.safety_guards` | `migration` |
216
+ | `migration.compat_statement` | `migration` |
217
+ | `archive.apply` | `archive` |
218
+ | `rename.apply` | `rename` |
219
+ | `rename.title_storage_interaction` | `rename` |
220
+ | `batch.apply` | `batch` |
221
+ | `op.detect_conflict` | `concurrency` |
222
+ | `op.dry_run` | `dry-run` |
223
+ | `time.start` | `time-tracking` |
224
+ | `time.stop` | `time-tracking` |
225
+ | `time.replace_entries` | `time-tracking` |
226
+ | `time.remove_entry` | `time-tracking` |
227
+ | `time.auto_stop_on_complete` | `time-tracking` |
228
+ | `time.report_totals` | `time-tracking` |
229
+
230
+ ---
231
+
232
+ ## Fixture shape
233
+
234
+ Fixture files are JSON arrays. Each element is a fixture object with fields defined in `FIXTURE_FORMAT.md`.
235
+
236
+ Summary of fields:
237
+
238
+ - `id` (string, unique)
239
+ - `section` (string, spec reference)
240
+ - `profile` (string)
241
+ - `operation` (string)
242
+ - `assertion` (string)
243
+ - `requires` (string[], optional)
244
+ - `input` (object)
245
+ - `expect` (object, conditional)
@@ -0,0 +1,247 @@
1
+ # Fixture Format
2
+
3
+ Each fixture is a JSON object:
4
+
5
+ ```json
6
+ {
7
+ "id": "date.parse_utc.valid.0001",
8
+ "section": "§3",
9
+ "profile": "core-lite",
10
+ "operation": "date.parse_utc",
11
+ "assertion": "envelope_equals",
12
+ "requires": [],
13
+ "input": { "value": "2026-02-20" },
14
+ "expect": {
15
+ "ok": true,
16
+ "result": { "date": "2026-02-20" }
17
+ }
18
+ }
19
+ ```
20
+
21
+ ## Fields
22
+
23
+ | Field | Type | Required | Description |
24
+ |-------|------|----------|-------------|
25
+ | `id` | string | yes | Unique fixture identifier, e.g. `date.parse_utc.valid.0001` |
26
+ | `section` | string | yes | Spec section this fixture exercises, e.g. `§3` |
27
+ | `profile` | string | yes | Conformance profile: `core-lite`, `recurrence`, `extended`, `templating`, or `materialized-occurrences`; runner skips when adapter claim does not satisfy profile (including cumulative expansion rules) |
28
+ | `operation` | string | yes | Operation name passed to the adapter, e.g. `date.parse_utc` |
29
+ | `assertion` | string | yes | Assertion kind; see below |
30
+ | `requires` | string[] | no | Capability tokens required; after profile check, runner skips if the adapter does not claim all listed capabilities |
31
+ | `input` | object | yes | Arguments passed to the operation |
32
+ | `expect` | object | conditional | Expected outcome; required for `envelope_equals` and `envelope_error` |
33
+
34
+ ---
35
+
36
+ ## Assertion kinds
37
+
38
+ ### `envelope_equals`
39
+
40
+ Recursively deep-matches the adapter's response envelope against `expect` using the matcher directives described below. Every key in `expect` must be present and matching in the actual envelope; extra keys in the actual envelope are allowed.
41
+
42
+ **Inputs**: `input` (passed to operation), `expect` (partial envelope to match)
43
+
44
+ **Passes when**: `deepMatch(actualEnvelope, expect)` succeeds without error.
45
+
46
+ **Example**:
47
+ ```json
48
+ {
49
+ "assertion": "envelope_equals",
50
+ "input": { "value": "2026-02-20" },
51
+ "expect": { "ok": true, "result": { "date": "2026-02-20" } }
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ### `envelope_error`
58
+
59
+ Asserts that the operation returns a failure envelope, and optionally matches the error message.
60
+
61
+ **Passes when**:
62
+ 1. `envelope.ok === false`
63
+ 2. If `expect.error` is present: `deepMatch(envelope.error, expect.error)` succeeds
64
+
65
+ **Example**:
66
+ ```json
67
+ {
68
+ "assertion": "envelope_error",
69
+ "input": { "value": "not-a-date" },
70
+ "expect": { "error": { "$regex": "invalid" } }
71
+ }
72
+ ```
73
+
74
+ If `expect` is absent or `expect.error` is absent, only `ok === false` is checked.
75
+
76
+ ---
77
+
78
+ ### `recurrence_complete_invariants`
79
+
80
+ Asserts structural and semantic invariants on the result of a `recurrence.complete` operation. Does **not** use `expect`; instead applies fixed logical checks derived from `input`.
81
+
82
+ **Passes when ALL of the following hold**:
83
+
84
+ 1. `envelope.ok === true`
85
+ 2. `result.completeInstances` is an array
86
+ 3. `result.skippedInstances` is an array
87
+ 4. `result.completeInstances` **includes** `input.completionDate`
88
+ 5. `result.skippedInstances` does **not** include `input.completionDate`
89
+ 6. `result.updatedRecurrence` matches `/FREQ=/`
90
+ 7. `result.updatedRecurrence` matches `/DTSTART:/`
91
+ 8. **Anchor-conditional DTSTART day** (if `input.recurrenceAnchor === "completion"`):
92
+ - Extract `dtstartDay` = `input.completionDate` with hyphens removed (e.g. `"2026-03-01"` → `"20260301"`)
93
+ - `result.updatedRecurrence` matches `/DTSTART:20260301(?:;|$)/`
94
+ 9. **Scheduled anchor** (if `input.recurrenceAnchor === "scheduled"` and `input.scheduled` is a string):
95
+ - Extract `dtstartDay` = first 10 chars of `input.scheduled` with hyphens removed
96
+ - `result.updatedRecurrence` matches `/DTSTART:{dtstartDay}(?:;|$)/`
97
+ 10. If `result.nextScheduled` is present:
98
+ - It matches `/^\d{4}-\d{2}-\d{2}/`
99
+ - `result.nextScheduled.slice(0, 10) >= input.completionDate` (string comparison)
100
+ 11. If `result.nextScheduled`, `result.nextDue`, `input.scheduled`, and `input.due` are all present strings:
101
+ - `originalOffset` = `(Date(input.due[0:10]) - Date(input.scheduled[0:10]))` in whole days
102
+ - `(Date(result.nextDue[0:10]) - Date(result.nextScheduled[0:10]))` in whole days === `originalOffset`
103
+
104
+ ---
105
+
106
+ ### `recurrence_recalculate_invariants`
107
+
108
+ Asserts structural and semantic invariants on the result of a `recurrence.recalculate` operation. Does **not** use `expect`.
109
+
110
+ **Passes when ALL of the following hold**:
111
+
112
+ 1. `envelope.ok === true`
113
+ 2. `result.updatedRecurrence` matches `/FREQ=/`
114
+ 3. If `input.recurrenceAnchor === "scheduled"`:
115
+ - `result.updatedRecurrence` matches `/DTSTART:/`
116
+ 4. If `result.nextScheduled` is present:
117
+ - `result.nextScheduled.slice(0, 10) >= input.referenceDate` (string comparison)
118
+ - Let `complete = input.completeInstances ?? []` and `skipped = input.skippedInstances ?? []`
119
+ - If `input.recurrenceAnchor !== "completion"`: `complete` does **not** include `result.nextScheduled.slice(0, 10)`
120
+ - `skipped` does **not** include `result.nextScheduled.slice(0, 10)`
121
+ 5. If `result.nextScheduled`, `result.nextDue`, `input.scheduled`, and `input.due` are all present strings:
122
+ - `originalOffset` = `(Date(input.due[0:10]) - Date(input.scheduled[0:10]))` in whole days
123
+ - `(Date(result.nextDue[0:10]) - Date(result.nextScheduled[0:10]))` in whole days === `originalOffset`
124
+
125
+ ---
126
+
127
+ ### `create_compat_invariants`
128
+
129
+ Asserts the result of a `create_compat.create` operation. Combines `envelope_equals` matching with additional path invariants.
130
+
131
+ **Passes when ALL of the following hold**:
132
+
133
+ 1. `deepMatch(actualEnvelope, expect)` succeeds (same as `envelope_equals`)
134
+ 2. If `envelope.ok === true` and `envelope.result.path` is present:
135
+ - `envelope.result.path` ends with `.md`
136
+ - `envelope.result.path` does **not** contain `{`
137
+ - `envelope.result.path` does **not** contain `}`
138
+
139
+ The path checks ensure template variables were fully expanded and no literal brace characters remain.
140
+
141
+ ---
142
+
143
+ ## Matcher directives in `expect`
144
+
145
+ Matcher directives are special single-key objects that appear within `expect` values. When the deep-matcher encounters an object with exactly one of these keys, it applies the corresponding matching logic instead of a structural equality check.
146
+
147
+ Directives may appear at any level of nesting. The matching context (the full `input` object) is threaded through all recursive calls to support `$ref`.
148
+
149
+ ### `$regex`
150
+
151
+ ```json
152
+ { "$regex": "^\\d{4}-\\d{2}-\\d{2}$" }
153
+ ```
154
+
155
+ Asserts the actual value is a **string** matching the given regular expression. The pattern is interpreted as a standard ECMAScript regex (compatible with most language regex engines).
156
+
157
+ **Algorithm**:
158
+ ```
159
+ if actual is not a string → fail
160
+ compile $regex as a regex pattern
161
+ if actual does not match pattern → fail
162
+ ```
163
+
164
+ ---
165
+
166
+ ### `$contains`
167
+
168
+ ```json
169
+ { "$contains": ["item1", "item2"] }
170
+ ```
171
+
172
+ or for objects:
173
+
174
+ ```json
175
+ { "$contains": { "key": "value" } }
176
+ ```
177
+
178
+ **For arrays**: asserts the actual array contains at least one element matching each item in the `$contains` array (order-independent). Each element is matched with full `deepMatch`.
179
+
180
+ **For objects**: asserts the actual object contains all key-value pairs in the `$contains` object. Values are matched with full `deepMatch`.
181
+
182
+ **Algorithm (array)**:
183
+ ```
184
+ if actual is not an array → fail
185
+ for each expectedItem in $contains:
186
+ if no element in actual deepMatches expectedItem → fail
187
+ ```
188
+
189
+ **Algorithm (object)**:
190
+ ```
191
+ if actual is not an object → fail
192
+ for each (key, value) in $contains:
193
+ deepMatch(actual[key], value)
194
+ ```
195
+
196
+ ---
197
+
198
+ ### `$oneOf`
199
+
200
+ ```json
201
+ { "$oneOf": ["OPEN", "IN_PROGRESS", null] }
202
+ ```
203
+
204
+ Asserts the actual value matches **at least one** of the listed alternatives. Each alternative is matched with full `deepMatch` (so alternatives may themselves contain directives).
205
+
206
+ **Algorithm**:
207
+ ```
208
+ for each option in $oneOf:
209
+ try deepMatch(actual, option)
210
+ if it succeeds → pass
211
+ fail (no option matched)
212
+ ```
213
+
214
+ ---
215
+
216
+ ### `$ref`
217
+
218
+ ```json
219
+ { "$ref": "input.completionDate" }
220
+ ```
221
+
222
+ Resolves a dotted path into the fixture's `input` object and uses the resolved value as the expected value. The path must start with `"input."`.
223
+
224
+ **Algorithm**:
225
+ ```
226
+ if ref does not start with "input." → treat as literal value (no resolution)
227
+ segments = ref["input.".length:].split(".")
228
+ current = context.input
229
+ for each segment in segments:
230
+ if current is null or not an object → resolved = undefined; break
231
+ current = current[segment]
232
+ resolved = current
233
+ deepMatch(actual, resolved)
234
+ ```
235
+
236
+ **Example**: if `input = { "completionDate": "2026-03-01" }` and the expect field is `{ "$ref": "input.completionDate" }`, the matcher checks `actual === "2026-03-01"`.
237
+
238
+ ---
239
+
240
+ ## Reference implementation
241
+
242
+ The canonical implementation of all matchers and assertion kinds is:
243
+
244
+ - **Matchers**: `conformance/lib/matchers.mjs` — `applyAssertion(caseDef, envelope)` and internal `deepMatch`
245
+ - **Runner**: `conformance/tests/runner.test.mjs` — loads fixtures, calls adapter, applies assertions
246
+
247
+ When this documentation and the reference implementation disagree, file an issue; the intent is for them to be identical.