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
package/00-overview.md ADDED
@@ -0,0 +1,172 @@
1
+ # 0. Overview
2
+
3
+ ## 0.0 About tasknotes
4
+
5
+ **TaskNotes** is an [Obsidian](https://obsidian.md) plugin that stores tasks as individual markdown files with YAML frontmatter in a user's vault. Each task is a first-class file: readable and editable with any text editor, version-controllable, and searchable without special tooling.
6
+
7
+ A minimal task file looks like this:
8
+
9
+ ```markdown
10
+ ---
11
+ title: Buy groceries
12
+ status: open
13
+ priority: normal
14
+ due: 2026-02-21
15
+ tags: [task]
16
+ dateCreated: 2026-02-20T11:15:00Z
17
+ dateModified: 2026-02-20T11:15:00Z
18
+ ---
19
+
20
+ Buy fruit and cleaning supplies.
21
+ ```
22
+
23
+ Tasks are identified within the vault by a configurable detection method — by default, by the presence of a specific tag (e.g. `#task` in body or frontmatter). The plugin stores its configuration in `.obsidian/plugins/tasknotes/data.json` within the vault root.
24
+
25
+ **This specification** was derived from the behavior of the tasknotes plugin and is intended to serve as a stable, testable contract for:
26
+
27
+ - CLI tools and scripts that process task files outside Obsidian,
28
+ - any application that wants to interoperate with a tasknotes vault.
29
+
30
+ The spec is deliberately language-agnostic. It does not require any particular runtime, and the executable conformance suite (in `conformance/`) can be driven by an adapter written in any language.
31
+
32
+ The tasknotes plugin is the primary reference for how this spec was derived, but **the specification is normative** — where the spec and the plugin behavior differ, the spec defines the intended behavior. Known deviations are disclosed via conformance claims (§7.5).
33
+
34
+ ## 0.1 Status and authority
35
+
36
+ This document is part of `tasknotes-spec` version `0.2.0-draft`.
37
+
38
+ This specification is normative for implementations that claim conformance.
39
+ No single implementation is normative; implementations are expected to conform to the specification.
40
+
41
+ ## 0.2 Motivation
42
+
43
+ TaskNotes-style systems store tasks as markdown files with YAML frontmatter. This model has practical advantages:
44
+
45
+ - Task records are plain files that can be inspected and edited without proprietary tooling.
46
+ - Data remains portable across environments.
47
+ - Files are suitable for version control and collaboration workflows.
48
+
49
+ As soon as more than one tool reads and writes the same task files, implicit assumptions become an interoperability risk. In practice, these risks appear in three areas:
50
+
51
+ 1. Field semantics and naming drift.
52
+ 2. Date and timezone interpretation differences.
53
+ 3. Recurring-task state transitions that are handled differently by different clients.
54
+
55
+ When these rules are not explicit, users can get inconsistent results from equivalent operations. A shared specification reduces that risk by making behavior testable and versioned.
56
+
57
+ ## 0.3 Objectives
58
+
59
+ This specification defines a stable contract for:
60
+
61
+ - Representing task records in markdown frontmatter.
62
+ - Mapping storage keys to canonical semantic roles.
63
+ - Defining effective collection configuration via provider model (for example `tasknotes.yaml` and/or TaskNotes `data.json`).
64
+ - Interpreting date and datetime values consistently.
65
+ - Applying recurrence, per-instance completion/skip behavior, and optional materialized occurrence notes.
66
+ - Managing time tracking sessions and `time_entries` lifecycle behavior.
67
+ - Parsing and resolving links consistently across task fields.
68
+ - Defining dependency and reminder behavior.
69
+ - Optionally applying deterministic create-time templating behavior.
70
+ - Executing write operations with predictable side-effects.
71
+ - Validating task records and reporting issues.
72
+
73
+ ## 0.4 Non-objectives
74
+
75
+ This specification does not standardize:
76
+
77
+ - UI layout, styling, command palettes, or interaction design.
78
+ - Internal caching architecture or rendering pipelines.
79
+ - Transport protocols (HTTP, IPC, etc.) except where they affect persisted task semantics.
80
+ - Rich template-language features beyond the portable create-time templating contract in §5/§7/§9.
81
+
82
+ ## 0.5 Scope
83
+
84
+ This specification is standalone. It does not require conformance to any other specification.
85
+
86
+ Implementations MAY internally reuse other libraries or specifications, but conformance claims under `tasknotes-spec` are evaluated only against this document.
87
+ Templating is optional and profile-gated; implementations that do not claim the templating profile remain conformant without template support.
88
+ References to TaskNotes plugin behavior, Obsidian behavior, or external specifications are informative context only unless this specification explicitly marks a requirement as normative.
89
+
90
+ ## 0.6 Design principles
91
+
92
+ ### 0.6.1 File-first persistence
93
+
94
+ Task state is represented in user-visible files. Derived indexes and caches are non-canonical.
95
+
96
+ ### 0.6.2 Deterministic semantics
97
+
98
+ Equivalent inputs MUST produce equivalent persisted outputs under the same configuration and active runtime timezone.
99
+
100
+ ### 0.6.3 Explicit mapping
101
+
102
+ Semantic roles are canonical. Storage key names are configurable and therefore must be mapped explicitly.
103
+
104
+ ### 0.6.4 Backward evolution
105
+
106
+ The specification supports migration from legacy keys and historical behaviors through explicit compatibility rules.
107
+
108
+ ## 0.7 Conformance
109
+
110
+ Conformance is profile-based (see §7). A minimal implementation can conform to a narrow profile while remaining interoperable for core operations.
111
+
112
+ ## 0.8 Normative language
113
+
114
+ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** are to be interpreted as described in RFC 2119.
115
+
116
+ ## 0.9 Example collection
117
+
118
+ A default tasknotes vault looks like this:
119
+
120
+ ```text
121
+ MyVault/
122
+ ├── TaskNotes/
123
+ │ ├── Tasks/
124
+ │ │ ├── buy-groceries.md ← task file (title-derived filename by default)
125
+ │ │ └── weekly-review.md
126
+ │ └── Archive/ ← archive folder (if moveArchivedTasks=true)
127
+ ├── .obsidian/
128
+ │ └── plugins/
129
+ │ └── tasknotes/
130
+ │ └── data.json ← plugin settings (primary config provider)
131
+ └── tasknotes.yaml ← optional spec-level config (secondary provider)
132
+ ```
133
+
134
+ Task files are identified by the `#task` tag by default (configurable). The full default collection state is described in §9.21.
135
+
136
+ Configuration provider semantics and effective configuration rules are defined in §9. An implementation MAY use either provider, both, or additional providers, based on its documented precedence policy.
137
+
138
+ A task file example (using default field mapping):
139
+
140
+ ```markdown
141
+ ---
142
+ title: Buy groceries
143
+ status: open
144
+ priority: normal
145
+ due: 2026-02-21
146
+ tags: [task, errands]
147
+ contexts: ["town"]
148
+ dateCreated: 2026-02-20T11:15:00Z
149
+ dateModified: 2026-02-20T11:15:00Z
150
+ ---
151
+
152
+ Buy fruit and cleaning supplies.
153
+ ```
154
+
155
+ ## 0.10 Versioning policy
156
+
157
+ This specification uses semantic versioning.
158
+
159
+ - A **major** version introduces breaking semantic changes.
160
+ - A **minor** version introduces additive behavior or optional features.
161
+ - A **patch** version clarifies wording or fixes non-breaking defects.
162
+
163
+ Implementations MUST reject unsupported major versions when strict mode is enabled.
164
+
165
+ ## 0.11 Change governance
166
+
167
+ Specification changes SHOULD be accompanied by:
168
+
169
+ - a motivation statement,
170
+ - precise normative edits,
171
+ - migration notes when behavior changes,
172
+ - updated examples.
@@ -0,0 +1,156 @@
1
+ # 1. Terminology
2
+
3
+ This section defines terms used normatively in this specification.
4
+
5
+ ## 1.1 Collection
6
+
7
+ A **collection** is a filesystem scope within which task files are discovered and managed under a single TaskNotes configuration.
8
+
9
+ ## 1.2 Task file
10
+
11
+ A **task file** is a markdown file identified as a task record by collection rules.
12
+
13
+ ## 1.3 Frontmatter
14
+
15
+ **Frontmatter** is the YAML block at the start of a markdown file delimited by `---` markers.
16
+
17
+ ## 1.4 Task record
18
+
19
+ A **task record** is the semantic task object obtained from parsing a task file frontmatter (plus optional derived metadata).
20
+
21
+ ## 1.5 Semantic role
22
+
23
+ A **semantic role** is a canonical meaning defined by this specification, independent of storage key names.
24
+
25
+ Examples: `title`, `status`, `date_created`, `complete_instances`.
26
+
27
+ ## 1.6 Storage key
28
+
29
+ A **storage key** is the YAML property name used in frontmatter.
30
+
31
+ Examples: `dateCreated`, `date_created`, `completedDate`.
32
+
33
+ ## 1.7 Field mapping
34
+
35
+ **Field mapping** is the configuration that maps semantic roles to storage keys for reads and writes.
36
+
37
+ ## 1.8 Alias
38
+
39
+ An **alias** is a non-canonical storage key accepted for compatibility during reads.
40
+
41
+ ## 1.9 Canonical write key
42
+
43
+ The **canonical write key** is the configured storage key that conforming writers use for persisted output.
44
+
45
+ ## 1.10 Date value
46
+
47
+ A **date value** represents a calendar day with no time-of-day and no timezone.
48
+
49
+ Canonical form: `YYYY-MM-DD`.
50
+
51
+ ## 1.11 Datetime value
52
+
53
+ A **datetime value** represents an instant in time.
54
+
55
+ Canonical form: ISO 8601 UTC with trailing `Z`.
56
+
57
+ ## 1.12 Target date
58
+
59
+ A **target date** is the calendar date to which a recurrence instance operation applies (for example complete instance on 2026-02-20).
60
+
61
+ ## 1.13 Recurrence rule
62
+
63
+ A **recurrence rule** is a tasknotes recurrence string stored in the recurrence semantic role.
64
+ It uses RFC 5545 RRULE-style parameters inside a single semicolon-delimited field value and MAY include an explicit inline `DTSTART` prefix segment as defined in §4.3.
65
+
66
+ ## 1.14 Recurrence anchor
67
+
68
+ **Recurrence anchor** defines how next-instance progression is computed. Allowed values:
69
+
70
+ - `scheduled`
71
+ - `completion`
72
+
73
+ ## 1.15 Complete instances
74
+
75
+ **Complete instances** is the set/list of target dates marked completed for a recurring task.
76
+
77
+ ## 1.16 Skipped instances
78
+
79
+ **Skipped instances** is the set/list of target dates marked skipped for a recurring task.
80
+
81
+ ## 1.17 Materialized occurrence note
82
+
83
+ A **materialized occurrence note** is a task file created for one target date of a recurring parent task.
84
+ It stores user-authored per-occurrence content and, when supported by the implementation, owns the authoritative task state for that occurrence date.
85
+
86
+ ## 1.18 Recurrence parent
87
+
88
+ A **recurrence parent** is the recurring task record referenced by a materialized occurrence note.
89
+ The reference is stored in semantic role `recurrence_parent` and resolved as a link according to §11.
90
+
91
+ ## 1.19 Occurrence materialization
92
+
93
+ **Occurrence materialization** is the creation and reconciliation of materialized occurrence notes from a recurring parent task according to §4.18 and §5.20.
94
+
95
+ ## 1.20 Effective status
96
+
97
+ **Effective status** is the status presented for a given date context after applying recurrence instance state.
98
+
99
+ ## 1.21 Completed-status list
100
+
101
+ The **completed-status list** is the configured ordered list of status values treated as completed for non-recurring completion semantics.
102
+ When a single value must be chosen deterministically, the first list entry is used unless explicit operation input overrides it.
103
+
104
+ ## 1.22 Idempotent operation
105
+
106
+ An operation is **idempotent** if applying it multiple times with the same input produces the same persisted state as applying it once.
107
+
108
+ ## 1.23 Unknown field
109
+
110
+ An **unknown field** is a frontmatter property not mapped to a semantic role in current configuration.
111
+
112
+ ## 1.24 Validation issue
113
+
114
+ A **validation issue** is a structured report containing at least:
115
+
116
+ - machine-readable code,
117
+ - severity,
118
+ - optional path/field context,
119
+ - human-readable message.
120
+
121
+ ## 1.25 Strict validation mode
122
+
123
+ **Strict validation mode** is a mode where invalid required semantics are treated as hard errors and write operations fail.
124
+
125
+ ## 1.26 Legacy compatibility mode
126
+
127
+ **Legacy compatibility mode** is a mode where specific historical behaviors or aliases are accepted for migration but are not canonical for new writes.
128
+
129
+ ## 1.27 Configuration provider
130
+
131
+ A **configuration provider** is an adapter that loads configuration from a source (for example `tasknotes.yaml` or `.obsidian/plugins/tasknotes/data.json`) and normalizes it to the schema in §9.
132
+
133
+ ## 1.28 Effective configuration
134
+
135
+ The **effective configuration** is the final resolved configuration after applying provider precedence and fallback rules.
136
+
137
+ ## 1.29 Template file
138
+
139
+ A **template file** is a markdown document used at create time to generate frontmatter and/or body content through variable expansion.
140
+ When templating is enabled, template behavior is defined by §5.3.5 and §9.14.
141
+
142
+ ## 1.30 Template expansion
143
+
144
+ **Template expansion** is deterministic replacement of template variables with create-time task data and runtime date/time values, followed by template merge rules.
145
+
146
+ ## 1.31 Time entry
147
+
148
+ A **time entry** is a structured record in `time_entries` containing `startTime`, optional `endTime`, and optional `description`.
149
+
150
+ ## 1.32 Active time entry
151
+
152
+ An **active time entry** is a time entry with `startTime` present and `endTime` absent.
153
+
154
+ ## 1.33 Time tracking management
155
+
156
+ **Time tracking management** is the set of operations that start, stop, edit, and remove `time_entries`, including completion-triggered auto-stop behavior when configured.
@@ -0,0 +1,288 @@
1
+ # 2. Model and Field Mapping
2
+
3
+ ## 2.1 Task record model
4
+
5
+ A task record consists of:
6
+
7
+ - frontmatter object (structured fields),
8
+ - markdown body (freeform text),
9
+ - file metadata (path, created/modified time) where available.
10
+
11
+ Frontmatter is normative for task semantics. Body content is non-normative except where an implementation exposes body search or body-derived features.
12
+
13
+ ## 2.2 Required semantic roles
14
+
15
+ Conforming implementations MUST support these semantic roles:
16
+
17
+ | Semantic role | Type | Required |
18
+ |---|---|---|
19
+ | `title` | string | support required (value required via configured title source, see §2.2.2) |
20
+ | `status` | string/enum | yes |
21
+ | `completed_date` | date | support required (conditionally required, see §2.2.1) |
22
+ | `date_created` | datetime | yes |
23
+ | `date_modified` | datetime | yes |
24
+
25
+ If storage omits a role marked `yes`, validation MUST report an error (see §6).
26
+ For operation-required roles, validation and operation rules in §5 and §6 apply.
27
+
28
+ ### 2.2.1 Conditional requiredness (`completed_date`)
29
+
30
+ `completed_date` support is mandatory, but presence is conditional:
31
+
32
+ - For non-recurring tasks with `status` in configured `status.completed_values`, `completed_date` MUST be present.
33
+ - For non-recurring tasks with non-completed status, `completed_date` MAY be absent.
34
+ - For recurring tasks, `completed_date` is not required by recurrence completion semantics and MAY be absent.
35
+
36
+ Validators MUST apply this conditional rule and MUST NOT treat `completed_date` as unconditionally required.
37
+
38
+ ### 2.2.2 Conditional requiredness (`title`)
39
+
40
+ Semantic `title` is always required, with deterministic read resolution and storage-mode-dependent read/write behavior:
41
+
42
+ - Readers MUST resolve semantic title using storage-mode-aware precedence (§9.13):
43
+ 1. when `title.storage=frontmatter`: mapped `title` key when present and non-empty, then file basename fallback;
44
+ 2. when `title.storage=filename`: file basename first, then mapped `title` fallback when basename is unavailable.
45
+ - If mapped title and filename-derived title both exist and differ, the source authoritative for active `title.storage` MUST win and implementations SHOULD emit `title_source_conflict`.
46
+ - When `title.storage=filename`, implementations MAY keep mapped `title` as a synchronized compatibility mirror, but filename-derived title remains authoritative.
47
+ - Title storage mode controls canonical writes (§9.13): `frontmatter` favors mapped-key writes; `filename` favors filename-derived title with rename-on-title-change behavior.
48
+
49
+ Validators MUST enforce semantic title presence using the active title-resolution policy (§9.13), not frontmatter key presence alone.
50
+
51
+ ## 2.3 Common semantic roles
52
+
53
+ Implementations conforming beyond minimal scope SHOULD support:
54
+
55
+ | Semantic role | Type | Notes |
56
+ |---|---|---|
57
+ | `id` | string | stable task identity token; see §2.6.5 |
58
+ | `priority` | string/enum | configurable values |
59
+ | `due` | date or datetime | see §3 |
60
+ | `scheduled` | date or datetime | see §3 |
61
+ | `tags` | list<string> | free tags |
62
+ | `contexts` | list<string> | commonly prefixed with `@` |
63
+ | `projects` | list<link-or-string> | project references, see §11 |
64
+ | `time_estimate` | integer >= 0 | minutes |
65
+ | `time_entries` | list<object> | see §2.6 |
66
+ | `recurrence` | string | tasknotes recurrence string; see §4 |
67
+ | `recurrence_anchor` | enum | `scheduled` or `completion` |
68
+ | `complete_instances` | list<date> | recurring completion state |
69
+ | `skipped_instances` | list<date> | recurring skip state |
70
+ | `recurrence_parent` | link-or-string | parent recurring task reference for materialized occurrence notes; see §4.18 |
71
+ | `occurrence_date` | date | target date represented by a materialized occurrence note |
72
+ | `occurrence_materialization` | enum | parent task materialization mode: `manual`, `on_completion`, or `rolling` |
73
+ | `occurrence_next_trigger` | enum | parent task next-note trigger: `completion` or `completion_or_skip` |
74
+ | `occurrence_template` | link-or-string | optional template used when creating materialized occurrence notes |
75
+ | `occurrence_past_horizon` | duration | optional rolling materialization lookbehind bound |
76
+ | `occurrence_future_horizon` | duration | optional rolling materialization lookahead bound |
77
+ | `blocked_by` | list<object> | dependency records, see §10 and §11 |
78
+ | `reminders` | list<object> | reminder records, see §10 |
79
+
80
+ Optional extended roles are defined in §7 profiles.
81
+
82
+ ## 2.4 Field mapping requirements
83
+
84
+ ### 2.4.1 Mapping presence
85
+
86
+ An implementation MUST have an effective mapping from each supported semantic role to a canonical write key.
87
+ Mapping configuration is defined in §9.
88
+ If semantic role `id` is supported, it MUST also have a canonical write key.
89
+
90
+ ### 2.4.2 Read behavior
91
+
92
+ Readers MUST:
93
+
94
+ 1. Read the canonical mapped key if present.
95
+ 2. Support configured aliases where enabled.
96
+ 3. Resolve conflicts deterministically when canonical and alias keys coexist.
97
+ 4. When canonical and alias keys both exist for the same semantic role, canonical key value MUST win.
98
+ 5. When canonical key wins over alias, validator/loader SHOULD emit warning `alias_conflict_ignored`.
99
+
100
+ ### 2.4.3 Write behavior
101
+
102
+ Writers MUST:
103
+
104
+ - write only canonical keys,
105
+ - avoid introducing alias keys in new writes,
106
+ - preserve unknown fields unless the operation explicitly opts into normalization.
107
+
108
+ ## 2.5 Legacy alias compatibility
109
+
110
+ Implementations SHOULD accept these aliases on read for interoperability:
111
+
112
+ | Semantic role | Canonical example | Alias examples |
113
+ |---|---|---|
114
+ | `recurrence_anchor` | `recurrence_anchor` | `recurrenceAnchor` |
115
+ | `complete_instances` | `complete_instances` | `completeInstances` |
116
+ | `skipped_instances` | `skipped_instances` | `skippedInstances` |
117
+ | `recurrence_parent` | `recurrence_parent` | `recurrenceParent` |
118
+ | `occurrence_date` | `occurrence_date` | `occurrenceDate` |
119
+ | `occurrence_materialization` | `occurrence_materialization` | `occurrenceMaterialization` |
120
+ | `occurrence_next_trigger` | `occurrence_next_trigger` | `occurrenceNextTrigger` |
121
+ | `occurrence_template` | `occurrence_template` | `occurrenceTemplate` |
122
+ | `occurrence_past_horizon` | `occurrence_past_horizon` | `occurrencePastHorizon` |
123
+ | `occurrence_future_horizon` | `occurrence_future_horizon` | `occurrenceFutureHorizon` |
124
+ | `date_created` | `dateCreated` | `date_created` |
125
+ | `date_modified` | `dateModified` | `date_modified` |
126
+ | `completed_date` | `completedDate` | `completed_date` |
127
+ | `time_entries` | `timeEntries` | `time_entries` |
128
+ | `time_estimate` | `timeEstimate` | `time_estimate` |
129
+ | `blocked_by` | `blockedBy` | `blocked_by` |
130
+
131
+ This table is compatibility guidance, not a requirement to choose camelCase or snake_case as canonical.
132
+
133
+ ## 2.6 Structured role schemas
134
+
135
+ ### 2.6.1 time_entries
136
+
137
+ `time_entries` items MUST be objects with:
138
+
139
+ - `startTime` datetime (required)
140
+ - `endTime` datetime (optional)
141
+ - `description` string (optional)
142
+
143
+ Additional constraints:
144
+
145
+ - a task MUST NOT contain more than one active time entry (entry with `startTime` and no `endTime`) at commit time.
146
+ - an entry with missing `endTime` is interpreted as an active/running session.
147
+ - an implementation MAY accept `duration` on read for backward compatibility, but canonical duration is derived from start/end and SHOULD NOT be persisted on canonical writes.
148
+
149
+ Nested key names in `time_entries` are fixed by this specification and are not independently configurable in `mapping`.
150
+
151
+ ### 2.6.2 projects
152
+
153
+ `projects` SHOULD be represented as links when link semantics are supported, but plain strings MAY be accepted.
154
+ When interpreted as links, parsing and resolution MUST follow §11.
155
+
156
+ ### 2.6.3 blocked_by
157
+
158
+ `blocked_by` items MUST be objects with:
159
+
160
+ - `uid` link-or-string task reference (required)
161
+ - `reltype` enum (required; default allowed by §10 when omitted)
162
+ - `gap` ISO 8601 duration string (optional)
163
+
164
+ Detailed semantics are defined in §10. `uid` parsing and resolution MUST follow §11.
165
+
166
+ ### 2.6.4 reminders
167
+
168
+ `reminders` items MUST be objects with:
169
+
170
+ - `id` string (required)
171
+ - `type` enum `absolute|relative` (required)
172
+ - relative-only fields: `relatedTo`, `offset`
173
+ - absolute-only fields: `absoluteTime`
174
+ - `description` string (optional)
175
+
176
+ Detailed semantics are defined in §10.
177
+
178
+ ### 2.6.5 id (stable task identity)
179
+
180
+ When semantic role `id` is present:
181
+
182
+ - value MUST be a non-empty string.
183
+ - value MUST be treated as stable identity metadata and MUST NOT be rewritten by rename, move, or title-change operations.
184
+ - readers and link resolution MAY use it as an identity lookup key (see §11.4 Step 3).
185
+ - implementations SHOULD keep `id` unique within a collection; duplicate IDs SHOULD produce a validation issue.
186
+
187
+ ### 2.6.6 Materialized occurrence roles
188
+
189
+ The roles in this subsection are required only for implementations claiming profile `materialized-occurrences` (§7.3.4).
190
+
191
+ Parent recurring task roles:
192
+
193
+ - `occurrence_materialization`: enum `manual|on_completion|rolling`; controls when occurrence notes are created (§4.18.5).
194
+ - `occurrence_next_trigger`: enum `completion|completion_or_skip`; controls whether skip/cancel transitions advance creation of the next occurrence note (§4.18.6).
195
+ - `occurrence_template`: link-or-string reference to a template file used when creating occurrence notes. Link parsing/resolution follows §11 when supported.
196
+ - `occurrence_past_horizon` and `occurrence_future_horizon`: ISO 8601 durations used by rolling materialization (§4.18.7).
197
+
198
+ Materialized occurrence note roles:
199
+
200
+ - `recurrence_parent`: link-or-string reference to the parent recurring task. Wikilinks are the default interoperable representation in Obsidian-oriented collections.
201
+ - `occurrence_date`: date value identifying the target date this occurrence note represents.
202
+
203
+ Implementations claiming `materialized-occurrences` MUST treat `recurrence_parent` and `occurrence_date` together as the occurrence identity key.
204
+ Within a resolved parent task, at most one materialized occurrence note SHOULD exist for each `occurrence_date`; duplicate notes MUST be reported when detected (§6.4).
205
+
206
+ Materialized occurrence notes MAY also use normal task roles such as `status`, `completed_date`, `scheduled`, `due`, `time_entries`, `reminders`, `contexts`, `projects`, and body content.
207
+ For dates where a materialized occurrence note exists, its own task state is authoritative as defined in §4.18.3.
208
+
209
+ ## 2.7 Unknown fields
210
+
211
+ Unknown frontmatter keys MUST be preserved by default during updates, complete/uncomplete, skip/unskip, dependency mutations, reminder mutations, and archive operations.
212
+
213
+ Unknown fields MAY be removed only when:
214
+
215
+ - explicit normalization/migration is requested, or
216
+ - strict schema replacement is explicitly requested.
217
+
218
+ ## 2.8 Example mapping
219
+
220
+ Example effective mapping:
221
+
222
+ ```yaml
223
+ mapping:
224
+ id: id
225
+ title: title
226
+ status: status
227
+ date_created: dateCreated
228
+ date_modified: dateModified
229
+ completed_date: completedDate
230
+ recurrence: recurrence
231
+ recurrence_anchor: recurrence_anchor
232
+ complete_instances: complete_instances
233
+ skipped_instances: skipped_instances
234
+ recurrence_parent: recurrence_parent
235
+ occurrence_date: occurrence_date
236
+ occurrence_materialization: occurrence_materialization
237
+ occurrence_next_trigger: occurrence_next_trigger
238
+ occurrence_template: occurrence_template
239
+ occurrence_past_horizon: occurrence_past_horizon
240
+ occurrence_future_horizon: occurrence_future_horizon
241
+ time_estimate: timeEstimate
242
+ time_entries: timeEntries
243
+ blocked_by: blockedBy
244
+ reminders: reminders
245
+ ```
246
+
247
+ ## 2.9 Example task record
248
+
249
+ ```markdown
250
+ ---
251
+ id: task-2026-01-10-weekly-review
252
+ title: Weekly review
253
+ status: open
254
+ priority: high
255
+ scheduled: 2026-02-20
256
+ recurrence: FREQ=WEEKLY;BYDAY=FR
257
+ recurrence_anchor: scheduled
258
+ complete_instances: [2026-02-13]
259
+ skipped_instances: []
260
+ blockedBy:
261
+ - uid: "[[prepare-metrics]]"
262
+ reltype: FINISHTOSTART
263
+ gap: P1D
264
+ reminders:
265
+ - id: rem_day_before
266
+ type: relative
267
+ relatedTo: due
268
+ offset: -P1D
269
+ - id: rem_start
270
+ type: absolute
271
+ absoluteTime: 2026-02-20T09:00:00Z
272
+ dateCreated: 2026-01-10T09:30:00Z
273
+ dateModified: 2026-02-20T08:02:11Z
274
+ ---
275
+
276
+ Review completed work and plan next week.
277
+ ```
278
+
279
+ ## 2.10 Deterministic load example
280
+
281
+ Given frontmatter:
282
+
283
+ ```yaml
284
+ recurrence_anchor: scheduled
285
+ recurrenceAnchor: completion
286
+ ```
287
+
288
+ If canonical key is `recurrence_anchor`, a conforming loader using canonical-precedence policy MUST resolve semantic `recurrence_anchor` as `scheduled` and SHOULD emit a compatibility warning.