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/05-operations.md
ADDED
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
# 5. Operations
|
|
2
|
+
|
|
3
|
+
## 5.1 Purpose
|
|
4
|
+
|
|
5
|
+
This section defines normative behavior for task mutations and read-facing resolution concerns that affect persisted state.
|
|
6
|
+
|
|
7
|
+
## 5.2 General operation rules
|
|
8
|
+
|
|
9
|
+
For all mutating operations:
|
|
10
|
+
|
|
11
|
+
1. Validation MUST run before commit in strict mode.
|
|
12
|
+
2. Writes MUST be atomic at file level (all-or-nothing per file).
|
|
13
|
+
3. `date_modified` MUST be updated on successful state change.
|
|
14
|
+
4. Unknown fields MUST be preserved unless explicit normalization is requested. This applies to every operation in this section: create, update, complete, uncomplete, skip, unskip, dependency mutations, reminder mutations, archive, and time-tracking operations.
|
|
15
|
+
5. Operations identified as idempotent MUST remain safe under repetition.
|
|
16
|
+
|
|
17
|
+
### 5.2.1 Target day/date resolution
|
|
18
|
+
|
|
19
|
+
Recurring instance operations act on a target day/date (`complete instance`, `uncomplete instance`, `skip`, `unskip`).
|
|
20
|
+
Non-recurring `complete` uses completion-day semantics for `completed_date`.
|
|
21
|
+
|
|
22
|
+
Resolution MUST be deterministic:
|
|
23
|
+
|
|
24
|
+
1. If caller provides an explicit target date/datetime, that value is authoritative.
|
|
25
|
+
2. If caller omits target for recurring instance operations, implementations MUST resolve in this order:
|
|
26
|
+
- task `scheduled`, if present and target-day-resolvable,
|
|
27
|
+
- else task `due`, if present and target-day-resolvable,
|
|
28
|
+
- else current local day in active runtime timezone (§3.6).
|
|
29
|
+
3. Target-day resolution for fallback task fields (`scheduled` / `due`) MUST use this extraction policy:
|
|
30
|
+
- if stored value is canonical date (`YYYY-MM-DD`), use it as-is;
|
|
31
|
+
- if stored value is datetime, use the literal date token before `T` (`YYYY-MM-DD`) without timezone shifting;
|
|
32
|
+
- if stored value is non-canonical but accepted by compatibility policy, implementations MAY normalize to a date token and SHOULD emit a warning;
|
|
33
|
+
- if a candidate value is unusable, continue to the next fallback source.
|
|
34
|
+
4. For non-recurring complete, if caller omits explicit completion-day input, implementations MUST use current local day in active runtime timezone.
|
|
35
|
+
|
|
36
|
+
When an explicit target is datetime:
|
|
37
|
+
|
|
38
|
+
- operations that require day semantics MUST use its calendar date part in active runtime timezone,
|
|
39
|
+
- for recurring `complete instance` with `recurrence_anchor=completion`, the same explicit datetime target MUST also drive `DTSTART` rewrite semantics in §4.4.3.
|
|
40
|
+
|
|
41
|
+
This explicit-target datetime rule is intentionally distinct from persisted-field fallback extraction above.
|
|
42
|
+
|
|
43
|
+
### 5.2.2 Idempotency and `date_modified`
|
|
44
|
+
|
|
45
|
+
For operations marked idempotent:
|
|
46
|
+
|
|
47
|
+
- first application MAY change state and `date_modified`,
|
|
48
|
+
- repeated application with semantically equivalent input MUST leave persisted state unchanged.
|
|
49
|
+
|
|
50
|
+
When a repeated idempotent operation is a no-op, `date_modified` MUST remain unchanged.
|
|
51
|
+
|
|
52
|
+
## 5.3 Create
|
|
53
|
+
|
|
54
|
+
### 5.3.1 Input requirements
|
|
55
|
+
|
|
56
|
+
Create MUST provide at minimum semantic roles required by §2, directly or via defaults.
|
|
57
|
+
|
|
58
|
+
### 5.3.2 Required behavior
|
|
59
|
+
|
|
60
|
+
Create MUST:
|
|
61
|
+
|
|
62
|
+
- apply default values,
|
|
63
|
+
- generate `date_created` and `date_modified` when absent,
|
|
64
|
+
- resolve semantic `title` and filename/path behavior according to §9.13,
|
|
65
|
+
- apply create-time templating when enabled and supported (§5.3.5, §9.14, §7),
|
|
66
|
+
- serialize canonical keys and canonical temporal formats,
|
|
67
|
+
- when `recurrence` is present, canonicalize `DTSTART` per §4.4.5,
|
|
68
|
+
- preserve caller-provided semantic `id` when present,
|
|
69
|
+
- fail with validation errors if required constraints are unmet,
|
|
70
|
+
- apply default reminders according to §10.3.9 when configured.
|
|
71
|
+
|
|
72
|
+
### 5.3.3 Title and filename resolution on create
|
|
73
|
+
|
|
74
|
+
Create MUST produce a deterministic initial filename/path.
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
|
|
78
|
+
- Generated filenames MUST use filename-safe sanitization and MUST avoid invalid/empty basenames.
|
|
79
|
+
- If `title.storage=filename`:
|
|
80
|
+
- basename MUST derive from semantic title,
|
|
81
|
+
- title changes after create are handled by update semantics (§5.4.4),
|
|
82
|
+
- implementations MAY also persist mapped `title` as a synchronized compatibility mirror,
|
|
83
|
+
- `title.filename_format` and `title.custom_filename_template` MUST be ignored.
|
|
84
|
+
- If `title.storage=frontmatter`, create-time basename MUST follow `title.filename_format`:
|
|
85
|
+
- `title`: sanitized semantic title,
|
|
86
|
+
- `zettel`: implementation-defined zettel pattern (MUST be documented),
|
|
87
|
+
- `timestamp`: implementation-defined timestamp pattern (MUST be documented),
|
|
88
|
+
- `custom`: `title.custom_filename_template` expansion.
|
|
89
|
+
- If generated path already exists, implementation MUST resolve collision deterministically (for example numeric suffixing).
|
|
90
|
+
|
|
91
|
+
### 5.3.4 Example
|
|
92
|
+
|
|
93
|
+
Input intent:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
title: "Pay electricity bill"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Assume defaults:
|
|
100
|
+
|
|
101
|
+
- status default `open`
|
|
102
|
+
- priority default `normal`
|
|
103
|
+
|
|
104
|
+
Persisted frontmatter (example):
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
title: Pay electricity bill
|
|
108
|
+
status: open
|
|
109
|
+
priority: normal
|
|
110
|
+
dateCreated: 2026-02-20T14:00:00Z
|
|
111
|
+
dateModified: 2026-02-20T14:00:00Z
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 5.3.5 Optional create-time templating
|
|
115
|
+
|
|
116
|
+
This subsection is required only for implementations claiming profile `templating` (§7.3.3).
|
|
117
|
+
|
|
118
|
+
Create-time templating pipeline MUST be deterministic:
|
|
119
|
+
|
|
120
|
+
1. Compute base create payload from explicit input, defaults (§9.8), and generated/system values (`date_created`, `date_modified`, resolved title policy).
|
|
121
|
+
2. Expand template variables using that base payload and active runtime date/time context.
|
|
122
|
+
3. Merge template frontmatter into base frontmatter with base payload precedence (base keys MUST win on conflict).
|
|
123
|
+
4. Resolve body output:
|
|
124
|
+
- if expanded template body is non-empty, use it;
|
|
125
|
+
- otherwise use caller-provided body/details (if any).
|
|
126
|
+
|
|
127
|
+
Template parse rules:
|
|
128
|
+
|
|
129
|
+
- If template content begins with `---`, implementations MUST treat the first `--- ... ---` block as template frontmatter and the remainder as template body.
|
|
130
|
+
- Frontmatter variable expansion MUST run before YAML parsing of template frontmatter.
|
|
131
|
+
|
|
132
|
+
Portable variable support:
|
|
133
|
+
|
|
134
|
+
Implementations claiming `templating` MUST support all variables in the **Required** column of the table below. Implementations MAY support any variable in the **Extended** column.
|
|
135
|
+
|
|
136
|
+
**Required task-data variables:**
|
|
137
|
+
|
|
138
|
+
| Variable | Value | Format / separator |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `{{title}}` | Semantic title | string; YAML-quoted if needed |
|
|
141
|
+
| `{{status}}` | Task status value | string |
|
|
142
|
+
| `{{priority}}` | Task priority value | string |
|
|
143
|
+
| `{{dueDate}}` | Due date if set | `YYYY-MM-DD` or empty |
|
|
144
|
+
| `{{scheduledDate}}` | Scheduled date if set | `YYYY-MM-DD` or empty |
|
|
145
|
+
| `{{details}}` | Caller-provided body/description | string |
|
|
146
|
+
| `{{contexts}}` | Contexts list | comma-separated, e.g. `"work, home"` |
|
|
147
|
+
| `{{tags}}` | Tags list | comma-separated, e.g. `"task, errands"` |
|
|
148
|
+
| `{{hashtags}}` | Tags list as hashtags | space-separated, e.g. `"#task #errands"` |
|
|
149
|
+
| `{{timeEstimate}}` | Time estimate in minutes | integer string or empty |
|
|
150
|
+
| `{{parentNote}}` | Parent note name/path | string; YAML-quoted if needed |
|
|
151
|
+
|
|
152
|
+
**Required date/time variables (expansion time):**
|
|
153
|
+
|
|
154
|
+
| Variable | Value | Format |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `{{date}}` | Current date | `yyyy-MM-dd` |
|
|
157
|
+
| `{{time}}` | Current time (24h) | `HH:mm` |
|
|
158
|
+
| `{{year}}` | Full year | `yyyy` |
|
|
159
|
+
| `{{month}}` | Month number | `MM` |
|
|
160
|
+
| `{{day}}` | Day of month | `dd` |
|
|
161
|
+
|
|
162
|
+
**Extended variables (optional):**
|
|
163
|
+
|
|
164
|
+
| Variable | Value | Format |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `{{dateTime}}` | Date + time | `yyyy-MM-dd-HHmm` |
|
|
167
|
+
| `{{timestamp}}` | Full timestamp | `yyyy-MM-dd-HHmmss` |
|
|
168
|
+
| `{{shortDate}}` | Short date | `yyMMdd` |
|
|
169
|
+
| `{{shortYear}}` | Two-digit year | `yy` |
|
|
170
|
+
| `{{monthName}}` | Month full name | e.g. `February` |
|
|
171
|
+
| `{{monthNameShort}}` | Month short name | e.g. `Feb` |
|
|
172
|
+
| `{{dayName}}` | Weekday full name | e.g. `Monday` |
|
|
173
|
+
| `{{dayNameShort}}` | Weekday short name | e.g. `Mon` |
|
|
174
|
+
| `{{week}}` | ISO week number | `ww` |
|
|
175
|
+
| `{{quarter}}` | Quarter number | `1`–`4` |
|
|
176
|
+
| `{{hour}}` | Hour (24h) | `HH` |
|
|
177
|
+
| `{{minute}}` | Minute | `mm` |
|
|
178
|
+
| `{{second}}` | Second | `ss` |
|
|
179
|
+
| `{{time12}}` | Time with AM/PM | `hh:mm a` |
|
|
180
|
+
| `{{time24}}` | Time (24h) | `HH:mm` |
|
|
181
|
+
| `{{timezone}}` | UTC offset long | e.g. `+05:30` |
|
|
182
|
+
| `{{utcOffset}}` | UTC offset long | e.g. `+05:30` |
|
|
183
|
+
| `{{unix}}` | Unix timestamp (seconds) | integer string |
|
|
184
|
+
| `{{unixMs}}` | Unix timestamp (ms) | integer string |
|
|
185
|
+
| `{{zettel}}` | Zettelkasten ID | date + seconds-since-midnight base-36 |
|
|
186
|
+
| `{{titleLower}}` | Title lowercase | string |
|
|
187
|
+
| `{{titleUpper}}` | Title uppercase | string |
|
|
188
|
+
| `{{titleSnake}}` | Title snake_case | string |
|
|
189
|
+
| `{{titleKebab}}` | Title kebab-case | string |
|
|
190
|
+
| `{{titleCamel}}` | Title camelCase | string |
|
|
191
|
+
| `{{titlePascal}}` | Title PascalCase | string |
|
|
192
|
+
| `{{priorityShort}}` | First letter of priority | uppercase char |
|
|
193
|
+
| `{{statusShort}}` | First letter of status | uppercase char |
|
|
194
|
+
|
|
195
|
+
Unknown variables (not in the above lists) MUST be handled per `templating.unknown_variable_policy` (§9.14):
|
|
196
|
+
- `preserve`: leave the `{{...}}` placeholder as-is.
|
|
197
|
+
- `empty`: replace with an empty string.
|
|
198
|
+
|
|
199
|
+
Deterministic expansion context:
|
|
200
|
+
|
|
201
|
+
- date/time variable expansion MUST use the active runtime timezone from §3.6.
|
|
202
|
+
- locale-sensitive variables (`monthName`, `monthNameShort`, `dayName`, `dayNameShort`, `time12`) MUST use English locale outputs for portability (`January`, `Jan`, `Monday`, `Mon`, `AM`/`PM`).
|
|
203
|
+
- template-generated datetime-like outputs used in frontmatter MUST normalize to second precision when written as canonical datetime roles (§3.3.2).
|
|
204
|
+
|
|
205
|
+
Failure behavior:
|
|
206
|
+
|
|
207
|
+
- `templating.failure_mode=error`: template read/parse failures MUST abort create.
|
|
208
|
+
- `templating.failure_mode=warning_fallback`: template read/parse failures MUST continue create using non-templated behavior (base frontmatter + caller body/details) and SHOULD emit warnings (`template_missing` or `template_parse_failed`).
|
|
209
|
+
|
|
210
|
+
### 5.3.6 Identity handling on create
|
|
211
|
+
|
|
212
|
+
If semantic `id` is provided on create, writers MUST preserve it.
|
|
213
|
+
If implementation supports auto-generation of semantic `id`, generation policy MUST be documented and resulting `id` MUST remain stable after create.
|
|
214
|
+
|
|
215
|
+
## 5.4 Update
|
|
216
|
+
|
|
217
|
+
### 5.4.1 Patch semantics
|
|
218
|
+
|
|
219
|
+
Update MUST be patch-by-default: only targeted semantic roles are changed.
|
|
220
|
+
|
|
221
|
+
### 5.4.2 Preservation rules
|
|
222
|
+
|
|
223
|
+
Update MUST preserve:
|
|
224
|
+
|
|
225
|
+
- unrelated known roles,
|
|
226
|
+
- unknown fields,
|
|
227
|
+
- original date/datetime granularity for untouched fields.
|
|
228
|
+
|
|
229
|
+
### 5.4.3 Example
|
|
230
|
+
|
|
231
|
+
Before:
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
title: Weekly review
|
|
235
|
+
scheduled: 2026-02-20
|
|
236
|
+
priority: normal
|
|
237
|
+
customClient: ACME
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Operation: set priority to `high`.
|
|
241
|
+
|
|
242
|
+
After:
|
|
243
|
+
|
|
244
|
+
```yaml
|
|
245
|
+
title: Weekly review
|
|
246
|
+
scheduled: 2026-02-20
|
|
247
|
+
priority: high
|
|
248
|
+
customClient: ACME
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 5.4.4 Title updates and filename updates
|
|
252
|
+
|
|
253
|
+
If an update changes semantic `title`, behavior MUST follow `title.storage`:
|
|
254
|
+
|
|
255
|
+
- `title.storage=filename`: implementation MUST rename file basename to match updated title (with sanitization and deterministic collision handling), preserving parent folder unless caller requested a move.
|
|
256
|
+
- `title.storage=frontmatter`: implementation MUST update mapped title field and MUST NOT rename file unless an explicit rename operation is requested.
|
|
257
|
+
|
|
258
|
+
If implementation keeps frontmatter title for compatibility while `title.storage=filename`, it MUST keep stored title synchronized with effective filename-derived title.
|
|
259
|
+
|
|
260
|
+
## 5.5 Complete (non-recurring)
|
|
261
|
+
|
|
262
|
+
For non-recurring tasks, complete MUST:
|
|
263
|
+
|
|
264
|
+
1. Set `status` to a configured completed value, unless already completed.
|
|
265
|
+
If multiple completed values are configured, implementation MUST choose deterministically (default: first entry in `status.completed_values`).
|
|
266
|
+
2. Set `completed_date` to completion day:
|
|
267
|
+
- explicit completion-day input when provided,
|
|
268
|
+
- otherwise current local day in active runtime timezone (§5.2.1).
|
|
269
|
+
3. Apply overwrite policy deterministically (`overwrite` or `preserve_if_present`).
|
|
270
|
+
4. Update `date_modified` on state change.
|
|
271
|
+
|
|
272
|
+
Implementations MUST document completion-day input handling and `completed_date` overwrite policy.
|
|
273
|
+
|
|
274
|
+
Operation MUST be idempotent.
|
|
275
|
+
|
|
276
|
+
## 5.6 Uncomplete (non-recurring)
|
|
277
|
+
|
|
278
|
+
For non-recurring tasks, uncomplete MUST:
|
|
279
|
+
|
|
280
|
+
1. Set `status` to configured active/default status according to policy (default: `status.default`).
|
|
281
|
+
2. Clear or retain `completed_date` according to policy.
|
|
282
|
+
3. Update `date_modified` on state change.
|
|
283
|
+
|
|
284
|
+
Policy MUST be documented and deterministic.
|
|
285
|
+
|
|
286
|
+
Operation MUST be idempotent.
|
|
287
|
+
|
|
288
|
+
## 5.7 Complete instance (recurring)
|
|
289
|
+
|
|
290
|
+
For recurring tasks, complete with resolved target date `D` (§5.2.1) MUST follow §4.7.
|
|
291
|
+
|
|
292
|
+
Writers MUST NOT convert recurring completion into a base status rewrite unless explicit configuration requires it.
|
|
293
|
+
When `recurrence_anchor=completion` and caller supplies an explicit datetime target, implementations MUST apply §4.4.3 datetime `DTSTART` rewrite behavior.
|
|
294
|
+
When `recurrence_anchor=scheduled` and `DTSTART` is absent, implementations MUST insert `DTSTART` deterministically per §4.4.5.
|
|
295
|
+
Next-occurrence recalculation in this mode MUST follow §4.4.4.
|
|
296
|
+
|
|
297
|
+
## 5.8 Uncomplete instance (recurring)
|
|
298
|
+
|
|
299
|
+
For recurring tasks, uncomplete with resolved target date `D` (§5.2.1) MUST follow §4.8.
|
|
300
|
+
For `recurrence_anchor=completion`, ordinary uncomplete is intentionally non-reversible and MUST NOT roll back `DTSTART`.
|
|
301
|
+
|
|
302
|
+
## 5.9 Skip and unskip instance
|
|
303
|
+
|
|
304
|
+
Skip/unskip for recurring tasks with resolved target date `D` (§5.2.1) MUST follow §4.9 and §4.10.
|
|
305
|
+
|
|
306
|
+
## 5.10 Dependency operations
|
|
307
|
+
|
|
308
|
+
Dependency operations MUST follow §10.2.
|
|
309
|
+
|
|
310
|
+
### 5.10.1 Add dependency
|
|
311
|
+
|
|
312
|
+
Add dependency MUST:
|
|
313
|
+
|
|
314
|
+
- validate dependency entry schema,
|
|
315
|
+
- parse and resolve `uid` per §11,
|
|
316
|
+
- enforce duplicate and self-reference rules,
|
|
317
|
+
- preserve existing non-target dependency entries,
|
|
318
|
+
- update `date_modified` on change.
|
|
319
|
+
|
|
320
|
+
### 5.10.2 Remove dependency
|
|
321
|
+
|
|
322
|
+
Remove dependency by `uid` MUST be idempotent.
|
|
323
|
+
|
|
324
|
+
### 5.10.3 Replace dependency list
|
|
325
|
+
|
|
326
|
+
Replace dependency list MUST be explicit (not default patch behavior).
|
|
327
|
+
|
|
328
|
+
## 5.11 Reminder operations
|
|
329
|
+
|
|
330
|
+
Reminder operations MUST follow §10.3.
|
|
331
|
+
|
|
332
|
+
### 5.11.1 Add reminder
|
|
333
|
+
|
|
334
|
+
Add reminder MUST:
|
|
335
|
+
|
|
336
|
+
- validate reminder schema,
|
|
337
|
+
- enforce unique `id` within task,
|
|
338
|
+
- update `date_modified` on change.
|
|
339
|
+
|
|
340
|
+
### 5.11.2 Update reminder
|
|
341
|
+
|
|
342
|
+
Update reminder MUST target a reminder by `id` and be patch-by-default.
|
|
343
|
+
|
|
344
|
+
### 5.11.3 Remove reminder
|
|
345
|
+
|
|
346
|
+
Remove reminder by `id` MUST be idempotent.
|
|
347
|
+
|
|
348
|
+
## 5.12 Archive
|
|
349
|
+
|
|
350
|
+
Implementations MAY support archive semantics through:
|
|
351
|
+
|
|
352
|
+
- archive tag,
|
|
353
|
+
- archive status,
|
|
354
|
+
- dedicated boolean/archive field.
|
|
355
|
+
|
|
356
|
+
Archive behavior MUST be documented.
|
|
357
|
+
|
|
358
|
+
Archive MUST NOT implicitly delete the task.
|
|
359
|
+
|
|
360
|
+
## 5.13 Delete
|
|
361
|
+
|
|
362
|
+
Delete MUST remove the task file.
|
|
363
|
+
|
|
364
|
+
Optional safety behavior:
|
|
365
|
+
|
|
366
|
+
- Implementations MAY perform backlink/dependency checks.
|
|
367
|
+
- If checks are enabled, implementation MUST provide a bypass option for explicit force delete.
|
|
368
|
+
|
|
369
|
+
## 5.14 Rename
|
|
370
|
+
|
|
371
|
+
Rename operation changes file path/filename while preserving semantic record identity.
|
|
372
|
+
|
|
373
|
+
### 5.14.1 Rename interaction with title storage mode
|
|
374
|
+
|
|
375
|
+
- If `title.storage=filename`, basename changes from rename MUST update effective semantic title.
|
|
376
|
+
- If `title.storage=frontmatter`, rename MUST NOT implicitly rewrite mapped `title` unless explicitly requested.
|
|
377
|
+
- Implementations that persist frontmatter title in `title.storage=filename` mode MUST either update that field on rename or remove it to prevent divergence.
|
|
378
|
+
- If semantic `id` is present, rename/move MUST preserve its value unchanged.
|
|
379
|
+
|
|
380
|
+
If implementation supports link/reference updating, it MUST:
|
|
381
|
+
|
|
382
|
+
- update resolvable references deterministically,
|
|
383
|
+
- report unresolved or ambiguous updates,
|
|
384
|
+
- update dependency `uid` and `projects` references consistently with normal links.
|
|
385
|
+
|
|
386
|
+
If implementation does not support reference updates, this limitation MUST be disclosed in conformance claim.
|
|
387
|
+
|
|
388
|
+
## 5.15 Batch operations
|
|
389
|
+
|
|
390
|
+
Batch operations MAY be supported.
|
|
391
|
+
|
|
392
|
+
If supported, implementation MUST report per-item outcomes and summary counts:
|
|
393
|
+
|
|
394
|
+
- total
|
|
395
|
+
- succeeded
|
|
396
|
+
- failed
|
|
397
|
+
|
|
398
|
+
Partial success behavior MUST be documented.
|
|
399
|
+
|
|
400
|
+
## 5.16 Concurrency
|
|
401
|
+
|
|
402
|
+
Implementations SHOULD provide write-conflict detection (for example based on modified timestamp or file hash).
|
|
403
|
+
|
|
404
|
+
When conflict is detected, operation MUST fail safely unless explicit overwrite is requested.
|
|
405
|
+
|
|
406
|
+
## 5.17 Dry run
|
|
407
|
+
|
|
408
|
+
If dry run mode is supported, operation MUST:
|
|
409
|
+
|
|
410
|
+
- execute validation and transformation logic,
|
|
411
|
+
- report intended changes,
|
|
412
|
+
- perform no file write.
|
|
413
|
+
|
|
414
|
+
## 5.18 Error model
|
|
415
|
+
|
|
416
|
+
Implementation-native operation failures MUST provide structured error information with:
|
|
417
|
+
|
|
418
|
+
- operation name,
|
|
419
|
+
- error code,
|
|
420
|
+
- message,
|
|
421
|
+
- optional field/path context.
|
|
422
|
+
|
|
423
|
+
For conformance adapter envelopes (§5.22), failures MAY be represented as a compact `error` string for transport compatibility, but implementations SHOULD also expose the structured fields (for example via `error_details`).
|
|
424
|
+
|
|
425
|
+
## 5.19 Time tracking management
|
|
426
|
+
|
|
427
|
+
This subsection applies to implementations that support `time_entries`.
|
|
428
|
+
|
|
429
|
+
### 5.19.1 Start time tracking
|
|
430
|
+
|
|
431
|
+
Start on task `T` MUST:
|
|
432
|
+
|
|
433
|
+
- fail when `T` already has an active time entry,
|
|
434
|
+
- append a new entry with canonical `startTime` and no `endTime`,
|
|
435
|
+
- MAY set a default `description`,
|
|
436
|
+
- update `date_modified`,
|
|
437
|
+
- persist canonical `time_entries` shape (including removal of legacy/stale `duration` fields when normalizing).
|
|
438
|
+
|
|
439
|
+
Recommended error code when already active: `time_tracking_already_active`.
|
|
440
|
+
|
|
441
|
+
### 5.19.2 Stop time tracking
|
|
442
|
+
|
|
443
|
+
Stop on task `T` MUST:
|
|
444
|
+
|
|
445
|
+
- fail when `T` has no active time entry,
|
|
446
|
+
- set `endTime` on the active entry to canonical current datetime,
|
|
447
|
+
- update `date_modified`,
|
|
448
|
+
- preserve non-target entries and unknown frontmatter fields.
|
|
449
|
+
|
|
450
|
+
Recommended error code when no active session exists: `no_active_time_entry`.
|
|
451
|
+
|
|
452
|
+
### 5.19.3 Edit or replace time entries
|
|
453
|
+
|
|
454
|
+
When replacing/editing `time_entries` explicitly, implementations MUST:
|
|
455
|
+
|
|
456
|
+
- validate each entry per §2.6.1 and §3.11,
|
|
457
|
+
- persist canonical datetime formats,
|
|
458
|
+
- treat `duration` as derived compatibility input and SHOULD remove it from canonical writes,
|
|
459
|
+
- update `date_modified` when persisted state changes.
|
|
460
|
+
|
|
461
|
+
### 5.19.4 Remove a time entry
|
|
462
|
+
|
|
463
|
+
Remove MUST target one deterministic entry (for example by index or stable selector) and update `date_modified` on change.
|
|
464
|
+
Selector semantics MUST be documented.
|
|
465
|
+
|
|
466
|
+
### 5.19.5 Completion-triggered auto-stop
|
|
467
|
+
|
|
468
|
+
If `time_tracking.auto_stop_on_complete=true` (§9.16), completion transitions MUST trigger stop behavior for the same task only.
|
|
469
|
+
|
|
470
|
+
Rules:
|
|
471
|
+
|
|
472
|
+
- non-recurring tasks: trigger when status transitions from non-completed to completed status.
|
|
473
|
+
- recurring tasks: trigger when `complete_instances` grows.
|
|
474
|
+
- materialized occurrence notes: trigger on the occurrence note's own status transition to completed; parent reconciliation SHOULD NOT trigger a second stop on the parent unless the parent itself has an active time entry and the implementation documents that policy.
|
|
475
|
+
- implementations MUST NOT stop active sessions on unrelated tasks.
|
|
476
|
+
- if `time_tracking.auto_stop_notification=true`, implementations MAY emit a user-visible notice.
|
|
477
|
+
|
|
478
|
+
### 5.19.6 Reporting tracked time
|
|
479
|
+
|
|
480
|
+
For reporting surfaces (for example task summaries or stats), implementations MUST document whether totals use `closed_minutes` or `live_minutes` semantics from §3.11.5.
|
|
481
|
+
|
|
482
|
+
## 5.20 Recurrence materialization operations
|
|
483
|
+
|
|
484
|
+
This subsection is required only for implementations claiming profile `materialized-occurrences` (§7.3.4).
|
|
485
|
+
|
|
486
|
+
### 5.20.1 Materialize occurrence
|
|
487
|
+
|
|
488
|
+
Operation: `materialize occurrence` for parent recurring task `P` and target date `D`.
|
|
489
|
+
|
|
490
|
+
Conforming behavior:
|
|
491
|
+
|
|
492
|
+
1. Resolve `P` and confirm it is recurring.
|
|
493
|
+
2. If an occurrence note for `(P, D)` already exists, return that note without creating a duplicate.
|
|
494
|
+
3. Create a task note using `occurrence_template` when configured, otherwise using normal create behavior.
|
|
495
|
+
4. Persist `recurrence_parent` as a link to `P` and `occurrence_date` as `D`.
|
|
496
|
+
5. Set `scheduled` to `D` unless caller input or template output explicitly supplies another per-occurrence scheduled value.
|
|
497
|
+
6. Set initial `status` using normal create defaults unless caller input or template output explicitly supplies a status.
|
|
498
|
+
7. Preserve template/body output as occurrence-owned content after creation.
|
|
499
|
+
8. Update `date_created` and `date_modified` according to create semantics.
|
|
500
|
+
|
|
501
|
+
The operation MUST be idempotent for `(P, D)`.
|
|
502
|
+
|
|
503
|
+
If `D` is not generated by the current recurrence rule, implementations MAY allow materialization as an explicit user action but SHOULD report `materialization_target_not_generated`.
|
|
504
|
+
|
|
505
|
+
### 5.20.2 Complete materialized occurrence
|
|
506
|
+
|
|
507
|
+
Completing a materialized occurrence note MUST:
|
|
508
|
+
|
|
509
|
+
1. Complete the occurrence note using normal non-recurring completion semantics (§5.5).
|
|
510
|
+
2. Reconcile the parent by adding `D` to `complete_instances` and removing `D` from `skipped_instances`.
|
|
511
|
+
3. If the parent has `recurrence_anchor=completion`, advance parent progression according to §4.4.3 and §4.4.4.
|
|
512
|
+
4. If `occurrence_materialization=on_completion`, compute the next occurrence from the parent after the completion transition and materialize that next occurrence note idempotently.
|
|
513
|
+
|
|
514
|
+
The occurrence note's successful completion MUST NOT be rolled back solely because next-occurrence materialization fails.
|
|
515
|
+
When the next note cannot be created, implementations SHOULD report `next_occurrence_materialization_failed`.
|
|
516
|
+
|
|
517
|
+
### 5.20.3 Skip materialized occurrence
|
|
518
|
+
|
|
519
|
+
Skipping a materialized occurrence note MUST preserve the note.
|
|
520
|
+
Skip is a state transition, not deletion.
|
|
521
|
+
|
|
522
|
+
Conforming behavior:
|
|
523
|
+
|
|
524
|
+
1. Set the occurrence note to the configured skipped status (`status.default_skipped`, or the first `status.skipped_values` entry when `default_skipped` is absent).
|
|
525
|
+
2. Clear or retain `completed_date` according to the same documented policy used for uncompletion unless the implementation documents a skip-specific policy.
|
|
526
|
+
3. Reconcile the parent by adding `D` to `skipped_instances` and removing `D` from `complete_instances`.
|
|
527
|
+
4. If `occurrence_materialization=on_completion` and `occurrence_next_trigger=completion_or_skip`, compute and materialize the next occurrence note idempotently.
|
|
528
|
+
|
|
529
|
+
If no skipped status is configured, skip of a materialized occurrence MUST fail deterministically in strict mode and SHOULD report `missing_skipped_status`.
|
|
530
|
+
|
|
531
|
+
### 5.20.4 Uncomplete and unskip materialized occurrence
|
|
532
|
+
|
|
533
|
+
Uncomplete of a materialized occurrence note MUST:
|
|
534
|
+
|
|
535
|
+
1. Reopen the occurrence note using normal uncomplete semantics (§5.6).
|
|
536
|
+
2. Remove `D` from parent `complete_instances`.
|
|
537
|
+
3. For `recurrence_anchor=completion`, MUST NOT roll back parent `DTSTART`, matching §4.8.
|
|
538
|
+
4. MUST NOT delete any later materialized occurrence note.
|
|
539
|
+
|
|
540
|
+
Unskip of a materialized occurrence note MUST:
|
|
541
|
+
|
|
542
|
+
1. Reopen the occurrence note to configured active/default status.
|
|
543
|
+
2. Remove `D` from parent `skipped_instances`.
|
|
544
|
+
3. MUST NOT add `D` to parent `complete_instances` implicitly.
|
|
545
|
+
4. MUST NOT delete any later materialized occurrence note.
|
|
546
|
+
|
|
547
|
+
### 5.20.5 Delete and unmaterialize occurrence
|
|
548
|
+
|
|
549
|
+
Deleting a materialized occurrence note removes the task file and MUST NOT be interpreted as skip.
|
|
550
|
+
|
|
551
|
+
Implementations MAY provide an explicit `unmaterialize occurrence` operation.
|
|
552
|
+
If supported, it MUST document whether parent state is preserved, cleared, or reconciled.
|
|
553
|
+
In all cases, deleting or unmaterializing an occurrence MUST NOT silently delete the parent recurring task.
|
|
554
|
+
|
|
555
|
+
If parent instance lists still refer to a deleted occurrence note's date, validators MAY report an orphan/cache warning such as `orphan_occurrence_note`.
|
|
556
|
+
|
|
557
|
+
## 5.21 Operation examples
|
|
558
|
+
|
|
559
|
+
### 5.21.1 Non-recurring complete
|
|
560
|
+
|
|
561
|
+
Before:
|
|
562
|
+
|
|
563
|
+
```yaml
|
|
564
|
+
title: Buy groceries
|
|
565
|
+
status: open
|
|
566
|
+
completedDate:
|
|
567
|
+
dateModified: 2026-02-20T09:00:00Z
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
After complete on `2026-02-20`:
|
|
571
|
+
|
|
572
|
+
```yaml
|
|
573
|
+
title: Buy groceries
|
|
574
|
+
status: done
|
|
575
|
+
completedDate: 2026-02-20
|
|
576
|
+
dateModified: 2026-02-20T09:05:00Z
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 5.21.2 Recurring complete instance
|
|
580
|
+
|
|
581
|
+
Before:
|
|
582
|
+
|
|
583
|
+
```yaml
|
|
584
|
+
title: Weekly review
|
|
585
|
+
status: open
|
|
586
|
+
scheduled: 2026-02-20
|
|
587
|
+
recurrence: FREQ=WEEKLY;BYDAY=FR
|
|
588
|
+
completeInstances: []
|
|
589
|
+
skippedInstances: []
|
|
590
|
+
dateModified: 2026-02-20T08:00:00Z
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
After complete instance on `2026-02-20`:
|
|
594
|
+
|
|
595
|
+
```yaml
|
|
596
|
+
title: Weekly review
|
|
597
|
+
status: open
|
|
598
|
+
scheduled: 2026-02-20
|
|
599
|
+
recurrence: DTSTART:20260220;FREQ=WEEKLY;BYDAY=FR
|
|
600
|
+
completeInstances: [2026-02-20]
|
|
601
|
+
skippedInstances: []
|
|
602
|
+
dateModified: 2026-02-20T08:10:00Z
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### 5.21.3 Recurring skip instance overriding completion
|
|
606
|
+
|
|
607
|
+
Before:
|
|
608
|
+
|
|
609
|
+
```yaml
|
|
610
|
+
completeInstances: [2026-02-20]
|
|
611
|
+
skippedInstances: []
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
After skip `2026-02-20`:
|
|
615
|
+
|
|
616
|
+
```yaml
|
|
617
|
+
completeInstances: []
|
|
618
|
+
skippedInstances: [2026-02-20]
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### 5.21.4 Preserve unknown fields on update
|
|
622
|
+
|
|
623
|
+
Before:
|
|
624
|
+
|
|
625
|
+
```yaml
|
|
626
|
+
title: Plan Q2
|
|
627
|
+
status: open
|
|
628
|
+
vendorTicket: ZX-42
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Update status to `in-progress`.
|
|
632
|
+
|
|
633
|
+
After:
|
|
634
|
+
|
|
635
|
+
```yaml
|
|
636
|
+
title: Plan Q2
|
|
637
|
+
status: in-progress
|
|
638
|
+
vendorTicket: ZX-42
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### 5.21.5 Add dependency
|
|
642
|
+
|
|
643
|
+
Before:
|
|
644
|
+
|
|
645
|
+
```yaml
|
|
646
|
+
blockedBy: []
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Add dependency:
|
|
650
|
+
|
|
651
|
+
```yaml
|
|
652
|
+
uid: "[[prepare-metrics]]"
|
|
653
|
+
reltype: FINISHTOSTART
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
After:
|
|
657
|
+
|
|
658
|
+
```yaml
|
|
659
|
+
blockedBy:
|
|
660
|
+
- uid: "[[prepare-metrics]]"
|
|
661
|
+
reltype: FINISHTOSTART
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### 5.21.6 Add reminder
|
|
665
|
+
|
|
666
|
+
Before:
|
|
667
|
+
|
|
668
|
+
```yaml
|
|
669
|
+
reminders: []
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Add reminder:
|
|
673
|
+
|
|
674
|
+
```yaml
|
|
675
|
+
id: due_minus_1h
|
|
676
|
+
type: relative
|
|
677
|
+
relatedTo: due
|
|
678
|
+
offset: -PT1H
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
After:
|
|
682
|
+
|
|
683
|
+
```yaml
|
|
684
|
+
reminders:
|
|
685
|
+
- id: due_minus_1h
|
|
686
|
+
type: relative
|
|
687
|
+
relatedTo: due
|
|
688
|
+
offset: -PT1H
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
## 5.22 Conformance operation interface
|
|
692
|
+
|
|
693
|
+
The conformance suite exercises operations via the `execute(operation, input) → envelope` interface defined in the adapter contract. This section specifies the input/output contracts for each conformance-testable operation name.
|
|
694
|
+
|
|
695
|
+
Unless otherwise noted, all operations return `{ ok: true, result: {...} }` on success and `{ ok: false, error: "..." }` on failure. Adapters MAY additionally return `error_details` with structured fields from §5.18.
|
|
696
|
+
|
|
697
|
+
### General operations
|
|
698
|
+
|
|
699
|
+
#### `op.mutate_with_validation`
|
|
700
|
+
|
|
701
|
+
Tests §5.2 rule 1: validation runs before commit.
|
|
702
|
+
|
|
703
|
+
Input:
|
|
704
|
+
```json
|
|
705
|
+
{ "strict": true, "frontmatter": { ... } }
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Behavior: validate `frontmatter` as a task record. In strict mode (`strict=true`), any validation error MUST produce `ok=false`.
|
|
709
|
+
|
|
710
|
+
Output on validation error:
|
|
711
|
+
```json
|
|
712
|
+
{ "ok": false, "error": "validation|invalid_type|..." }
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
#### `op.atomic_write`
|
|
718
|
+
|
|
719
|
+
Tests §5.2 rule 2: writes are all-or-nothing.
|
|
720
|
+
|
|
721
|
+
Input:
|
|
722
|
+
```json
|
|
723
|
+
{
|
|
724
|
+
"original": { ... },
|
|
725
|
+
"patch": { ... },
|
|
726
|
+
"simulateFailureAfterWrite": true
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
Behavior: apply `patch` to `original`, then simulate a post-write failure. The adapter MUST report whether the write was committed and what state the record is in after recovery.
|
|
731
|
+
|
|
732
|
+
Output:
|
|
733
|
+
```json
|
|
734
|
+
{
|
|
735
|
+
"ok": true,
|
|
736
|
+
"result": {
|
|
737
|
+
"committed": false,
|
|
738
|
+
"persisted": { ... }
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
`committed=false` means the write was rolled back. `persisted` reflects the actual frontmatter after recovery — it MUST match `original` (not the patched version) when `simulateFailureAfterWrite=true`.
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
#### `op.idempotency_check`
|
|
748
|
+
|
|
749
|
+
Tests §5.2 rule 5: operations are safe under repetition.
|
|
750
|
+
|
|
751
|
+
Input:
|
|
752
|
+
```json
|
|
753
|
+
{
|
|
754
|
+
"operation": "complete_nonrecurring",
|
|
755
|
+
"first": { "status": "open", "completedDate": null },
|
|
756
|
+
"second": { "status": "done", "completedDate": "2026-02-20" }
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
Behavior: applying the named operation to `first` state and then again to `second` state MUST produce identical persisted results (including unchanged `dateModified` on the second no-op application). The adapter checks whether this holds.
|
|
761
|
+
|
|
762
|
+
Output:
|
|
763
|
+
```json
|
|
764
|
+
{ "ok": true, "result": { "idempotent": true } }
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
#### `op.update_patch`
|
|
770
|
+
|
|
771
|
+
Tests §5.4 patch semantics and unknown-field preservation.
|
|
772
|
+
|
|
773
|
+
Input:
|
|
774
|
+
```json
|
|
775
|
+
{
|
|
776
|
+
"original": { "title": "...", "vendorTicket": "ZX-42", ... },
|
|
777
|
+
"patch": { "priority": "high" },
|
|
778
|
+
"changed": true
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Behavior: apply `patch` to `original`. Return the merged frontmatter.
|
|
783
|
+
|
|
784
|
+
Output:
|
|
785
|
+
```json
|
|
786
|
+
{
|
|
787
|
+
"ok": true,
|
|
788
|
+
"result": {
|
|
789
|
+
"changed": true,
|
|
790
|
+
"frontmatter": { "title": "...", "priority": "high", "vendorTicket": "ZX-42", ... }
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
- `changed` indicates whether any field actually changed value.
|
|
796
|
+
- `frontmatter` MUST include all unknown fields from `original` unchanged.
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
#### `op.complete_nonrecurring`
|
|
801
|
+
|
|
802
|
+
Tests §5.5 non-recurring completion.
|
|
803
|
+
|
|
804
|
+
Input:
|
|
805
|
+
```json
|
|
806
|
+
{
|
|
807
|
+
"frontmatter": { "title": "...", "status": "open" },
|
|
808
|
+
"completedValues": ["done", "cancelled"],
|
|
809
|
+
"explicitDate": "2026-02-20"
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
Behavior: apply a complete operation to `frontmatter` using `completedValues` (ordered list) and `explicitDate` as the completion day (omit to use current local day).
|
|
814
|
+
|
|
815
|
+
Output:
|
|
816
|
+
```json
|
|
817
|
+
{ "ok": true, "result": { "status": "done", "completedDate": "2026-02-20" } }
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
- `status` MUST be the first entry in `completedValues`.
|
|
821
|
+
- `completedDate` MUST be `explicitDate` if provided; otherwise a valid `YYYY-MM-DD` date.
|
|
822
|
+
- If `frontmatter.status` is already a completed value, the operation MUST still succeed (idempotent).
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
#### `op.uncomplete_nonrecurring`
|
|
827
|
+
|
|
828
|
+
Tests §5.6 non-recurring uncompletion.
|
|
829
|
+
|
|
830
|
+
Input:
|
|
831
|
+
```json
|
|
832
|
+
{
|
|
833
|
+
"frontmatter": { "title": "...", "status": "done", "completedDate": "2026-02-20" },
|
|
834
|
+
"defaultStatus": "open",
|
|
835
|
+
"clearCompletedDate": true
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Output:
|
|
840
|
+
```json
|
|
841
|
+
{ "ok": true, "result": { "status": "open", "completedDate": null } }
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
- `status` MUST be `defaultStatus`.
|
|
845
|
+
- If `clearCompletedDate=true`, `completedDate` MUST be `null` in the result.
|
|
846
|
+
- If `clearCompletedDate=false`, `completedDate` MUST be preserved.
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
#### `op.error_shape`
|
|
851
|
+
|
|
852
|
+
Tests §5.18 error model.
|
|
853
|
+
|
|
854
|
+
Input:
|
|
855
|
+
```json
|
|
856
|
+
{ "operation": "update", "code": "invalid_type", "message": "bad value", "field": "status" }
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
Behavior: construct and return a structured error object.
|
|
860
|
+
|
|
861
|
+
Output:
|
|
862
|
+
```json
|
|
863
|
+
{
|
|
864
|
+
"ok": true,
|
|
865
|
+
"result": {
|
|
866
|
+
"operation": "update",
|
|
867
|
+
"code": "invalid_type",
|
|
868
|
+
"message": "..."
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
The `message` field MUST be a non-empty string. The `operation` and `code` fields MUST match the input.
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
#### `op.detect_conflict`
|
|
878
|
+
|
|
879
|
+
Tests §5.16 write-conflict detection.
|
|
880
|
+
|
|
881
|
+
Input:
|
|
882
|
+
```json
|
|
883
|
+
{ "expectedVersion": "a", "actualVersion": "b", "overwrite": false }
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Behavior: when `expectedVersion != actualVersion` and `overwrite=false`, the operation MUST fail.
|
|
887
|
+
|
|
888
|
+
Output on conflict:
|
|
889
|
+
```json
|
|
890
|
+
{ "ok": false, "error": "conflict|write_conflict|..." }
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
---
|
|
894
|
+
|
|
895
|
+
#### `op.dry_run`
|
|
896
|
+
|
|
897
|
+
Tests §5.17 dry-run mode.
|
|
898
|
+
|
|
899
|
+
Input:
|
|
900
|
+
```json
|
|
901
|
+
{ "operation": "update", "patch": { "status": "done" } }
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
Output:
|
|
905
|
+
```json
|
|
906
|
+
{
|
|
907
|
+
"ok": true,
|
|
908
|
+
"result": {
|
|
909
|
+
"wrote": false,
|
|
910
|
+
"plannedChanges": ["status", ...]
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
`wrote` MUST be `false`. `plannedChanges` MUST list the field names that would have changed.
|
|
916
|
+
|
|
917
|
+
---
|
|
918
|
+
|
|
919
|
+
### Recurrence operations
|
|
920
|
+
|
|
921
|
+
For `recurrence.complete`, `recurrence.uncomplete_instance`, `recurrence.skip_instance`, `recurrence.unskip_instance`, and `recurrence.effective_state`, the input and output contracts are defined by the respective sections of §4 and §5.7–5.9.
|
|
922
|
+
|
|
923
|
+
Common input fields for recurrence operations:
|
|
924
|
+
- `targetDate` or `completionDate`: `YYYY-MM-DD` target day
|
|
925
|
+
- `completeInstances`: current list of completed dates
|
|
926
|
+
- `skippedInstances`: current list of skipped dates
|
|
927
|
+
|
|
928
|
+
`recurrence.effective_state` output:
|
|
929
|
+
```json
|
|
930
|
+
{ "ok": true, "result": { "value": "completed|skipped|open" } }
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
---
|
|
934
|
+
|
|
935
|
+
### Materialized occurrence operations
|
|
936
|
+
|
|
937
|
+
For `occurrence.materialize`, `occurrence.complete`, `occurrence.skip`, `occurrence.uncomplete`, `occurrence.unskip`, and `occurrence.unmaterialize`, input and output contracts are defined by §4.18 and §5.20.
|
|
938
|
+
|
|
939
|
+
Common input fields for materialized occurrence operations:
|
|
940
|
+
- `parent`: frontmatter/path/link object for the recurring parent task
|
|
941
|
+
- `occurrence`: frontmatter/path/link object for an existing occurrence note when applicable
|
|
942
|
+
- `targetDate`: `YYYY-MM-DD` target day
|
|
943
|
+
- `config`: effective occurrence/status configuration needed by the operation
|
|
944
|
+
|
|
945
|
+
`occurrence.materialize` output:
|
|
946
|
+
```json
|
|
947
|
+
{ "ok": true, "result": { "created": true, "occurrence": { "...": "..." } } }
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
`created` MUST be `false` when the operation returns an already-existing occurrence note for the same parent/date.
|
|
951
|
+
|
|
952
|
+
---
|
|
953
|
+
|
|
954
|
+
### Time-tracking operations
|
|
955
|
+
|
|
956
|
+
For `time.start`, `time.stop`, `time.replace_entries`, `time.remove_entry`, `time.auto_stop_on_complete`, and `time.report_totals`, the contracts are defined by §5.19.
|
|
957
|
+
|
|
958
|
+
Common input patterns:
|
|
959
|
+
- `entries`: current `time_entries` array
|
|
960
|
+
- `now`: ISO 8601 datetime representing current instant
|
|
961
|
+
- `dateModified`: current `date_modified` value
|
|
962
|
+
|
|
963
|
+
`time.report_totals` output:
|
|
964
|
+
```json
|
|
965
|
+
{ "ok": true, "result": { "closed_minutes": N, "live_minutes": M } }
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
`live_minutes` is only present when an active (no `endTime`) entry exists.
|