{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { link, mkdir, open, readdir, readFile, readlink, rename, rm } from \"node:fs/promises\";\nimport { hostname } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport {\n\tgetBlockedTasks,\n\tgetReadyTasks,\n\tTODO_ID_PATTERN,\n\tTodoValidationError,\n\tvalidateDefinitions,\n} from \"./scheduler.ts\";\nimport type {\n\tTodoClaim,\n\tTodoDefinition,\n\tTodoListDocument,\n\tTodoListView,\n\tTodoSnapshot,\n\tTodoStatus,\n\tTodoTask,\n} from \"./types.ts\";\nimport { TODO_STATUSES } from \"./types.ts\";\n\nconst LOCK_WAIT_MS = 25;\nconst DEFAULT_LOCK_TIMEOUT_MS = 60_000;\nconst DOCUMENT_HISTORY_LIMIT = 20;\nconst REVISION_SNAPSHOT_LIMIT = 1000;\n\nexport interface FileTodoStoreOptions {\n\trevisionSnapshotLimit?: number;\n\tlockTimeoutMs?: number;\n\tlockHeartbeatMs?: number;\n\tlockHook?: (phase: \"published\" | \"acquired\", listId: string) => Promise<void>;\n\tlockNonce?: () => string;\n\tlockEnvironment?: LockEnvironment;\n}\n\ninterface LockContender {\n\tversion: 1;\n\tnonce: string;\n\townerId: string;\n\tpid: number;\n\thost: string;\n\thostId: string;\n\tbootId: string;\n\tpidNamespace: string;\n\tprocessStartToken: string;\n\tcreatedAt: number;\n\tchoosing: boolean;\n\tticket: number;\n}\n\nexport interface LockIdentity {\n\thost: string;\n\thostId: string;\n\tbootId: string;\n\tpidNamespace: string;\n\tprocessStartToken: string;\n}\n\nexport interface ProcessProbe {\n\tstatus: \"alive\" | \"missing\" | \"unknown\";\n\tstartToken?: string;\n}\n\nexport interface LockEnvironment {\n\tcurrent(): Promise<LockIdentity>;\n\tprobe(pid: number): Promise<ProcessProbe>;\n\tnow(): number;\n}\n\ninterface LockHeartbeat {\n\tversion: 1;\n\townerId: string;\n\ttimestamp: number;\n}\n\nexport const TODO_LIST_NOT_FOUND = \"todo-list-not-found\";\n\nexport class TodoPersistenceError extends Error {\n\treadonly code: string;\n\tconstructor(message: string, options?: ErrorOptions & { code?: string }) {\n\t\tsuper(message, options);\n\t\tthis.name = \"TodoPersistenceError\";\n\t\tthis.code = options?.code ?? \"todo-persistence\";\n\t}\n}\n\nfunction cloneTask(task: TodoTask): TodoTask {\n\treturn {\n\t\t...task,\n\t\tdepends_on: [...task.depends_on],\n\t\tacceptance_criteria: task.acceptance_criteria ? [...task.acceptance_criteria] : undefined,\n\t};\n}\n\nfunction snapshot(document: TodoListDocument): TodoSnapshot {\n\treturn {\n\t\trevision: document.revision,\n\t\tglobal_direction: document.global_direction,\n\t\ttasks: document.tasks.map(cloneTask),\n\t\ttombstones: document.tombstones.map((entry) => ({ ...entry })),\n\t\tcreated_at: document.created_at,\n\t\tupdated_at: document.updated_at,\n\t};\n}\n\nfunction cloneDocument(document: TodoListDocument): TodoListDocument {\n\treturn {\n\t\t...snapshot(document),\n\t\tversion: 1,\n\t\tid: document.id,\n\t\thistory: document.history.map((entry) => ({\n\t\t\t...entry,\n\t\t\ttasks: entry.tasks.map(cloneTask),\n\t\t\ttombstones: entry.tombstones.map((tombstone) => ({ ...tombstone })),\n\t\t})),\n\t};\n}\n\nfunction definitions(tasks: readonly TodoTask[]): TodoDefinition[] {\n\treturn tasks.map(({ id, subject, description, active_form, depends_on, acceptance_criteria }) => ({\n\t\tid,\n\t\tsubject,\n\t\tdescription,\n\t\tactive_form,\n\t\tdepends_on,\n\t\tacceptance_criteria,\n\t}));\n}\n\nfunction assertDocument(value: unknown, path: string, expectedId: string): asserts value is TodoListDocument {\n\tif (typeof value !== \"object\" || value === null) throw new TodoPersistenceError(`Invalid todo data in ${path}`);\n\tconst record = value as Record<string, unknown>;\n\tif (\n\t\trecord.version !== 1 ||\n\t\trecord.id !== expectedId ||\n\t\t!isPositiveInteger(record.revision) ||\n\t\ttypeof record.global_direction !== \"string\" ||\n\t\t!Array.isArray(record.tasks) ||\n\t\t!Array.isArray(record.tombstones) ||\n\t\t!Array.isArray(record.history) ||\n\t\t!isTimestamp(record.created_at) ||\n\t\t!isTimestamp(record.updated_at)\n\t) {\n\t\tthrow new TodoPersistenceError(`Unsupported or malformed todo data in ${path}`);\n\t}\n\tfor (const task of record.tasks) assertTask(task, record.revision, path);\n\tfor (const tombstone of record.tombstones) assertTombstone(tombstone, record.revision, path);\n\tfor (const historical of record.history) assertSnapshot(historical, path);\n\tconst taskIds = new Set(record.tasks.map((task) => (task as Record<string, unknown>).id));\n\tif (record.tombstones.some((entry) => taskIds.has((entry as Record<string, unknown>).id))) {\n\t\tthrow new TodoPersistenceError(`Task and tombstone ids overlap in ${path}`);\n\t}\n}\n\nexport class FileTodoStore {\n\tprivate readonly rootDir: string;\n\tprivate readonly revisionSnapshotLimit: number;\n\tprivate readonly lockTimeoutMs: number;\n\tprivate readonly lockHeartbeatMs: number;\n\tprivate readonly lockHook: FileTodoStoreOptions[\"lockHook\"];\n\tprivate readonly lockNonce: () => string;\n\tprivate readonly lockEnvironment: LockEnvironment;\n\tprivate diagnostic: string | undefined;\n\n\tconstructor(rootDir: string, options: FileTodoStoreOptions = {}) {\n\t\tthis.rootDir = rootDir;\n\t\tthis.revisionSnapshotLimit = options.revisionSnapshotLimit ?? REVISION_SNAPSHOT_LIMIT;\n\t\tthis.lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;\n\t\tthis.lockHeartbeatMs = options.lockHeartbeatMs ?? 5000;\n\t\tthis.lockHook = options.lockHook;\n\t\tthis.lockNonce = options.lockNonce ?? randomUUID;\n\t\tthis.lockEnvironment = options.lockEnvironment ?? SYSTEM_LOCK_ENVIRONMENT;\n\t\tif (!Number.isInteger(this.revisionSnapshotLimit) || this.revisionSnapshotLimit < 1) {\n\t\t\tthrow new TodoValidationError(\"Revision snapshot limit must be a positive integer\");\n\t\t}\n\t\tif (!Number.isInteger(this.lockTimeoutMs) || this.lockTimeoutMs < 1) {\n\t\t\tthrow new TodoValidationError(\"Lock timeout must be a positive integer\");\n\t\t}\n\t\tif (!Number.isInteger(this.lockHeartbeatMs) || this.lockHeartbeatMs < 10) {\n\t\t\tthrow new TodoValidationError(\"Lock heartbeat must be at least 10 milliseconds\");\n\t\t}\n\t}\n\n\ttakeDiagnostic(): string | undefined {\n\t\tconst diagnostic = this.diagnostic;\n\t\tthis.diagnostic = undefined;\n\t\treturn diagnostic;\n\t}\n\n\tasync create(\n\t\tglobalDirection: string,\n\t\titems: readonly TodoDefinition[],\n\t\tid: string = randomUUID(),\n\t): Promise<TodoListDocument> {\n\t\tif (!globalDirection.trim()) throw new TodoValidationError(\"Global direction must not be empty\");\n\t\tvalidateDefinitions(items);\n\t\tawait mkdir(this.rootDir, { recursive: true });\n\t\treturn this.withLock(id, async () => {\n\t\t\tconst path = this.documentPath(id);\n\t\t\ttry {\n\t\t\t\tawait readFile(path);\n\t\t\t\tthrow new TodoValidationError(`Todo list \"${id}\" already exists`);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof TodoValidationError) throw error;\n\t\t\t\tif (!isNotFound(error)) throw error;\n\t\t\t}\n\t\t\tconst now = new Date().toISOString();\n\t\t\tconst tasks = items.map((item) => ({\n\t\t\t\t...item,\n\t\t\t\tdepends_on: [...item.depends_on],\n\t\t\t\tacceptance_criteria: item.acceptance_criteria ? [...item.acceptance_criteria] : undefined,\n\t\t\t\tstatus: \"pending\" as const,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t\trevision: 1,\n\t\t\t}));\n\t\t\tconst document: TodoListDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tid,\n\t\t\t\trevision: 1,\n\t\t\t\tglobal_direction: globalDirection.trim(),\n\t\t\t\ttasks,\n\t\t\t\ttombstones: [],\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t\thistory: [],\n\t\t\t};\n\t\t\tawait this.writeDocument(document);\n\t\t\treturn cloneDocument(document);\n\t\t});\n\t}\n\n\tasync read(id: string, revision?: number): Promise<TodoListDocument> {\n\t\treturn this.withLock(id, async () => {\n\t\t\tconst document = await this.readDocument(id);\n\t\t\tif (revision === undefined || revision === document.revision) return cloneDocument(document);\n\t\t\tconst historical =\n\t\t\t\tdocument.history.find((entry) => entry.revision === revision) ?? (await this.readSnapshot(id, revision));\n\t\t\tif (!historical) throw new TodoValidationError(`Todo list \"${id}\" has no revision ${revision}`);\n\t\t\treturn {\n\t\t\t\t...historical,\n\t\t\t\tversion: 1,\n\t\t\t\tid,\n\t\t\t\thistory: document.history.map((entry) => ({\n\t\t\t\t\t...entry,\n\t\t\t\t\ttasks: entry.tasks.map(cloneTask),\n\t\t\t\t\ttombstones: entry.tombstones.map((tombstone) => ({ ...tombstone })),\n\t\t\t\t})),\n\t\t\t};\n\t\t});\n\t}\n\n\tasync view(id: string, revision?: number): Promise<TodoListView> {\n\t\tconst list = await this.read(id, revision);\n\t\tconst ready = getReadyTasks(list.tasks).map(cloneTask);\n\t\tconst blocked = getBlockedTasks(list.tasks).map(cloneTask);\n\t\treturn {\n\t\t\tlist,\n\t\t\tready,\n\t\t\tblocked,\n\t\t\tsummary: {\n\t\t\t\ttotal: list.tasks.length,\n\t\t\t\tpending: list.tasks.filter((task) => task.status === \"pending\").length,\n\t\t\t\tin_progress: list.tasks.filter((task) => task.status === \"in_progress\").length,\n\t\t\t\tcompleted: list.tasks.filter((task) => task.status === \"completed\").length,\n\t\t\t\tready: ready.length,\n\t\t\t\tblocked: blocked.length,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync add(id: string, items: readonly TodoDefinition[]): Promise<TodoListDocument> {\n\t\tif (items.length === 0) throw new TodoValidationError(\"At least one task is required\");\n\t\treturn this.mutate(id, (document, now) => {\n\t\t\tconst deletedIds = new Set(document.tombstones.map((entry) => entry.id));\n\t\t\tfor (const item of items) {\n\t\t\t\tif (deletedIds.has(item.id)) {\n\t\t\t\t\tthrow new TodoValidationError(`Todo \"${item.id}\" was deleted and its id cannot be reused`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst next = [\n\t\t\t\t...document.tasks,\n\t\t\t\t...items.map((item) => ({\n\t\t\t\t\t...item,\n\t\t\t\t\tdepends_on: [...item.depends_on],\n\t\t\t\t\tacceptance_criteria: item.acceptance_criteria ? [...item.acceptance_criteria] : undefined,\n\t\t\t\t\tstatus: \"pending\" as const,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t\trevision: document.revision + 1,\n\t\t\t\t})),\n\t\t\t];\n\t\t\tvalidateDefinitions(definitions(next));\n\t\t\tdocument.tasks = next;\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync update(\n\t\tlistId: string,\n\t\ttaskId: string,\n\t\tpatch: {\n\t\t\tsubject?: string;\n\t\t\tdescription?: string | null;\n\t\t\tactive_form?: string | null;\n\t\t\tdepends_on?: string[];\n\t\t\tacceptance_criteria?: string[] | null;\n\t\t\tstatus?: TodoStatus;\n\t\t\texpected_revision?: number;\n\t\t},\n\t): Promise<TodoListDocument> {\n\t\treturn this.mutate(listId, (document, now) => {\n\t\t\tconst task = requireTask(document, taskId);\n\t\t\tif (patch.expected_revision !== undefined && patch.expected_revision !== task.revision) {\n\t\t\t\tthrow new TodoValidationError(\n\t\t\t\t\t`REJECTED: revision mismatch (expected ${patch.expected_revision}, current ${task.revision}). Call todo_list to get current state, then retry with current revision.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (patch.status === \"in_progress\" && task.status !== \"in_progress\") {\n\t\t\t\tthrow new TodoValidationError(`Todo \"${taskId}\" must enter in_progress through todo_claim`);\n\t\t\t}\n\t\t\tif (patch.status === \"completed\" && task.status !== \"in_progress\") {\n\t\t\t\tthrow new TodoValidationError(`Todo \"${taskId}\" must be claimed before completion`);\n\t\t\t}\n\t\t\tif (patch.subject !== undefined) task.subject = patch.subject;\n\t\t\tif (patch.description !== undefined) task.description = patch.description?.trim() || undefined;\n\t\t\tif (patch.active_form !== undefined) task.active_form = patch.active_form?.trim() || undefined;\n\t\t\tif (patch.depends_on !== undefined) task.depends_on = [...patch.depends_on];\n\t\t\tif (patch.acceptance_criteria !== undefined) {\n\t\t\t\ttask.acceptance_criteria = patch.acceptance_criteria?.map((criterion) => criterion.trim()) ?? undefined;\n\t\t\t}\n\t\t\tif (patch.status !== undefined) {\n\t\t\t\ttask.status = patch.status;\n\t\t\t\tif (patch.status !== \"in_progress\") {\n\t\t\t\t\ttask.owner = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tvalidateDefinitions(definitions(document.tasks));\n\t\t\ttask.updated_at = now;\n\t\t\ttask.revision = document.revision + 1;\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync claim(listId: string, taskId: string, owner: string, expectedRevision?: number): Promise<TodoClaim> {\n\t\tif (!owner.trim()) throw new TodoValidationError(\"Claim owner must not be empty\");\n\t\tconst list = await this.mutate(listId, (document, now) => {\n\t\t\tconst task = requireTask(document, taskId);\n\t\t\tif (expectedRevision !== undefined && task.revision !== expectedRevision) {\n\t\t\t\tthrow new TodoValidationError(\n\t\t\t\t\t`REJECTED: revision mismatch (expected ${expectedRevision}, current ${task.revision}). Call todo_list to get current state, then retry with current revision.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (task.status !== \"pending\")\n\t\t\t\tthrow new TodoValidationError(`Todo \"${taskId}\" is ${task.status}, not pending`);\n\t\t\tconst readyIds = new Set(getReadyTasks(document.tasks).map((candidate) => candidate.id));\n\t\t\tif (!readyIds.has(taskId)) throw new TodoValidationError(`Todo \"${taskId}\" is blocked by dependencies`);\n\t\t\ttask.status = \"in_progress\";\n\t\t\ttask.owner = owner.trim();\n\t\t\ttask.updated_at = now;\n\t\t\ttask.revision = document.revision + 1;\n\t\t\treturn true;\n\t\t});\n\t\treturn { task: cloneTask(requireTask(list, taskId)) };\n\t}\n\n\tasync release(listId: string, taskId: string): Promise<TodoListDocument> {\n\t\treturn this.mutate(listId, (document, now) => {\n\t\t\tconst task = requireTask(document, taskId);\n\t\t\tif (task.status !== \"in_progress\")\n\t\t\t\tthrow new TodoValidationError(`Todo \"${taskId}\" is ${task.status}, not in_progress`);\n\t\t\ttask.status = \"pending\";\n\t\t\ttask.owner = undefined;\n\t\t\ttask.updated_at = now;\n\t\t\ttask.revision = document.revision + 1;\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync releaseIfOwned(listId: string, taskId: string, expectedOwner: string): Promise<TodoListDocument> {\n\t\treturn this.mutate(listId, (document, now) => {\n\t\t\tconst task = requireTask(document, taskId);\n\t\t\tif (task.status !== \"in_progress\" || task.owner !== expectedOwner) return false;\n\t\t\ttask.status = \"pending\";\n\t\t\ttask.owner = undefined;\n\t\t\ttask.updated_at = now;\n\t\t\ttask.revision = document.revision + 1;\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync transfer(listId: string, taskId: string, newOwner: string): Promise<TodoListDocument> {\n\t\tif (!newOwner.trim()) throw new TodoValidationError(\"New claim owner must not be empty\");\n\t\treturn this.mutate(listId, (document, now) => {\n\t\t\tconst task = requireTask(document, taskId);\n\t\t\tif (task.status !== \"in_progress\")\n\t\t\t\tthrow new TodoValidationError(`Todo \"${taskId}\" is ${task.status}, not in_progress`);\n\t\t\tif (task.owner === newOwner) return false;\n\t\t\ttask.owner = newOwner.trim();\n\t\t\ttask.updated_at = now;\n\t\t\ttask.revision = document.revision + 1;\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync reconcileOwners(listId: string, liveOwners: ReadonlySet<string>): Promise<TodoListDocument> {\n\t\treturn this.mutate(listId, (document, now) => {\n\t\t\tconst orphans = document.tasks.filter(\n\t\t\t\t(task) => task.status === \"in_progress\" && task.owner !== undefined && !liveOwners.has(task.owner),\n\t\t\t);\n\t\t\tif (orphans.length === 0) return false;\n\t\t\tfor (const task of orphans) {\n\t\t\t\ttask.status = \"pending\";\n\t\t\t\ttask.owner = undefined;\n\t\t\t\ttask.updated_at = now;\n\t\t\t\ttask.revision = document.revision + 1;\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync delete(listId: string, taskId: string): Promise<TodoListDocument> {\n\t\treturn this.mutate(listId, (document, now) => {\n\t\t\trequireTask(document, taskId);\n\t\t\tdocument.tasks = document.tasks.filter((candidate) => candidate.id !== taskId);\n\t\t\tfor (const candidate of document.tasks) {\n\t\t\t\tif (!candidate.depends_on.includes(taskId)) continue;\n\t\t\t\tcandidate.depends_on = candidate.depends_on.filter((id) => id !== taskId);\n\t\t\t\tcandidate.updated_at = now;\n\t\t\t\tcandidate.revision = document.revision + 1;\n\t\t\t}\n\t\t\tdocument.tombstones.push({ id: taskId, deleted_at: now, revision: document.revision + 1 });\n\t\t\tvalidateDefinitions(definitions(document.tasks), true);\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tasync clone(sourceId: string, revision?: number, targetId: string = randomUUID()): Promise<TodoListDocument> {\n\t\tconst source = await this.read(sourceId, revision);\n\t\treturn this.withLock(targetId, async () => {\n\t\t\ttry {\n\t\t\t\tawait readFile(this.documentPath(targetId));\n\t\t\t\tthrow new TodoValidationError(`Todo list \"${targetId}\" already exists`);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof TodoValidationError) throw error;\n\t\t\t\tif (!isNotFound(error)) throw error;\n\t\t\t}\n\t\t\tconst now = new Date().toISOString();\n\t\t\tconst document: TodoListDocument = {\n\t\t\t\t...source,\n\t\t\t\tid: targetId,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t\ttasks: source.tasks.map((task) => ({\n\t\t\t\t\t...cloneTask(task),\n\t\t\t\t\tstatus: task.status === \"in_progress\" ? \"pending\" : task.status,\n\t\t\t\t\towner: undefined,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t})),\n\t\t\t\thistory: [],\n\t\t\t};\n\t\t\tawait this.writeDocument(document);\n\t\t\treturn cloneDocument(document);\n\t\t});\n\t}\n\n\tasync removeList(id: string): Promise<void> {\n\t\tawait this.withLock(id, async () => rm(this.listDir(id), { recursive: true, force: true }));\n\t}\n\n\tprivate async mutate(\n\t\tid: string,\n\t\toperation: (document: TodoListDocument, now: string) => boolean,\n\t): Promise<TodoListDocument> {\n\t\treturn this.withLock(id, async () => {\n\t\t\tconst document = await this.readDocument(id);\n\t\t\tconst previous = snapshot(document);\n\t\t\tconst now = new Date().toISOString();\n\t\t\tif (operation(document, now) === false) return cloneDocument(document);\n\t\t\tawait this.writeSnapshot(id, previous);\n\t\t\tdocument.history.push(previous);\n\t\t\tif (document.history.length > DOCUMENT_HISTORY_LIMIT) {\n\t\t\t\tdocument.history.splice(0, document.history.length - DOCUMENT_HISTORY_LIMIT);\n\t\t\t}\n\t\t\tdocument.revision++;\n\t\t\tdocument.updated_at = now;\n\t\t\tawait this.writeDocument(document);\n\t\t\treturn cloneDocument(document);\n\t\t});\n\t}\n\n\tprivate async readDocument(id: string): Promise<TodoListDocument> {\n\t\tvalidateListId(id);\n\t\tconst path = this.documentPath(id);\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(await readFile(path, \"utf8\"));\n\t\t\tassertDocument(value, path, id);\n\t\t\tvalidateDefinitions(definitions(value.tasks), true);\n\t\t\treturn value;\n\t\t} catch (error) {\n\t\t\tif (isNotFound(error))\n\t\t\t\tthrow new TodoPersistenceError(`Todo list \"${id}\" does not exist`, {\n\t\t\t\t\tcause: error,\n\t\t\t\t\tcode: TODO_LIST_NOT_FOUND,\n\t\t\t\t});\n\t\t\tconst backupPath = this.backupPath(id);\n\t\t\ttry {\n\t\t\t\tconst backup = await readFile(backupPath, \"utf8\");\n\t\t\t\tconst value: unknown = JSON.parse(backup);\n\t\t\t\tassertDocument(value, backupPath, id);\n\t\t\t\tvalidateDefinitions(definitions(value.tasks), true);\n\t\t\t\tawait this.atomicWrite(path, backup);\n\t\t\t\tthis.diagnostic = `Recovered todo list \"${id}\" from backup after ${path} became unreadable`;\n\t\t\t\treturn value;\n\t\t\t} catch (backupError) {\n\t\t\t\tthrow new TodoPersistenceError(`Todo list \"${id}\" is corrupt and no valid backup is available`, {\n\t\t\t\t\tcause: backupError,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async writeDocument(document: TodoListDocument): Promise<void> {\n\t\tconst directory = this.listDir(document.id);\n\t\tconst path = this.documentPath(document.id);\n\t\tawait mkdir(directory, { recursive: true });\n\t\ttry {\n\t\t\tconst previous = await readFile(path, \"utf8\");\n\t\t\tawait this.atomicWrite(this.backupPath(document.id), previous);\n\t\t} catch (error) {\n\t\t\tif (!isNotFound(error)) throw error;\n\t\t}\n\t\tawait this.atomicWrite(path, `${JSON.stringify(document, null, 2)}\\n`);\n\t}\n\n\tprivate async writeSnapshot(id: string, value: TodoSnapshot): Promise<void> {\n\t\tconst revisionsDir = join(this.listDir(id), \"revisions\");\n\t\tawait this.atomicWrite(join(revisionsDir, `${value.revision}.json`), `${JSON.stringify(value, null, 2)}\\n`);\n\t\tconst revisions = (await readdir(revisionsDir))\n\t\t\t.map((name) => ({ name, revision: parseRevisionSnapshotName(name) }))\n\t\t\t.filter((entry): entry is { name: string; revision: number } => entry.revision !== undefined)\n\t\t\t.sort((left, right) => left.revision - right.revision);\n\t\tfor (const entry of revisions.slice(0, Math.max(0, revisions.length - this.revisionSnapshotLimit))) {\n\t\t\tawait rm(join(revisionsDir, entry.name), { force: true });\n\t\t}\n\t}\n\n\tprivate async readSnapshot(id: string, revision: number): Promise<TodoSnapshot | undefined> {\n\t\tconst path = join(this.listDir(id), \"revisions\", `${revision}.json`);\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(await readFile(path, \"utf8\"));\n\t\t\tassertSnapshot(value, path);\n\t\t\treturn value as TodoSnapshot;\n\t\t} catch (error) {\n\t\t\tif (isNotFound(error)) return undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async atomicWrite(path: string, content: string): Promise<void> {\n\t\tconst directory = dirname(path);\n\t\tawait mkdir(directory, { recursive: true });\n\t\tconst temporary = join(directory, `.${process.pid}.${randomUUID()}.tmp`);\n\t\tconst handle = await open(temporary, \"wx\", 0o600);\n\t\ttry {\n\t\t\tawait handle.writeFile(content, \"utf8\");\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait rename(temporary, path);\n\t\t// Syncing the parent directory persists the rename for crash consistency on\n\t\t// POSIX. Windows does not support fsync on directory handles and rejects it\n\t\t// with EPERM, so skip it there; NTFS journals the rename metadata instead.\n\t\tif (process.platform !== \"win32\") {\n\t\t\tconst directoryHandle = await open(directory, \"r\");\n\t\t\ttry {\n\t\t\t\tawait directoryHandle.sync();\n\t\t\t} finally {\n\t\t\t\tawait directoryHandle.close();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async atomicLockWrite(path: string, content: string): Promise<void> {\n\t\tconst directory = dirname(path);\n\t\tawait mkdir(directory, { recursive: true });\n\t\tconst temporary = join(directory, `.${process.pid}.${randomUUID()}.tmp`);\n\t\tconst handle = await open(temporary, \"wx\", 0o600);\n\t\ttry {\n\t\t\tawait handle.writeFile(content, \"utf8\");\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait rename(temporary, path);\n\t}\n\n\tprivate async atomicLockPublish(path: string, content: string): Promise<void> {\n\t\tconst directory = dirname(path);\n\t\tawait mkdir(directory, { recursive: true });\n\t\tconst temporary = join(directory, `.${process.pid}.${randomUUID()}.tmp`);\n\t\tconst handle = await open(temporary, \"wx\", 0o600);\n\t\ttry {\n\t\t\tawait handle.writeFile(content, \"utf8\");\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\ttry {\n\t\t\tawait link(temporary, path);\n\t\t} finally {\n\t\t\tawait rm(temporary, { force: true });\n\t\t}\n\t}\n\n\tprivate async withLock<T>(id: string, operation: () => Promise<T>): Promise<T> {\n\t\tvalidateListId(id);\n\t\tconst lockDir = join(this.rootDir, \".locks\", id);\n\t\tawait mkdir(lockDir, { recursive: true });\n\t\tconst identity = await this.lockEnvironment.current();\n\t\tconst nonce = this.lockNonce();\n\t\tif (!/^[A-Za-z0-9-]{1,64}$/.test(nonce)) throw new TodoPersistenceError(\"Lock nonce is invalid\");\n\t\tconst ownerId = randomUUID();\n\t\tconst contenderPath = join(lockDir, `${nonce}.${ownerId}.json`);\n\t\tconst heartbeatPath = join(lockDir, `${nonce}.${ownerId}.heartbeat`);\n\t\tconst contender: LockContender = {\n\t\t\tversion: 1,\n\t\t\tnonce,\n\t\t\townerId,\n\t\t\tpid: process.pid,\n\t\t\t...identity,\n\t\t\tcreatedAt: this.lockEnvironment.now(),\n\t\t\tchoosing: true,\n\t\t\tticket: 0,\n\t\t};\n\t\tlet published = false;\n\t\tlet stopHeartbeat: (() => Promise<void>) | undefined;\n\t\ttry {\n\t\t\tawait this.atomicLockPublish(\n\t\t\t\theartbeatPath,\n\t\t\t\tJSON.stringify({ version: 1, ownerId, timestamp: contender.createdAt } satisfies LockHeartbeat),\n\t\t\t);\n\t\t\tawait this.atomicLockPublish(contenderPath, JSON.stringify(contender));\n\t\t\tpublished = true;\n\t\t\tstopHeartbeat = this.startLockHeartbeat(heartbeatPath, ownerId);\n\t\t\tawait this.lockHook?.(\"published\", id);\n\t\t\tconst initial = await readLockContenders(lockDir, identity, this.lockEnvironment);\n\t\t\tcontender.choosing = false;\n\t\t\tcontender.ticket = Math.max(0, ...initial.valid.map((candidate) => candidate.ticket)) + 1;\n\t\t\tawait this.atomicLockWrite(contenderPath, JSON.stringify(contender));\n\t\t\tconst deadline = Date.now() + this.lockTimeoutMs;\n\t\t\twhile (true) {\n\t\t\t\tconst contenders = await readLockContenders(lockDir, identity, this.lockEnvironment);\n\t\t\t\tfor (const stale of contenders.stale) {\n\t\t\t\t\tawait removeOwnedContender(stale.contenderPath, stale.ownerId);\n\t\t\t\t\tawait rm(stale.heartbeatPath, { force: true });\n\t\t\t\t}\n\t\t\t\tif (contenders.malformed || !contenders.valid.some((candidate) => candidate.ownerId === ownerId)) {\n\t\t\t\t\tthrow new TodoPersistenceError(`Todo list \"${id}\" lock state is malformed`);\n\t\t\t\t}\n\t\t\t\tconst blockers = contenders.valid.filter(\n\t\t\t\t\t(candidate) =>\n\t\t\t\t\t\tcandidate.ownerId !== ownerId &&\n\t\t\t\t\t\t(candidate.choosing ||\n\t\t\t\t\t\t\tcandidate.ticket < contender.ticket ||\n\t\t\t\t\t\t\t(candidate.ticket === contender.ticket && compareContenders(candidate, contender) < 0)),\n\t\t\t\t);\n\t\t\t\tif (blockers.length === 0) break;\n\t\t\t\tif (Date.now() >= deadline) {\n\t\t\t\t\tconst retainedUnverifiable = blockers.some((candidate) =>\n\t\t\t\t\t\tcontenders.unverifiableOwnerIds.has(candidate.ownerId),\n\t\t\t\t\t);\n\t\t\t\t\tconst detail = retainedUnverifiable\n\t\t\t\t\t\t? \"owner identity could not be verified, so its contender was retained conservatively\"\n\t\t\t\t\t\t: \"another live owner still holds the lock\";\n\t\t\t\t\tthrow new TodoPersistenceError(`Timed out waiting for todo list \"${id}\" lock; ${detail}`);\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS));\n\t\t\t}\n\t\t\tawait this.lockHook?.(\"acquired\", id);\n\t\t\treturn await operation();\n\t\t} finally {\n\t\t\tawait stopHeartbeat?.();\n\t\t\tif (published) await removeOwnedContender(contenderPath, ownerId);\n\t\t\tawait rm(heartbeatPath, { force: true });\n\t\t}\n\t}\n\n\tprivate startLockHeartbeat(path: string, ownerId: string): () => Promise<void> {\n\t\tlet stopped = false;\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet pending = Promise.resolve();\n\t\tconst schedule = () => {\n\t\t\tif (stopped) return;\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tpending = this.atomicLockWrite(\n\t\t\t\t\tpath,\n\t\t\t\t\tJSON.stringify({ version: 1, ownerId, timestamp: this.lockEnvironment.now() } satisfies LockHeartbeat),\n\t\t\t\t).then(schedule, schedule);\n\t\t\t}, this.lockHeartbeatMs);\n\t\t};\n\t\tschedule();\n\t\treturn async () => {\n\t\t\tstopped = true;\n\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\tawait pending;\n\t\t};\n\t}\n\n\tprivate listDir(id: string): string {\n\t\tvalidateListId(id);\n\t\treturn join(this.rootDir, id);\n\t}\n\n\tprivate documentPath(id: string): string {\n\t\treturn join(this.listDir(id), \"tasks.json\");\n\t}\n\n\tprivate backupPath(id: string): string {\n\t\treturn join(this.listDir(id), \"tasks.json.bak\");\n\t}\n}\n\nfunction requireTask(document: TodoListDocument, taskId: string): TodoTask {\n\tconst task = document.tasks.find((candidate) => candidate.id === taskId);\n\tif (!task) throw new TodoValidationError(`Todo \"${taskId}\" does not exist`);\n\treturn task;\n}\n\nfunction isNotFound(error: unknown): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction parseRevisionSnapshotName(name: string): number | undefined {\n\tconst match = /^(\\d+)\\.json$/.exec(name);\n\tif (!match) return undefined;\n\tconst revision = Number(match[1]);\n\treturn isPositiveInteger(revision) ? revision : undefined;\n}\n\nasync function readLockContenders(\n\tlockDir: string,\n\tidentity: LockIdentity,\n\tenvironment: LockEnvironment,\n): Promise<{\n\tvalid: LockContender[];\n\tstale: Array<{ contenderPath: string; heartbeatPath: string; ownerId: string }>;\n\tunverifiableOwnerIds: Set<string>;\n\tmalformed: boolean;\n}> {\n\tconst valid: LockContender[] = [];\n\tconst stale: Array<{ contenderPath: string; heartbeatPath: string; ownerId: string }> = [];\n\tconst unverifiableOwnerIds = new Set<string>();\n\tlet malformed = false;\n\tfor (const name of await readdir(lockDir)) {\n\t\tif (!name.endsWith(\".json\")) continue;\n\t\tconst path = join(lockDir, name);\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(await readFile(path, \"utf8\"));\n\t\t\tif (!isLockContender(value, name.slice(0, -\".json\".length))) {\n\t\t\t\tmalformed = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst heartbeatPath = join(lockDir, `${value.nonce}.${value.ownerId}.heartbeat`);\n\t\t\tconst state = await classifyContender(value, identity, environment);\n\t\t\tif (state === \"stale\") {\n\t\t\t\tstale.push({ contenderPath: path, heartbeatPath, ownerId: value.ownerId });\n\t\t\t} else {\n\t\t\t\tvalid.push(value);\n\t\t\t\tif (state === \"unverifiable\" || state === \"foreign\") unverifiableOwnerIds.add(value.ownerId);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (!isNotFound(error)) malformed = true;\n\t\t}\n\t}\n\treturn { valid, stale, unverifiableOwnerIds, malformed };\n}\n\nfunction isLockContender(value: unknown, expectedKey: string): value is LockContender {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tconst contender = value as Record<string, unknown>;\n\treturn (\n\t\tcontender.version === 1 &&\n\t\ttypeof contender.nonce === \"string\" &&\n\t\ttypeof contender.ownerId === \"string\" &&\n\t\t`${contender.nonce}.${contender.ownerId}` === expectedKey &&\n\t\ttypeof contender.pid === \"number\" &&\n\t\tNumber.isInteger(contender.pid) &&\n\t\ttypeof contender.host === \"string\" &&\n\t\ttypeof contender.hostId === \"string\" &&\n\t\ttypeof contender.bootId === \"string\" &&\n\t\ttypeof contender.pidNamespace === \"string\" &&\n\t\ttypeof contender.processStartToken === \"string\" &&\n\t\ttypeof contender.createdAt === \"number\" &&\n\t\tNumber.isFinite(contender.createdAt) &&\n\t\ttypeof contender.choosing === \"boolean\" &&\n\t\ttypeof contender.ticket === \"number\" &&\n\t\tNumber.isInteger(contender.ticket) &&\n\t\tcontender.ticket >= 0\n\t);\n}\n\nconst SYSTEM_LOCK_ENVIRONMENT: LockEnvironment = {\n\tcurrent: currentSystemLockIdentity,\n\tprobe: probeSystemProcess,\n\tnow: Date.now,\n};\n\nasync function currentSystemLockIdentity(): Promise<LockIdentity> {\n\tconst linux = process.platform === \"linux\";\n\tconst [hostId, bootId, pidNamespace, processProbe] = await Promise.all([\n\t\tlinux ? readOptionalText(\"/etc/machine-id\") : undefined,\n\t\tcurrentSystemBootId(),\n\t\tlinux ? readOptionalLink(\"/proc/self/ns/pid\") : \"system\",\n\t\tprobeSystemProcess(process.pid),\n\t]);\n\tconst host = hostname();\n\treturn {\n\t\thost,\n\t\thostId: hostId ?? host,\n\t\tbootId: bootId ?? \"unverified\",\n\t\tpidNamespace: pidNamespace ?? \"unverified\",\n\t\tprocessStartToken: processProbe.startToken ?? \"unverified\",\n\t};\n}\n\nasync function classifyContender(\n\tcontender: LockContender,\n\tidentity: LockIdentity,\n\tenvironment: LockEnvironment,\n): Promise<\"stale\" | \"live\" | \"unverifiable\" | \"foreign\"> {\n\tif (contender.hostId !== identity.hostId) return \"foreign\";\n\tif (contender.bootId !== \"unverified\" && identity.bootId !== \"unverified\" && contender.bootId !== identity.bootId) {\n\t\treturn \"stale\";\n\t}\n\tif (\n\t\tcontender.pidNamespace === \"unverified\" ||\n\t\tidentity.pidNamespace === \"unverified\" ||\n\t\tcontender.pidNamespace !== identity.pidNamespace\n\t) {\n\t\treturn \"unverifiable\";\n\t}\n\tconst processProbe = await environment.probe(contender.pid);\n\tif (processProbe.status === \"missing\") return \"stale\";\n\tif (\n\t\tprocessProbe.status === \"alive\" &&\n\t\tprocessProbe.startToken !== undefined &&\n\t\tcontender.processStartToken !== \"unverified\"\n\t) {\n\t\treturn processProbe.startToken === contender.processStartToken ? \"live\" : \"stale\";\n\t}\n\treturn \"unverifiable\";\n}\n\nfunction compareContenders(left: LockContender, right: LockContender): number {\n\tif (left.nonce !== right.nonce) return left.nonce < right.nonce ? -1 : 1;\n\tif (left.ownerId === right.ownerId) return 0;\n\treturn left.ownerId < right.ownerId ? -1 : 1;\n}\n\nasync function removeOwnedContender(path: string, ownerId: string): Promise<void> {\n\ttry {\n\t\tconst value: unknown = JSON.parse(await readFile(path, \"utf8\"));\n\t\tif (typeof value === \"object\" && value !== null && (value as Record<string, unknown>).ownerId === ownerId) {\n\t\t\tawait rm(path, { force: true });\n\t\t}\n\t} catch (error) {\n\t\tif (!isNotFound(error)) throw error;\n\t}\n}\n\nasync function readOptionalText(path: string): Promise<string | undefined> {\n\ttry {\n\t\treturn (await readFile(path, \"utf8\")).trim() || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function readOptionalLink(path: string): Promise<string | undefined> {\n\ttry {\n\t\treturn await readlink(path);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function currentSystemBootId(): Promise<string | undefined> {\n\tif (process.platform === \"linux\") return readOptionalText(\"/proc/sys/kernel/random/boot_id\");\n\ttry {\n\t\tif (process.platform === \"darwin\")\n\t\t\treturn (await runSystemCommand(\"sysctl\", [\"-n\", \"kern.boottime\"])) || undefined;\n\t\tif (process.platform === \"win32\") {\n\t\t\treturn (\n\t\t\t\t(await runSystemCommand(\"powershell.exe\", [\n\t\t\t\t\t\"-NoProfile\",\n\t\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\t\"-Command\",\n\t\t\t\t\t\"(Get-CimInstance Win32_OperatingSystem).LastBootUpTime.ToUniversalTime().Ticks\",\n\t\t\t\t])) || undefined\n\t\t\t);\n\t\t}\n\t} catch {\n\t\treturn undefined;\n\t}\n\treturn undefined;\n}\n\nasync function probeSystemProcess(pid: number): Promise<ProcessProbe> {\n\tif (process.platform === \"linux\") {\n\t\ttry {\n\t\t\tconst statValue = await readFile(`/proc/${pid}/stat`, \"utf8\");\n\t\t\tconst commandEnd = statValue.lastIndexOf(\")\");\n\t\t\tif (commandEnd < 0) return { status: \"unknown\" };\n\t\t\tconst fields = statValue\n\t\t\t\t.slice(commandEnd + 1)\n\t\t\t\t.trim()\n\t\t\t\t.split(/\\s+/);\n\t\t\tconst startToken = fields[19];\n\t\t\treturn startToken ? { status: \"alive\", startToken } : { status: \"unknown\" };\n\t\t} catch (error) {\n\t\t\tif (!isNotFound(error)) return { status: \"unknown\" };\n\t\t}\n\t} else {\n\t\ttry {\n\t\t\tconst startToken =\n\t\t\t\tprocess.platform === \"darwin\"\n\t\t\t\t\t? await runSystemCommand(\"ps\", [\"-o\", \"lstart=\", \"-p\", String(pid)])\n\t\t\t\t\t: process.platform === \"win32\"\n\t\t\t\t\t\t? await runSystemCommand(\"powershell.exe\", [\n\t\t\t\t\t\t\t\t\"-NoProfile\",\n\t\t\t\t\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\t\t\t\t\"-Command\",\n\t\t\t\t\t\t\t\t`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').CreationDate.ToUniversalTime().Ticks`,\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t: \"\";\n\t\t\tif (startToken) return { status: \"alive\", startToken };\n\t\t} catch {\n\t\t\t// Fall back to existence probing when the platform command is unavailable or races with process exit.\n\t\t}\n\t}\n\treturn probeProcessExistence(pid);\n}\n\nfunction probeProcessExistence(pid: number): ProcessProbe {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn { status: \"alive\" };\n\t} catch (error) {\n\t\treturn error instanceof Error && \"code\" in error && error.code === \"ESRCH\"\n\t\t\t? { status: \"missing\" }\n\t\t\t: { status: \"unknown\" };\n\t}\n}\n\nfunction runSystemCommand(file: string, args: readonly string[]): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\texecFile(file, [...args], { encoding: \"utf8\", windowsHide: true }, (error, stdout) => {\n\t\t\tif (error) reject(error);\n\t\t\telse resolve(stdout.trim());\n\t\t});\n\t});\n}\n\nfunction validateListId(id: string): void {\n\tif (!/^[A-Za-z0-9-]{1,64}$/.test(id)) throw new TodoValidationError(`Invalid todo list id \"${id}\"`);\n}\n\nfunction isPositiveInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 1;\n}\n\nfunction isTimestamp(value: unknown): value is string {\n\treturn typeof value === \"string\" && !Number.isNaN(Date.parse(value));\n}\n\nfunction assertTask(value: unknown, listRevision: number, path: string): asserts value is TodoTask {\n\tif (typeof value !== \"object\" || value === null) throw new TodoPersistenceError(`Malformed task in ${path}`);\n\tconst task = value as Record<string, unknown>;\n\tif (\n\t\ttypeof task.id !== \"string\" ||\n\t\t!TODO_ID_PATTERN.test(task.id) ||\n\t\ttypeof task.subject !== \"string\" ||\n\t\t!task.subject.trim() ||\n\t\t(task.description !== undefined && typeof task.description !== \"string\") ||\n\t\t(task.active_form !== undefined && typeof task.active_form !== \"string\") ||\n\t\t!Array.isArray(task.depends_on) ||\n\t\t!task.depends_on.every((dependency) => typeof dependency === \"string\") ||\n\t\t(task.acceptance_criteria !== undefined &&\n\t\t\t(!Array.isArray(task.acceptance_criteria) ||\n\t\t\t\t!task.acceptance_criteria.every((criterion) => typeof criterion === \"string\" && criterion.trim()))) ||\n\t\t!TODO_STATUSES.includes(task.status as TodoStatus) ||\n\t\t!isTimestamp(task.created_at) ||\n\t\t!isTimestamp(task.updated_at) ||\n\t\t!isPositiveInteger(task.revision) ||\n\t\ttask.revision > listRevision\n\t) {\n\t\tthrow new TodoPersistenceError(`Malformed task in ${path}`);\n\t}\n\tconst hasOwner = typeof task.owner === \"string\" && task.owner.length > 0;\n\tif (task.owner !== undefined && !hasOwner) {\n\t\tthrow new TodoPersistenceError(`Malformed task ownership in ${path}`);\n\t}\n\tif ((task.status === \"in_progress\" && !hasOwner) || (task.status !== \"in_progress\" && hasOwner)) {\n\t\tthrow new TodoPersistenceError(`Inconsistent task ownership in ${path}`);\n\t}\n}\n\nfunction assertTombstone(value: unknown, listRevision: number, path: string): void {\n\tif (typeof value !== \"object\" || value === null) throw new TodoPersistenceError(`Malformed tombstone in ${path}`);\n\tconst tombstone = value as Record<string, unknown>;\n\tif (\n\t\ttypeof tombstone.id !== \"string\" ||\n\t\t!TODO_ID_PATTERN.test(tombstone.id) ||\n\t\t!isTimestamp(tombstone.deleted_at) ||\n\t\t!isPositiveInteger(tombstone.revision) ||\n\t\ttombstone.revision > listRevision\n\t) {\n\t\tthrow new TodoPersistenceError(`Malformed tombstone in ${path}`);\n\t}\n}\n\nfunction assertSnapshot(value: unknown, path: string): void {\n\tif (typeof value !== \"object\" || value === null) throw new TodoPersistenceError(`Malformed history in ${path}`);\n\tconst snapshotValue = value as Record<string, unknown>;\n\tif (\n\t\t!isPositiveInteger(snapshotValue.revision) ||\n\t\ttypeof snapshotValue.global_direction !== \"string\" ||\n\t\t!Array.isArray(snapshotValue.tasks) ||\n\t\t!Array.isArray(snapshotValue.tombstones) ||\n\t\t!isTimestamp(snapshotValue.created_at) ||\n\t\t!isTimestamp(snapshotValue.updated_at)\n\t) {\n\t\tthrow new TodoPersistenceError(`Malformed history in ${path}`);\n\t}\n\tfor (const task of snapshotValue.tasks) assertTask(task, snapshotValue.revision, path);\n\tfor (const tombstone of snapshotValue.tombstones) assertTombstone(tombstone, snapshotValue.revision, path);\n}\n"]}