5. Operations
5.1 Purpose
This section defines normative behavior for task mutations and read-facing resolution concerns that affect persisted state.
5.2 General operation rules
For all mutating operations:
- Validation MUST run before commit in strict mode.
- Writes MUST be atomic at file level (all-or-nothing per file).
date_modifiedMUST be updated on successful state change.- 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.
- Operations identified as idempotent MUST remain safe under repetition.
5.2.1 Target day/date resolution
Recurring instance operations act on a target day/date (complete instance, uncomplete instance, skip, unskip).
Non-recurring complete uses completion-day semantics for completed_date.
Resolution MUST be deterministic:
- If caller provides an explicit target date/datetime, that value is authoritative.
- If caller omits target for recurring instance operations, implementations MUST resolve in this order:
- task
scheduled, if present and target-day-resolvable, - else task
due, if present and target-day-resolvable, - else current local day in active runtime timezone (§3.6).
- task
- Target-day resolution for fallback task fields (
scheduled/due) MUST use this extraction policy:- if stored value is canonical date (
YYYY-MM-DD), use it as-is; - if stored value is datetime, use the literal date token before
T(YYYY-MM-DD) without timezone shifting; - if stored value is non-canonical but accepted by compatibility policy, implementations MAY normalize to a date token and SHOULD emit a warning;
- if a candidate value is unusable, continue to the next fallback source.
- if stored value is canonical date (
- For non-recurring complete, if caller omits explicit completion-day input, implementations MUST use current local day in active runtime timezone.
When an explicit target is datetime:
- operations that require day semantics MUST use its calendar date part in active runtime timezone,
- for recurring
complete instancewithrecurrence_anchor=completion, the same explicit datetime target MUST also driveDTSTARTrewrite semantics in §4.4.3.
This explicit-target datetime rule is intentionally distinct from persisted-field fallback extraction above.
5.2.2 Idempotency and date_modified
For operations marked idempotent:
- first application MAY change state and
date_modified, - repeated application with semantically equivalent input MUST leave persisted state unchanged.
When a repeated idempotent operation is a no-op, date_modified MUST remain unchanged.
5.3 Create
5.3.1 Input requirements
Create MUST provide at minimum semantic roles required by §2, directly or via defaults.
5.3.2 Required behavior
Create MUST:
- apply default values,
- generate
date_createdanddate_modifiedwhen absent, - resolve semantic
titleand filename/path behavior according to §9.13, - apply create-time templating when enabled and supported (§5.3.5, §9.14, §7),
- serialize canonical keys and canonical temporal formats,
- when
recurrenceis present, canonicalizeDTSTARTper §4.4.5, - preserve caller-provided semantic
idwhen present, - fail with validation errors if required constraints are unmet,
- apply default reminders according to §10.3.9 when configured.
5.3.3 Title and filename resolution on create
Create MUST produce a deterministic initial filename/path.
Rules:
- Generated filenames MUST use filename-safe sanitization and MUST avoid invalid/empty basenames.
- If
title.storage=filename:- basename MUST derive from semantic title,
- title changes after create are handled by update semantics (§5.4.4),
- implementations MAY also persist mapped
titleas a synchronized compatibility mirror, title.filename_formatandtitle.custom_filename_templateMUST be ignored.
- If
title.storage=frontmatter, create-time basename MUST followtitle.filename_format:title: sanitized semantic title,zettel: implementation-defined zettel pattern (MUST be documented),timestamp: implementation-defined timestamp pattern (MUST be documented),custom:title.custom_filename_templateexpansion.
- If generated path already exists, implementation MUST resolve collision deterministically (for example numeric suffixing).
5.3.4 Example
Input intent:
title: "Pay electricity bill"
Assume defaults:
- status default
open - priority default
normal
Persisted frontmatter (example):
title: Pay electricity bill
status: open
priority: normal
dateCreated: 2026-02-20T14:00:00Z
dateModified: 2026-02-20T14:00:00Z
5.3.5 Optional create-time templating
This subsection is required only for implementations claiming profile templating (§7.3.3).
Create-time templating pipeline MUST be deterministic:
- Compute base create payload from explicit input, defaults (§9.8), and generated/system values (
date_created,date_modified, resolved title policy). - Expand template variables using that base payload and active runtime date/time context.
- Merge template frontmatter into base frontmatter with base payload precedence (base keys MUST win on conflict).
- Resolve body output:
- if expanded template body is non-empty, use it;
- otherwise use caller-provided body/details (if any).
Template parse rules:
- If template content begins with
---, implementations MUST treat the first--- ... ---block as template frontmatter and the remainder as template body. - Frontmatter variable expansion MUST run before YAML parsing of template frontmatter.
Portable variable support:
Implementations claiming templating MUST support all variables in the Required column of the table below. Implementations MAY support any variable in the Extended column.
Required task-data variables:
| Variable | Value | Format / separator |
|---|---|---|
{{title}} |
Semantic title | string; YAML-quoted if needed |
{{status}} |
Task status value | string |
{{priority}} |
Task priority value | string |
{{dueDate}} |
Due date if set | YYYY-MM-DD or empty |
{{scheduledDate}} |
Scheduled date if set | YYYY-MM-DD or empty |
{{details}} |
Caller-provided body/description | string |
{{contexts}} |
Contexts list | comma-separated, e.g. "work, home" |
{{tags}} |
Tags list | comma-separated, e.g. "task, errands" |
{{hashtags}} |
Tags list as hashtags | space-separated, e.g. "#task #errands" |
{{timeEstimate}} |
Time estimate in minutes | integer string or empty |
{{parentNote}} |
Parent note name/path | string; YAML-quoted if needed |
Required date/time variables (expansion time):
| Variable | Value | Format |
|---|---|---|
{{date}} |
Current date | yyyy-MM-dd |
{{time}} |
Current time (24h) | HH:mm |
{{year}} |
Full year | yyyy |
{{month}} |
Month number | MM |
{{day}} |
Day of month | dd |
Extended variables (optional):
| Variable | Value | Format |
|---|---|---|
{{dateTime}} |
Date + time | yyyy-MM-dd-HHmm |
{{timestamp}} |
Full timestamp | yyyy-MM-dd-HHmmss |
{{shortDate}} |
Short date | yyMMdd |
{{shortYear}} |
Two-digit year | yy |
{{monthName}} |
Month full name | e.g. February |
{{monthNameShort}} |
Month short name | e.g. Feb |
{{dayName}} |
Weekday full name | e.g. Monday |
{{dayNameShort}} |
Weekday short name | e.g. Mon |
{{week}} |
ISO week number | ww |
{{quarter}} |
Quarter number | 1–4 |
{{hour}} |
Hour (24h) | HH |
{{minute}} |
Minute | mm |
{{second}} |
Second | ss |
{{time12}} |
Time with AM/PM | hh:mm a |
{{time24}} |
Time (24h) | HH:mm |
{{timezone}} |
UTC offset long | e.g. +05:30 |
{{utcOffset}} |
UTC offset long | e.g. +05:30 |
{{unix}} |
Unix timestamp (seconds) | integer string |
{{unixMs}} |
Unix timestamp (ms) | integer string |
{{zettel}} |
Zettelkasten ID | date + seconds-since-midnight base-36 |
{{titleLower}} |
Title lowercase | string |
{{titleUpper}} |
Title uppercase | string |
{{titleSnake}} |
Title snake_case | string |
{{titleKebab}} |
Title kebab-case | string |
{{titleCamel}} |
Title camelCase | string |
{{titlePascal}} |
Title PascalCase | string |
{{priorityShort}} |
First letter of priority | uppercase char |
{{statusShort}} |
First letter of status | uppercase char |
Unknown variables (not in the above lists) MUST be handled per templating.unknown_variable_policy (§9.14):
preserve: leave the{{...}}placeholder as-is.empty: replace with an empty string.
Deterministic expansion context:
- date/time variable expansion MUST use the active runtime timezone from §3.6.
- locale-sensitive variables (
monthName,monthNameShort,dayName,dayNameShort,time12) MUST use English locale outputs for portability (January,Jan,Monday,Mon,AM/PM). - template-generated datetime-like outputs used in frontmatter MUST normalize to second precision when written as canonical datetime roles (§3.3.2).
Failure behavior:
templating.failure_mode=error: template read/parse failures MUST abort create.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_missingortemplate_parse_failed).
5.3.6 Identity handling on create
If semantic id is provided on create, writers MUST preserve it.
If implementation supports auto-generation of semantic id, generation policy MUST be documented and resulting id MUST remain stable after create.
5.4 Update
5.4.1 Patch semantics
Update MUST be patch-by-default: only targeted semantic roles are changed.
5.4.2 Preservation rules
Update MUST preserve:
- unrelated known roles,
- unknown fields,
- original date/datetime granularity for untouched fields.
5.4.3 Example
Before:
title: Weekly review
scheduled: 2026-02-20
priority: normal
customClient: ACME
Operation: set priority to high.
After:
title: Weekly review
scheduled: 2026-02-20
priority: high
customClient: ACME
5.4.4 Title updates and filename updates
If an update changes semantic title, behavior MUST follow title.storage:
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.title.storage=frontmatter: implementation MUST update mapped title field and MUST NOT rename file unless an explicit rename operation is requested.
If implementation keeps frontmatter title for compatibility while title.storage=filename, it MUST keep stored title synchronized with effective filename-derived title.
5.5 Complete (non-recurring)
For non-recurring tasks, complete MUST:
- Set
statusto a configured completed value, unless already completed. If multiple completed values are configured, implementation MUST choose deterministically (default: first entry instatus.completed_values). - Set
completed_dateto completion day:- explicit completion-day input when provided,
- otherwise current local day in active runtime timezone (§5.2.1).
- Apply overwrite policy deterministically (
overwriteorpreserve_if_present). - Update
date_modifiedon state change.
Implementations MUST document completion-day input handling and completed_date overwrite policy.
Operation MUST be idempotent.
5.6 Uncomplete (non-recurring)
For non-recurring tasks, uncomplete MUST:
- Set
statusto configured active/default status according to policy (default:status.default). - Clear or retain
completed_dateaccording to policy. - Update
date_modifiedon state change.
Policy MUST be documented and deterministic.
Operation MUST be idempotent.
5.7 Complete instance (recurring)
For recurring tasks, complete with resolved target date D (§5.2.1) MUST follow §4.7.
Writers MUST NOT convert recurring completion into a base status rewrite unless explicit configuration requires it.
When recurrence_anchor=completion and caller supplies an explicit datetime target, implementations MUST apply §4.4.3 datetime DTSTART rewrite behavior.
When recurrence_anchor=scheduled and DTSTART is absent, implementations MUST insert DTSTART deterministically per §4.4.5.
Next-occurrence recalculation in this mode MUST follow §4.4.4.
5.8 Uncomplete instance (recurring)
For recurring tasks, uncomplete with resolved target date D (§5.2.1) MUST follow §4.8.
For recurrence_anchor=completion, ordinary uncomplete is intentionally non-reversible and MUST NOT roll back DTSTART.
5.9 Skip and unskip instance
Skip/unskip for recurring tasks with resolved target date D (§5.2.1) MUST follow §4.9 and §4.10.
5.10 Dependency operations
Dependency operations MUST follow §10.2.
5.10.1 Add dependency
Add dependency MUST:
- validate dependency entry schema,
- parse and resolve
uidper §11, - enforce duplicate and self-reference rules,
- preserve existing non-target dependency entries,
- update
date_modifiedon change.
5.10.2 Remove dependency
Remove dependency by uid MUST be idempotent.
5.10.3 Replace dependency list
Replace dependency list MUST be explicit (not default patch behavior).
5.11 Reminder operations
Reminder operations MUST follow §10.3.
5.11.1 Add reminder
Add reminder MUST:
- validate reminder schema,
- enforce unique
idwithin task, - update
date_modifiedon change.
5.11.2 Update reminder
Update reminder MUST target a reminder by id and be patch-by-default.
5.11.3 Remove reminder
Remove reminder by id MUST be idempotent.
5.12 Archive
Implementations MAY support archive semantics through:
- archive tag,
- archive status,
- dedicated boolean/archive field.
Archive behavior MUST be documented.
Archive MUST NOT implicitly delete the task.
5.13 Delete
Delete MUST remove the task file.
Optional safety behavior:
- Implementations MAY perform backlink/dependency checks.
- If checks are enabled, implementation MUST provide a bypass option for explicit force delete.
5.14 Rename
Rename operation changes file path/filename while preserving semantic record identity.
5.14.1 Rename interaction with title storage mode
- If
title.storage=filename, basename changes from rename MUST update effective semantic title. - If
title.storage=frontmatter, rename MUST NOT implicitly rewrite mappedtitleunless explicitly requested. - Implementations that persist frontmatter title in
title.storage=filenamemode MUST either update that field on rename or remove it to prevent divergence. - If semantic
idis present, rename/move MUST preserve its value unchanged.
If implementation supports link/reference updating, it MUST:
- update resolvable references deterministically,
- report unresolved or ambiguous updates,
- update dependency
uidandprojectsreferences consistently with normal links.
If implementation does not support reference updates, this limitation MUST be disclosed in conformance claim.
5.15 Batch operations
Batch operations MAY be supported.
If supported, implementation MUST report per-item outcomes and summary counts:
- total
- succeeded
- failed
Partial success behavior MUST be documented.
5.16 Concurrency
Implementations SHOULD provide write-conflict detection (for example based on modified timestamp or file hash).
When conflict is detected, operation MUST fail safely unless explicit overwrite is requested.
5.17 Dry run
If dry run mode is supported, operation MUST:
- execute validation and transformation logic,
- report intended changes,
- perform no file write.
5.18 Error model
Implementation-native operation failures MUST provide structured error information with:
- operation name,
- error code,
- message,
- optional field/path context.
For conformance adapter envelopes (§5.21), 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).
5.19 Time tracking management
This subsection applies to implementations that support time_entries.
5.19.1 Start time tracking
Start on task T MUST:
- fail when
Talready has an active time entry, - append a new entry with canonical
startTimeand noendTime, - MAY set a default
description, - update
date_modified, - persist canonical
time_entriesshape (including removal of legacy/staledurationfields when normalizing).
Recommended error code when already active: time_tracking_already_active.
5.19.2 Stop time tracking
Stop on task T MUST:
- fail when
Thas no active time entry, - set
endTimeon the active entry to canonical current datetime, - update
date_modified, - preserve non-target entries and unknown frontmatter fields.
Recommended error code when no active session exists: no_active_time_entry.
5.19.3 Edit or replace time entries
When replacing/editing time_entries explicitly, implementations MUST:
- validate each entry per §2.6.1 and §3.11,
- persist canonical datetime formats,
- treat
durationas derived compatibility input and SHOULD remove it from canonical writes, - update
date_modifiedwhen persisted state changes.
5.19.4 Remove a time entry
Remove MUST target one deterministic entry (for example by index or stable selector) and update date_modified on change.
Selector semantics MUST be documented.
5.19.5 Completion-triggered auto-stop
If time_tracking.auto_stop_on_complete=true (§9.16), completion transitions MUST trigger stop behavior for the same task only.
Rules:
- non-recurring tasks: trigger when status transitions from non-completed to completed status.
- recurring tasks: trigger when
complete_instancesgrows. - implementations MUST NOT stop active sessions on unrelated tasks.
- if
time_tracking.auto_stop_notification=true, implementations MAY emit a user-visible notice.
5.19.6 Reporting tracked time
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.
5.20 Operation examples
5.20.1 Non-recurring complete
Before:
title: Buy groceries
status: open
completedDate:
dateModified: 2026-02-20T09:00:00Z
After complete on 2026-02-20:
title: Buy groceries
status: done
completedDate: 2026-02-20
dateModified: 2026-02-20T09:05:00Z
5.20.2 Recurring complete instance
Before:
title: Weekly review
status: open
scheduled: 2026-02-20
recurrence: FREQ=WEEKLY;BYDAY=FR
completeInstances: []
skippedInstances: []
dateModified: 2026-02-20T08:00:00Z
After complete instance on 2026-02-20:
title: Weekly review
status: open
scheduled: 2026-02-20
recurrence: DTSTART:20260220;FREQ=WEEKLY;BYDAY=FR
completeInstances: [2026-02-20]
skippedInstances: []
dateModified: 2026-02-20T08:10:00Z
5.20.3 Recurring skip instance overriding completion
Before:
completeInstances: [2026-02-20]
skippedInstances: []
After skip 2026-02-20:
completeInstances: []
skippedInstances: [2026-02-20]
5.20.4 Preserve unknown fields on update
Before:
title: Plan Q2
status: open
vendorTicket: ZX-42
Update status to in-progress.
After:
title: Plan Q2
status: in-progress
vendorTicket: ZX-42
5.20.5 Add dependency
Before:
blockedBy: []
Add dependency:
uid: "[[prepare-metrics]]"
reltype: FINISHTOSTART
After:
blockedBy:
- uid: "[[prepare-metrics]]"
reltype: FINISHTOSTART
5.20.6 Add reminder
Before:
reminders: []
Add reminder:
id: due_minus_1h
type: relative
relatedTo: due
offset: -PT1H
After:
reminders:
- id: due_minus_1h
type: relative
relatedTo: due
offset: -PT1H
5.21 Conformance operation interface
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.
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.
General operations
op.mutate_with_validation
Tests §5.2 rule 1: validation runs before commit.
Input:
{ "strict": true, "frontmatter": { ... } }
Behavior: validate frontmatter as a task record. In strict mode (strict=true), any validation error MUST produce ok=false.
Output on validation error:
{ "ok": false, "error": "validation|invalid_type|..." }
op.atomic_write
Tests §5.2 rule 2: writes are all-or-nothing.
Input:
{
"original": { ... },
"patch": { ... },
"simulateFailureAfterWrite": true
}
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.
Output:
{
"ok": true,
"result": {
"committed": false,
"persisted": { ... }
}
}
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.
op.idempotency_check
Tests §5.2 rule 5: operations are safe under repetition.
Input:
{
"operation": "complete_nonrecurring",
"first": { "status": "open", "completedDate": null },
"second": { "status": "done", "completedDate": "2026-02-20" }
}
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.
Output:
{ "ok": true, "result": { "idempotent": true } }
op.update_patch
Tests §5.4 patch semantics and unknown-field preservation.
Input:
{
"original": { "title": "...", "vendorTicket": "ZX-42", ... },
"patch": { "priority": "high" },
"changed": true
}
Behavior: apply patch to original. Return the merged frontmatter.
Output:
{
"ok": true,
"result": {
"changed": true,
"frontmatter": { "title": "...", "priority": "high", "vendorTicket": "ZX-42", ... }
}
}
changedindicates whether any field actually changed value.frontmatterMUST include all unknown fields fromoriginalunchanged.
op.complete_nonrecurring
Tests §5.5 non-recurring completion.
Input:
{
"frontmatter": { "title": "...", "status": "open" },
"completedValues": ["done", "cancelled"],
"explicitDate": "2026-02-20"
}
Behavior: apply a complete operation to frontmatter using completedValues (ordered list) and explicitDate as the completion day (omit to use current local day).
Output:
{ "ok": true, "result": { "status": "done", "completedDate": "2026-02-20" } }
statusMUST be the first entry incompletedValues.completedDateMUST beexplicitDateif provided; otherwise a validYYYY-MM-DDdate.- If
frontmatter.statusis already a completed value, the operation MUST still succeed (idempotent).
op.uncomplete_nonrecurring
Tests §5.6 non-recurring uncompletion.
Input:
{
"frontmatter": { "title": "...", "status": "done", "completedDate": "2026-02-20" },
"defaultStatus": "open",
"clearCompletedDate": true
}
Output:
{ "ok": true, "result": { "status": "open", "completedDate": null } }
statusMUST bedefaultStatus.- If
clearCompletedDate=true,completedDateMUST benullin the result. - If
clearCompletedDate=false,completedDateMUST be preserved.
op.error_shape
Tests §5.18 error model.
Input:
{ "operation": "update", "code": "invalid_type", "message": "bad value", "field": "status" }
Behavior: construct and return a structured error object.
Output:
{
"ok": true,
"result": {
"operation": "update",
"code": "invalid_type",
"message": "..."
}
}
The message field MUST be a non-empty string. The operation and code fields MUST match the input.
op.detect_conflict
Tests §5.16 write-conflict detection.
Input:
{ "expectedVersion": "a", "actualVersion": "b", "overwrite": false }
Behavior: when expectedVersion != actualVersion and overwrite=false, the operation MUST fail.
Output on conflict:
{ "ok": false, "error": "conflict|write_conflict|..." }
op.dry_run
Tests §5.17 dry-run mode.
Input:
{ "operation": "update", "patch": { "status": "done" } }
Output:
{
"ok": true,
"result": {
"wrote": false,
"plannedChanges": ["status", ...]
}
}
wrote MUST be false. plannedChanges MUST list the field names that would have changed.
Recurrence operations
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.
Common input fields for recurrence operations:
targetDateorcompletionDate:YYYY-MM-DDtarget daycompleteInstances: current list of completed datesskippedInstances: current list of skipped dates
recurrence.effective_state output:
{ "ok": true, "result": { "value": "completed|skipped|open" } }
Time-tracking operations
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.
Common input patterns:
entries: currenttime_entriesarraynow: ISO 8601 datetime representing current instantdateModified: currentdate_modifiedvalue
time.report_totals output:
{ "ok": true, "result": { "closed_minutes": N, "live_minutes": M } }
live_minutes is only present when an active (no endTime) entry exists.