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.
- package/00-overview.md +172 -0
- package/01-terminology.md +156 -0
- package/02-model-and-mapping.md +288 -0
- package/03-temporal-semantics.md +290 -0
- package/04-recurrence.md +398 -0
- package/05-operations.md +968 -0
- package/06-validation.md +267 -0
- package/07-conformance.md +292 -0
- package/08-compatibility-and-migrations.md +188 -0
- package/09-configuration.md +837 -0
- package/10-dependencies-and-reminders.md +266 -0
- package/11-links.md +373 -0
- package/CHANGELOG.md +25 -0
- package/README.md +80 -0
- package/conformance/README.md +31 -0
- package/conformance/adapters/mdbase-tasknotes.adapter.mjs +141 -0
- package/conformance/adapters/tasknotes-core/conformance.ts +2498 -0
- package/conformance/adapters/tasknotes-core/create-compat.ts +1 -0
- package/conformance/adapters/tasknotes-core/date.ts +12 -0
- package/conformance/adapters/tasknotes-core/field-mapping.ts +14 -0
- package/conformance/adapters/tasknotes-core/recurrence.ts +10 -0
- package/conformance/adapters/tasknotes-date-bridge.ts +20 -0
- package/conformance/adapters/tasknotes-runtime-bridge.ts +1107 -0
- package/conformance/adapters/tasknotes-runtime-obsidian-shim.ts +84 -0
- package/conformance/adapters/tasknotes-templating-bridge.ts +13 -0
- package/conformance/adapters/tasknotes.adapter.mjs +485 -0
- package/conformance/docs/ADAPTER_CONTRACT.md +245 -0
- package/conformance/docs/FIXTURE_FORMAT.md +247 -0
- package/conformance/docs/RUNNER_GUIDE.md +393 -0
- package/conformance/fixtures/config-schema.json +634 -0
- package/conformance/fixtures/config.json +18984 -0
- package/conformance/fixtures/conformance.json +444 -0
- package/conformance/fixtures/create-compat.json +18639 -0
- package/conformance/fixtures/date.json +25612 -0
- package/conformance/fixtures/dependencies.json +8647 -0
- package/conformance/fixtures/field-mapping.json +5406 -0
- package/conformance/fixtures/links.json +1127 -0
- package/conformance/fixtures/migrations.json +668 -0
- package/conformance/fixtures/operations.json +2761 -0
- package/conformance/fixtures/recurrence.json +22958 -0
- package/conformance/fixtures/reminders.json +13333 -0
- package/conformance/fixtures/templating.json +497 -0
- package/conformance/fixtures/validation.json +5308 -0
- package/conformance/lib/adapter-loader.mjs +23 -0
- package/conformance/lib/load-fixtures.mjs +43 -0
- package/conformance/lib/matchers.mjs +200 -0
- package/conformance/manifest.json +232 -0
- package/conformance/scripts/generate-fixtures.mjs +5239 -0
- package/conformance/scripts/package-fixtures.mjs +101 -0
- package/conformance/tests/coverage.test.mjs +213 -0
- package/conformance/tests/runner.test.mjs +102 -0
- package/conformance/tests/tasknotes-runtime-routing.test.mjs +64 -0
- 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.
|