@hasna/todos 0.15.19 → 0.15.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/ai-tools.d.ts +80 -0
  2. package/dist/ai-tools.d.ts.map +1 -0
  3. package/dist/ai.d.ts +313 -0
  4. package/dist/ai.d.ts.map +1 -0
  5. package/dist/cli/cloud-router.d.ts +30 -2
  6. package/dist/cli/cloud-router.d.ts.map +1 -1
  7. package/dist/cli/commands/ai-commands.d.ts +3 -0
  8. package/dist/cli/commands/ai-commands.d.ts.map +1 -0
  9. package/dist/cli/commands/help-commands.d.ts +2 -1
  10. package/dist/cli/commands/help-commands.d.ts.map +1 -1
  11. package/dist/cli/commands/query-commands.d.ts.map +1 -1
  12. package/dist/cli/commands/task-commands.d.ts.map +1 -1
  13. package/dist/cli/index.js +3685 -572
  14. package/dist/cli/stage-a.d.ts +34 -12
  15. package/dist/cli/stage-a.d.ts.map +1 -1
  16. package/dist/contracts.d.ts +1 -0
  17. package/dist/contracts.d.ts.map +1 -1
  18. package/dist/contracts.js +861 -29
  19. package/dist/db/audit.d.ts +8 -0
  20. package/dist/db/audit.d.ts.map +1 -1
  21. package/dist/db/task-lifecycle.d.ts +7 -1
  22. package/dist/db/task-lifecycle.d.ts.map +1 -1
  23. package/dist/db/tasks.d.ts +1 -1
  24. package/dist/db/tasks.d.ts.map +1 -1
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3717 -1205
  28. package/dist/lib/cli-help.d.ts +3 -2
  29. package/dist/lib/cli-help.d.ts.map +1 -1
  30. package/dist/lib/config.d.ts +4 -0
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/saved-search-views.d.ts.map +1 -1
  33. package/dist/lib/stale-lock-handoff.d.ts +25 -0
  34. package/dist/lib/stale-lock-handoff.d.ts.map +1 -0
  35. package/dist/mcp/index.js +822 -101
  36. package/dist/mcp.js +3 -1
  37. package/dist/project-registration.js +4208 -3709
  38. package/dist/registry.js +826 -29
  39. package/dist/release-provenance.json +5 -5
  40. package/dist/sdk/index.js +8 -1
  41. package/dist/sdk/v1.generated.d.ts +29 -0
  42. package/dist/sdk/v1.generated.d.ts.map +1 -1
  43. package/dist/server/index.js +1155 -434
  44. package/dist/server/openapi.d.ts +189 -0
  45. package/dist/server/openapi.d.ts.map +1 -1
  46. package/dist/server/v1.d.ts.map +1 -1
  47. package/dist/storage/audit-history-import.d.ts +14 -0
  48. package/dist/storage/audit-history-import.d.ts.map +1 -0
  49. package/dist/storage/interfaces.d.ts +12 -1
  50. package/dist/storage/interfaces.d.ts.map +1 -1
  51. package/dist/storage/local-sqlite.d.ts.map +1 -1
  52. package/dist/storage/postgres-adapter.d.ts.map +1 -1
  53. package/dist/storage/sqlite-snapshot.d.ts.map +1 -1
  54. package/dist/storage.js +531 -34
  55. package/dist/task-manifest.js +30 -4
  56. package/dist/types/index.d.ts +43 -0
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/package.json +3 -1
package/dist/storage.js CHANGED
@@ -276,7 +276,7 @@ function isBlockingDependencyStatus(status) {
276
276
  function isTerminalStatus(status) {
277
277
  return status === "completed" || status === "failed" || status === "cancelled";
278
278
  }
279
- var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanRevisionConflictError, PlanNotFoundError, LockError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
279
+ var TASK_STATUSES, TASK_PRIORITIES, PLAN_STATUSES, VersionConflictError, TaskNotFoundError, TaskNotStartableError, TaskReferenceAmbiguousError, ProjectNotFoundError, ResourceConflictError, PlanRevisionConflictError, PlanNotFoundError, LockError, StaleLockHandoffError, AgentNotFoundError, IdentityAliasAmbiguousError, IdentityIdImmutableError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError, DISPATCH_STATUSES, DispatchNotFoundError;
280
280
  var init_types = __esm(() => {
281
281
  TASK_STATUSES = [
282
282
  "pending",
@@ -399,6 +399,16 @@ var init_types = __esm(() => {
399
399
  this.name = "LockError";
400
400
  }
401
401
  };
402
+ StaleLockHandoffError = class StaleLockHandoffError extends Error {
403
+ code;
404
+ details;
405
+ constructor(code, message, details = {}) {
406
+ super(message);
407
+ this.code = code;
408
+ this.details = details;
409
+ this.name = "StaleLockHandoffError";
410
+ }
411
+ };
402
412
  AgentNotFoundError = class AgentNotFoundError extends Error {
403
413
  agentId;
404
414
  static code = "AGENT_NOT_FOUND";
@@ -4721,7 +4731,7 @@ var init_database = __esm(() => {
4721
4731
  });
4722
4732
 
4723
4733
  // src/lib/config.ts
4724
- import { existsSync as existsSync4 } from "fs";
4734
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
4725
4735
  import { dirname as dirname2, join as join3 } from "path";
4726
4736
  function getConfigPath() {
4727
4737
  return join3(getTodosGlobalDir(), "config.json");
@@ -4753,6 +4763,22 @@ function saveConfig(config) {
4753
4763
  function updateConfig(patch) {
4754
4764
  return saveConfig({ ...loadConfig(), ...patch });
4755
4765
  }
4766
+ function getTodosAiConfig() {
4767
+ const configPath = getConfigPath();
4768
+ if (!existsSync4(configPath))
4769
+ return {};
4770
+ const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
4771
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4772
+ throw new Error("Todos config must be a JSON object");
4773
+ }
4774
+ const ai = parsed["ai"];
4775
+ if (ai === undefined)
4776
+ return {};
4777
+ if (ai === null || typeof ai !== "object" || Array.isArray(ai)) {
4778
+ throw new Error("Todos AI configuration must be an object");
4779
+ }
4780
+ return { ...ai };
4781
+ }
4756
4782
  function normalizeApiUrl(value) {
4757
4783
  const trimmed = value?.trim();
4758
4784
  if (!trimmed)
@@ -7083,7 +7109,7 @@ var init_shared_events = __esm(() => {
7083
7109
  });
7084
7110
 
7085
7111
  // src/lib/secret-redaction.ts
7086
- import { readFileSync as readFileSync2, existsSync as existsSync6 } from "fs";
7112
+ import { readFileSync as readFileSync3, existsSync as existsSync6 } from "fs";
7087
7113
  function registerCustomRedactor(fn) {
7088
7114
  customRedactors.push(fn);
7089
7115
  }
@@ -7150,7 +7176,7 @@ function scanAndRedactText(text, options = {}) {
7150
7176
  function scanFileForSecrets(path, options = {}) {
7151
7177
  if (!existsSync6(path))
7152
7178
  throw new Error(`File not found: ${path}`);
7153
- const content = readFileSync2(path, "utf8");
7179
+ const content = readFileSync3(path, "utf8");
7154
7180
  return scanAndRedactText(content, options);
7155
7181
  }
7156
7182
  function safeStringify(value, space) {
@@ -7503,28 +7529,55 @@ var init_activity_audit = __esm(() => {
7503
7529
  function sanitizeHistoryValue(value, context) {
7504
7530
  return value === undefined || value === null ? null : sanitizePreWriteText(String(value), context);
7505
7531
  }
7506
- function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
7532
+ function insertTaskHistory(entry, db) {
7507
7533
  const d = db || getDatabase();
7508
- const id = uuid();
7509
- const timestamp2 = now();
7510
- const machineId = currentStorageMachineId(d);
7511
- const safeOldValue = sanitizeHistoryValue(oldValue, "task_history.old_value");
7512
- const safeNewValue = sanitizeHistoryValue(newValue, "task_history.new_value");
7534
+ const safeEntry = {
7535
+ ...entry,
7536
+ field: entry.field || null,
7537
+ old_value: sanitizeHistoryValue(entry.old_value, "task_history.old_value"),
7538
+ new_value: sanitizeHistoryValue(entry.new_value, "task_history.new_value"),
7539
+ agent_id: entry.agent_id || null,
7540
+ machine_id: entry.machine_id ?? currentStorageMachineId(d)
7541
+ };
7513
7542
  d.run(`INSERT INTO task_history (id, task_id, action, field, old_value, new_value, agent_id, created_at, machine_id)
7514
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, taskId, action, field || null, safeOldValue, safeNewValue, agentId || null, timestamp2, machineId]);
7543
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
7544
+ safeEntry.id,
7545
+ safeEntry.task_id,
7546
+ safeEntry.action,
7547
+ safeEntry.field,
7548
+ safeEntry.old_value,
7549
+ safeEntry.new_value,
7550
+ safeEntry.agent_id,
7551
+ safeEntry.created_at,
7552
+ safeEntry.machine_id ?? null
7553
+ ]);
7515
7554
  try {
7516
7555
  const { logActivity: logActivity2 } = (init_activity_audit(), __toCommonJS(exports_activity_audit));
7517
7556
  logActivity2({
7518
7557
  entity_type: "task",
7519
- entity_id: taskId,
7520
- action,
7521
- field,
7522
- old_value: safeOldValue,
7523
- new_value: safeNewValue,
7524
- actor_id: agentId ?? undefined
7558
+ entity_id: safeEntry.task_id,
7559
+ action: safeEntry.action,
7560
+ field: safeEntry.field ?? undefined,
7561
+ old_value: safeEntry.old_value,
7562
+ new_value: safeEntry.new_value,
7563
+ actor_id: safeEntry.agent_id ?? undefined
7525
7564
  }, d);
7526
7565
  } catch {}
7527
- return { id, task_id: taskId, action, field: field || null, old_value: safeOldValue, new_value: safeNewValue, agent_id: agentId || null, created_at: timestamp2, machine_id: machineId };
7566
+ return safeEntry;
7567
+ }
7568
+ function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
7569
+ const d = db || getDatabase();
7570
+ return insertTaskHistory({
7571
+ id: uuid(),
7572
+ task_id: taskId,
7573
+ action,
7574
+ field: field || null,
7575
+ old_value: oldValue ?? null,
7576
+ new_value: newValue ?? null,
7577
+ agent_id: agentId || null,
7578
+ created_at: now(),
7579
+ machine_id: currentStorageMachineId(d)
7580
+ }, d);
7528
7581
  }
7529
7582
  function getTaskHistory(taskId, db) {
7530
7583
  const d = db || getDatabase();
@@ -8547,6 +8600,142 @@ var init_task_graph = __esm(() => {
8547
8600
  init_task_crud();
8548
8601
  });
8549
8602
 
8603
+ // src/lib/stale-lock-handoff.ts
8604
+ function normalizeExactTaskId(value) {
8605
+ if (typeof value !== "string" || !EXACT_TASK_UUID_RE.test(value.trim())) {
8606
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_TASK_ID", "stale-lock handoff requires one exact full task UUID", { task_id: typeof value === "string" ? value : null });
8607
+ }
8608
+ return value.trim().toLowerCase();
8609
+ }
8610
+ function requireNonEmptyString(value, field) {
8611
+ if (typeof value !== "string" || !value.trim()) {
8612
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", `${field} must be a non-empty string`, { field });
8613
+ }
8614
+ const trimmed = value.trim();
8615
+ if (field === "reason" && trimmed.length > MAX_REASON_LENGTH) {
8616
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", `reason must be at most ${MAX_REASON_LENGTH} characters`, { field, max_length: MAX_REASON_LENGTH });
8617
+ }
8618
+ return trimmed;
8619
+ }
8620
+ function requireCanonicalLockVersion(value) {
8621
+ if (typeof value !== "string" || !CANONICAL_LOCK_VERSION_RE.test(value)) {
8622
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must be the exact canonical locked_at timestamp (YYYY-MM-DDTHH:mm:ss.sssZ)", { field: "expected_lock_version" });
8623
+ }
8624
+ const parsed = Date.parse(value);
8625
+ if (value.startsWith("0000-") || Number.isNaN(parsed) || new Date(parsed).toISOString() !== value) {
8626
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "expected_lock_version must name a real canonical UTC instant", { field: "expected_lock_version" });
8627
+ }
8628
+ return value;
8629
+ }
8630
+ function requireStaleThreshold(value) {
8631
+ if (!Number.isSafeInteger(value) || Number(value) <= 0) {
8632
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "stale_after_seconds must be a positive safe integer", { field: "stale_after_seconds" });
8633
+ }
8634
+ return Number(value);
8635
+ }
8636
+ function prepareStaleLockHandoff(input, options = {}) {
8637
+ const taskId = normalizeExactTaskId(input.task_id);
8638
+ const actor = requireNonEmptyString(input.actor, "actor");
8639
+ const expectedHolder = requireNonEmptyString(input.expected_holder, "expected_holder");
8640
+ const newHolder = requireNonEmptyString(input.new_holder, "new_holder");
8641
+ const expectedLockVersion = requireCanonicalLockVersion(input.expected_lock_version);
8642
+ const staleAfterSeconds = requireStaleThreshold(input.stale_after_seconds);
8643
+ const reason = sanitizePreWriteText(requireNonEmptyString(input.reason, "reason"), "stale_lock_handoff.reason").trim();
8644
+ if (!reason) {
8645
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "reason must remain non-empty after safety filtering", { field: "reason" });
8646
+ }
8647
+ if (canonicalAgentRef(actor) !== canonicalAgentRef(newHolder)) {
8648
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_ACTOR_MISMATCH", "new_holder must match the authenticated actor", { actor, new_holder: newHolder });
8649
+ }
8650
+ const operationTimestamp = options.now ?? new Date().toISOString();
8651
+ if (!CANONICAL_LOCK_VERSION_RE.test(operationTimestamp) || new Date(Date.parse(operationTimestamp)).toISOString() !== operationTimestamp) {
8652
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_INVALID_INPUT", "operation timestamp must be a canonical UTC instant");
8653
+ }
8654
+ const staleCutoff = new Date(Date.parse(operationTimestamp) - staleAfterSeconds * 1000).toISOString();
8655
+ return {
8656
+ task_id: taskId,
8657
+ actor,
8658
+ expected_holder: expectedHolder,
8659
+ expected_lock_version: expectedLockVersion,
8660
+ stale_after_seconds: staleAfterSeconds,
8661
+ new_holder: newHolder,
8662
+ reason,
8663
+ operation_timestamp: operationTimestamp,
8664
+ stale_cutoff: staleCutoff,
8665
+ receipt_id: options.receiptId ?? crypto.randomUUID()
8666
+ };
8667
+ }
8668
+ function buildStaleLockHandoffReceipt(input) {
8669
+ return {
8670
+ schema_version: STALE_LOCK_HANDOFF_SCHEMA_VERSION,
8671
+ receipt_id: input.receipt_id,
8672
+ task_id: input.task_id,
8673
+ actor: input.actor,
8674
+ previous_holder: input.expected_holder,
8675
+ previous_lock_version: input.expected_lock_version,
8676
+ new_holder: input.new_holder,
8677
+ new_lock_version: input.operation_timestamp,
8678
+ stale_after_seconds: input.stale_after_seconds,
8679
+ stale_cutoff: input.stale_cutoff,
8680
+ reason: input.reason,
8681
+ created_at: input.operation_timestamp
8682
+ };
8683
+ }
8684
+ function staleLockHandoffHistory(receipt, machineId) {
8685
+ return {
8686
+ id: receipt.receipt_id,
8687
+ task_id: receipt.task_id,
8688
+ action: STALE_LOCK_HANDOFF_ACTION,
8689
+ field: STALE_LOCK_HANDOFF_FIELD,
8690
+ old_value: JSON.stringify({
8691
+ holder: receipt.previous_holder,
8692
+ lock_version: receipt.previous_lock_version
8693
+ }),
8694
+ new_value: JSON.stringify(receipt),
8695
+ agent_id: receipt.actor,
8696
+ created_at: receipt.created_at,
8697
+ machine_id: machineId
8698
+ };
8699
+ }
8700
+ function throwStaleLockHandoffConflict(task, input) {
8701
+ if (!task.locked_by || !task.locked_at) {
8702
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_NOT_LOCKED", `Task ${input.task_id} does not have a complete lock to hand off`, { task_id: input.task_id });
8703
+ }
8704
+ if (task.locked_at !== input.expected_lock_version) {
8705
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_VERSION_MISMATCH", `Task ${input.task_id} lock version changed`, {
8706
+ task_id: input.task_id,
8707
+ expected_lock_version: input.expected_lock_version,
8708
+ current_lock_version: task.locked_at
8709
+ });
8710
+ }
8711
+ if (task.locked_by !== input.expected_holder) {
8712
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_HOLDER_MISMATCH", `Task ${input.task_id} lock holder changed`, {
8713
+ task_id: input.task_id,
8714
+ expected_holder: input.expected_holder,
8715
+ current_holder: task.locked_by
8716
+ });
8717
+ }
8718
+ if (isTerminalStatus(task.status)) {
8719
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_TERMINAL", `Task ${input.task_id} is ${task.status} and cannot transfer a lock`, { task_id: input.task_id, status: task.status });
8720
+ }
8721
+ if (task.locked_at >= input.stale_cutoff) {
8722
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_NOT_STALE", `Task ${input.task_id} lock is not older than the supplied stale threshold`, {
8723
+ task_id: input.task_id,
8724
+ current_lock_version: task.locked_at,
8725
+ stale_cutoff: input.stale_cutoff
8726
+ });
8727
+ }
8728
+ throw new StaleLockHandoffError("STALE_LOCK_HANDOFF_CONFLICT", `Task ${input.task_id} changed during stale-lock handoff`, { task_id: input.task_id });
8729
+ }
8730
+ var STALE_LOCK_HANDOFF_SCHEMA_VERSION = "todos.stale-lock-handoff.v1", STALE_LOCK_HANDOFF_ACTION = "stale_lock_handoff", STALE_LOCK_HANDOFF_FIELD = "lock", EXACT_TASK_UUID_RE, CANONICAL_LOCK_VERSION_RE, MAX_REASON_LENGTH = 4096;
8731
+ var init_stale_lock_handoff = __esm(() => {
8732
+ init_types();
8733
+ init_creator_identity();
8734
+ init_prewrite_secrets();
8735
+ EXACT_TASK_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8736
+ CANONICAL_LOCK_VERSION_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
8737
+ });
8738
+
8550
8739
  // src/db/task-lifecycle.ts
8551
8740
  var exports_task_lifecycle = {};
8552
8741
  __export(exports_task_lifecycle, {
@@ -8555,6 +8744,7 @@ __export(exports_task_lifecycle, {
8555
8744
  startTask: () => startTask,
8556
8745
  spawnNextRecurrence: () => spawnNextRecurrence,
8557
8746
  lockTask: () => lockTask,
8747
+ handoffStaleTaskLock: () => handoffStaleTaskLock,
8558
8748
  getTasksChangedSince: () => getTasksChangedSince,
8559
8749
  getTaskLockStatus: () => getTaskLockStatus,
8560
8750
  getStaleTasks: () => getStaleTasks,
@@ -8823,6 +9013,38 @@ function unlockTask(id, agentId, db) {
8823
9013
  WHERE id = ?`, [timestamp2, id]);
8824
9014
  return true;
8825
9015
  }
9016
+ function handoffStaleTaskLock(input, db) {
9017
+ const d = db || getDatabase();
9018
+ const prepared = prepareStaleLockHandoff(input);
9019
+ const receipt = buildStaleLockHandoffReceipt(prepared);
9020
+ const history = staleLockHandoffHistory(receipt, null);
9021
+ const transfer = d.transaction(() => {
9022
+ const result = d.run(`UPDATE tasks
9023
+ SET locked_by = ?, locked_at = ?, updated_at = ?, version = version + 1
9024
+ WHERE id = ?
9025
+ AND locked_by = ?
9026
+ AND locked_at = ?
9027
+ AND julianday(locked_at) < julianday(?)
9028
+ AND status NOT IN ('completed', 'failed', 'cancelled')`, [
9029
+ prepared.new_holder,
9030
+ prepared.operation_timestamp,
9031
+ prepared.operation_timestamp,
9032
+ prepared.task_id,
9033
+ prepared.expected_holder,
9034
+ prepared.expected_lock_version,
9035
+ prepared.stale_cutoff
9036
+ ]);
9037
+ if (result.changes === 0) {
9038
+ const current = getTask(prepared.task_id, d);
9039
+ if (!current)
9040
+ throw new TaskNotFoundError(prepared.task_id);
9041
+ throwStaleLockHandoffConflict(current, prepared);
9042
+ }
9043
+ insertTaskHistory(history, d);
9044
+ });
9045
+ transfer();
9046
+ return receipt;
9047
+ }
8826
9048
  function getTaskLockStatus(id, db) {
8827
9049
  const d = db || getDatabase();
8828
9050
  const task = getTask(id, d);
@@ -9123,6 +9345,7 @@ var init_task_lifecycle = __esm(() => {
9123
9345
  init_task_crud();
9124
9346
  init_task_graph();
9125
9347
  init_prewrite_secrets();
9348
+ init_stale_lock_handoff();
9126
9349
  });
9127
9350
 
9128
9351
  // src/db/task-crud.ts
@@ -11001,7 +11224,7 @@ var init_boards = __esm(() => {
11001
11224
 
11002
11225
  // src/lib/artifact-store.ts
11003
11226
  import { createHash as createHash3 } from "crypto";
11004
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11227
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11005
11228
  import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
11006
11229
  import { tmpdir as tmpdir2 } from "os";
11007
11230
  function isInMemoryDb2(path) {
@@ -11068,7 +11291,7 @@ function storeArtifactContent(input) {
11068
11291
  const sourceStat = statSync2(sourcePath);
11069
11292
  if (!sourceStat.isFile())
11070
11293
  throw new Error(`Artifact path is not a file: ${sanitizePreWriteText(input.path, "artifact.path")}`);
11071
- const sourceBuffer = readFileSync3(sourcePath);
11294
+ const sourceBuffer = readFileSync4(sourcePath);
11072
11295
  const sourceSha = sha256(sourceBuffer);
11073
11296
  const textLike = isTextLike(sourceBuffer, input.path);
11074
11297
  let storedBuffer = sourceBuffer;
@@ -11155,7 +11378,7 @@ function verifyStoredArtifact(input) {
11155
11378
  message: "stored artifact content is missing"
11156
11379
  };
11157
11380
  }
11158
- const buffer = readFileSync3(storedPath);
11381
+ const buffer = readFileSync4(storedPath);
11159
11382
  const actualSha = sha256(buffer);
11160
11383
  const actualSize = buffer.length;
11161
11384
  const ok = actualSha === store.sha256 && actualSize === store.size_bytes;
@@ -11175,7 +11398,7 @@ function exportStoredArtifactContent(input) {
11175
11398
  const report = verifyStoredArtifact(input);
11176
11399
  if (report.status !== "ok" || !report.relative_path || !report.actual_sha256 || report.actual_size_bytes === null)
11177
11400
  return null;
11178
- const content = readFileSync3(artifactStorePath(report.relative_path));
11401
+ const content = readFileSync4(artifactStorePath(report.relative_path));
11179
11402
  return {
11180
11403
  artifact_id: input.id,
11181
11404
  sha256: report.actual_sha256,
@@ -11226,7 +11449,7 @@ function getArtifactStoreRoot(dbPath) {
11226
11449
  return join5(dirname4(resolve7(path)), "artifacts");
11227
11450
  }
11228
11451
  function computeContentHash(path) {
11229
- return sha256(readFileSync3(resolve7(path)));
11452
+ return sha256(readFileSync4(resolve7(path)));
11230
11453
  }
11231
11454
  function storeArtifactFile(input) {
11232
11455
  const sourcePath = resolve7(input.sourcePath);
@@ -11236,7 +11459,7 @@ function storeArtifactFile(input) {
11236
11459
  if (!statSync2(sourcePath).isFile()) {
11237
11460
  throw new Error(`Source path is not a file: ${input.sourcePath}`);
11238
11461
  }
11239
- const buffer = readFileSync3(sourcePath);
11462
+ const buffer = readFileSync4(sourcePath);
11240
11463
  const contentHash = sha256(buffer);
11241
11464
  const mimeType = mediaTypeFor(sourcePath, isTextLike(buffer, sourcePath));
11242
11465
  const storageMode = input.storageMode ?? "copy";
@@ -13760,6 +13983,36 @@ init_task_lists();
13760
13983
  init_tasks();
13761
13984
  init_templates();
13762
13985
  init_storage_tombstones();
13986
+
13987
+ // src/storage/audit-history-import.ts
13988
+ var AUDIT_HISTORY_DIVERGENT_REPLAY = "AUDIT_HISTORY_DIVERGENT_REPLAY";
13989
+ var AUDIT_HISTORY_TOMBSTONE_FORBIDDEN = "AUDIT_HISTORY_TOMBSTONE_FORBIDDEN";
13990
+ var AUDIT_HISTORY_FIELDS = [
13991
+ "id",
13992
+ "task_id",
13993
+ "action",
13994
+ "field",
13995
+ "old_value",
13996
+ "new_value",
13997
+ "agent_id",
13998
+ "created_at",
13999
+ "machine_id"
14000
+ ];
14001
+ function auditHistoryRowsAreFieldIdentical(left, right) {
14002
+ return AUDIT_HISTORY_FIELDS.every((field) => {
14003
+ const leftValue = field === "machine_id" ? left[field] ?? null : left[field];
14004
+ const rightValue = field === "machine_id" ? right[field] ?? null : right[field];
14005
+ return leftValue === rightValue;
14006
+ });
14007
+ }
14008
+ function divergentAuditHistoryReplayError(id) {
14009
+ return `${AUDIT_HISTORY_DIVERGENT_REPLAY}: immutable audit_history row ${id} differs from stored row`;
14010
+ }
14011
+ function forbiddenAuditHistoryTombstoneError(id) {
14012
+ return `${AUDIT_HISTORY_TOMBSTONE_FORBIDDEN}: audit_history tombstone ${id} is not allowed`;
14013
+ }
14014
+
14015
+ // src/storage/sqlite-snapshot.ts
13763
14016
  var PROJECT_COLUMNS = [
13764
14017
  "id",
13765
14018
  "name",
@@ -13960,8 +14213,11 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
13960
14213
  const existingTaskLists = d.query("SELECT id, project_id, slug FROM task_lists").all();
13961
14214
  result.errors.push(...validateSnapshotRoutingDestinationConflicts(snapshot.projects, snapshot.taskLists, existingProjects, existingTaskLists));
13962
14215
  }
14216
+ const auditImport = preflightAuditHistoryImport(d, snapshot.auditHistory, snapshot.tombstones ?? []);
14217
+ result.errors.push(...auditImport.errors);
13963
14218
  if (result.errors.length > 0)
13964
14219
  return result;
14220
+ result.skipped += auditImport.identicalReplayCount;
13965
14221
  const applyRows = (objectType, table, columns, rows, updateClockColumn, acceptEqualClock = true, afterUpsert) => {
13966
14222
  for (const row of rows) {
13967
14223
  try {
@@ -13996,10 +14252,72 @@ function importSqliteTodosStorageSnapshot(snapshot, db) {
13996
14252
  replaceTaskTags(row["id"], row["tags"].filter((tag) => typeof tag === "string"), d);
13997
14253
  }
13998
14254
  });
13999
- applyRows("audit_history", "task_history", AUDIT_COLUMNS, snapshot.auditHistory);
14255
+ insertAuditHistoryRows(d, auditImport.rowsToInsert, result);
14000
14256
  applyTombstones(d, snapshot.tombstones ?? [], result);
14001
14257
  return result;
14002
14258
  }
14259
+ function preflightAuditHistoryImport(db, rows, tombstones) {
14260
+ const errors = tombstones.filter((tombstone) => tombstone.object_type === "audit_history").map((tombstone) => forbiddenAuditHistoryTombstoneError(tombstone.object_id));
14261
+ const rowsToInsert = [];
14262
+ const seen = new Map;
14263
+ let identicalReplayCount = 0;
14264
+ for (const rawRow of rows) {
14265
+ try {
14266
+ const row = asRecord(rawRow);
14267
+ if (typeof row.id !== "string" || !row.id) {
14268
+ throw new Error("task_history row is missing id");
14269
+ }
14270
+ const prior = seen.get(row.id);
14271
+ if (prior) {
14272
+ if (auditHistoryRowsAreFieldIdentical(prior, row))
14273
+ identicalReplayCount += 1;
14274
+ else
14275
+ errors.push(divergentAuditHistoryReplayError(row.id));
14276
+ continue;
14277
+ }
14278
+ seen.set(row.id, row);
14279
+ const existing = getAuditHistoryById(db, row.id);
14280
+ if (!existing) {
14281
+ rowsToInsert.push(row);
14282
+ } else if (auditHistoryRowsAreFieldIdentical(existing, row)) {
14283
+ identicalReplayCount += 1;
14284
+ } else {
14285
+ errors.push(divergentAuditHistoryReplayError(row.id));
14286
+ }
14287
+ } catch (error) {
14288
+ errors.push(error instanceof Error ? error.message : String(error));
14289
+ }
14290
+ }
14291
+ return { rowsToInsert, identicalReplayCount, errors };
14292
+ }
14293
+ function insertAuditHistoryRows(db, rows, result) {
14294
+ for (const rawRow of rows) {
14295
+ try {
14296
+ const row = asRecord(rawRow);
14297
+ const presentColumns = AUDIT_COLUMNS.filter((column) => (column in row));
14298
+ if (!presentColumns.includes("id"))
14299
+ presentColumns.unshift("id");
14300
+ const placeholders = presentColumns.map(() => "?").join(", ");
14301
+ const values = presentColumns.map((column) => valueForColumn(column, row[column]));
14302
+ const changes = db.run(`INSERT OR IGNORE INTO task_history (${presentColumns.join(", ")}) VALUES (${placeholders})`, values).changes;
14303
+ if (changes > 0) {
14304
+ result.inserted += 1;
14305
+ continue;
14306
+ }
14307
+ const existing = getAuditHistoryById(db, String(row["id"]));
14308
+ if (existing && auditHistoryRowsAreFieldIdentical(existing, row)) {
14309
+ result.skipped += 1;
14310
+ } else {
14311
+ result.errors.push(divergentAuditHistoryReplayError(String(row["id"])));
14312
+ }
14313
+ } catch (error) {
14314
+ result.errors.push(error instanceof Error ? error.message : String(error));
14315
+ }
14316
+ }
14317
+ }
14318
+ function getAuditHistoryById(db, id) {
14319
+ return db.query(`SELECT ${AUDIT_COLUMNS.join(", ")} FROM task_history WHERE id = ? LIMIT 1`).get(id);
14320
+ }
14003
14321
  function upsertById(db, table, columns, row, updateClockColumn, acceptEqualClock = true) {
14004
14322
  const id = row["id"];
14005
14323
  if (typeof id !== "string" || !id)
@@ -14097,7 +14415,7 @@ function tableForTombstone(objectType) {
14097
14415
  return "task_templates";
14098
14416
  if (objectType === "template_tasks")
14099
14417
  return "template_tasks";
14100
- return "task_history";
14418
+ throw new Error(`unsupported storage tombstone object_type: ${String(objectType)}`);
14101
14419
  }
14102
14420
  function listRows(db, table, columns) {
14103
14421
  return db.query(`SELECT ${columns.join(", ")} FROM ${table} ORDER BY id`).all();
@@ -14229,6 +14547,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
14229
14547
  unlockTask(id, agentId, database());
14230
14548
  return true;
14231
14549
  },
14550
+ handoffStaleLock: (input) => handoffStaleTaskLock(input, database()),
14232
14551
  delete: (id) => deleteTask(id, database()),
14233
14552
  start: (id, agentId) => startTask(id, agentId, database()),
14234
14553
  complete: (id, agentId, options2) => completeTask(id, agentId, database(), options2),
@@ -14326,6 +14645,7 @@ function createLocalSqliteTodosStorageAdapter(options = {}) {
14326
14645
  init_types();
14327
14646
  import { randomUUID as randomUUID3 } from "crypto";
14328
14647
  init_creator_identity();
14648
+ init_stale_lock_handoff();
14329
14649
 
14330
14650
  // src/storage/postgres-sync.ts
14331
14651
  var DEFAULT_TODOS_POSTGRES_SYNC_TABLE = "todos_sync_records";
@@ -14733,6 +15053,32 @@ function assertSafeIdentifier(value) {
14733
15053
 
14734
15054
  // src/storage/postgres-adapter.ts
14735
15055
  init_redaction();
15056
+
15057
+ // src/task-manifest/canonical.ts
15058
+ import { createHash as createHash5 } from "crypto";
15059
+ function canonicalize2(value) {
15060
+ if (Array.isArray(value))
15061
+ return value.map(canonicalize2);
15062
+ if (value !== null && typeof value === "object") {
15063
+ return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry2]) => [key, canonicalize2(entry2)]));
15064
+ }
15065
+ return value;
15066
+ }
15067
+ function canonicalJson(value) {
15068
+ return JSON.stringify(canonicalize2(value));
15069
+ }
15070
+ function canonicalDigest(value) {
15071
+ return createHash5("sha256").update(canonicalJson(value)).digest("hex");
15072
+ }
15073
+ function deterministicUuid(namespace, ...parts) {
15074
+ const bytes = createHash5("sha256").update([namespace, ...parts].join("\x1F")).digest().subarray(0, 16);
15075
+ bytes[6] = bytes[6] & 15 | 80;
15076
+ bytes[8] = bytes[8] & 63 | 128;
15077
+ const hex = bytes.toString("hex");
15078
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
15079
+ }
15080
+
15081
+ // src/storage/postgres-adapter.ts
14736
15082
  function createPostgresTodosStorageAdapter(options) {
14737
15083
  const store = new PostgresJsonRecordStore(options);
14738
15084
  const adapter = {
@@ -14761,6 +15107,7 @@ function createPostgresTodosStorageAdapter(options) {
14761
15107
  getChangedSince: (since, filters) => getChangedSince(since, filters, store),
14762
15108
  lock: (id, agentId) => lockTask2(id, agentId, store),
14763
15109
  unlock: (id, agentId) => unlockTask2(id, agentId, store),
15110
+ handoffStaleLock: (input, context) => store.handoffStaleLock(input, context),
14764
15111
  getByFingerprint: (fingerprint) => store.getTaskByFingerprint(fingerprint)
14765
15112
  },
14766
15113
  dependencies: {
@@ -14907,6 +15254,91 @@ class PostgresJsonRecordStore {
14907
15254
  LIMIT 1`, [this.service, type, id]);
14908
15255
  return result.rows[0] ? payloadRecord2(result.rows[0].payload) : null;
14909
15256
  }
15257
+ async handoffStaleLock(input, context = {}) {
15258
+ const prepared = prepareStaleLockHandoff(input);
15259
+ const receipt = buildStaleLockHandoffReceipt(prepared);
15260
+ const history = staleLockHandoffHistory(receipt, this.machineId(context));
15261
+ await this.ensureSchema();
15262
+ const result = await this.options.client.query(`/* todos:stale-lock-handoff-atomic */ WITH
15263
+ target AS MATERIALIZED (
15264
+ SELECT payload
15265
+ FROM ${this.tableName}
15266
+ WHERE service = $1
15267
+ AND object_type = 'tasks'
15268
+ AND object_id = $2
15269
+ AND deleted_at IS NULL
15270
+ FOR UPDATE
15271
+ ),
15272
+ updated AS (
15273
+ UPDATE ${this.tableName} AS task_record
15274
+ SET payload = jsonb_set(
15275
+ jsonb_set(
15276
+ jsonb_set(
15277
+ jsonb_set(
15278
+ task_record.payload,
15279
+ '{locked_by}',
15280
+ to_jsonb($6::text),
15281
+ true
15282
+ ),
15283
+ '{locked_at}',
15284
+ to_jsonb($7::text),
15285
+ true
15286
+ ),
15287
+ '{updated_at}',
15288
+ to_jsonb($7::text),
15289
+ true
15290
+ ),
15291
+ '{version}',
15292
+ to_jsonb(COALESCE((task_record.payload->>'version')::integer, 0) + 1),
15293
+ true
15294
+ ),
15295
+ updated_at = $7::timestamptz,
15296
+ source_machine_id = $10,
15297
+ version = COALESCE(task_record.version, 0) + 1
15298
+ FROM target
15299
+ WHERE task_record.service = $1
15300
+ AND task_record.object_type = 'tasks'
15301
+ AND task_record.object_id = $2
15302
+ AND task_record.deleted_at IS NULL
15303
+ AND target.payload->>'locked_by' = $3
15304
+ AND target.payload->>'locked_at' = $4
15305
+ AND $4::timestamptz < $5::timestamptz
15306
+ AND COALESCE(target.payload->>'status', '') NOT IN ('completed', 'failed', 'cancelled')
15307
+ RETURNING task_record.payload
15308
+ ),
15309
+ audit AS (
15310
+ INSERT INTO ${this.tableName} (
15311
+ service, object_type, object_id, payload, updated_at,
15312
+ deleted_at, source_machine_id, version
15313
+ )
15314
+ SELECT $1, 'audit_history', $8, $9::jsonb, $7::timestamptz,
15315
+ NULL, $10, NULL
15316
+ FROM updated
15317
+ RETURNING payload
15318
+ )
15319
+ SELECT
15320
+ (SELECT payload FROM target) AS current_payload,
15321
+ (SELECT payload FROM updated) AS updated_payload,
15322
+ (SELECT payload FROM audit) AS audit_payload`, [
15323
+ this.service,
15324
+ prepared.task_id,
15325
+ prepared.expected_holder,
15326
+ prepared.expected_lock_version,
15327
+ prepared.stale_cutoff,
15328
+ prepared.new_holder,
15329
+ prepared.operation_timestamp,
15330
+ receipt.receipt_id,
15331
+ jsonbParam(history),
15332
+ this.machineId(context)
15333
+ ]);
15334
+ const row = result.rows[0];
15335
+ if (!row?.current_payload)
15336
+ throw new TaskNotFoundError(prepared.task_id);
15337
+ if (!row.updated_payload || !row.audit_payload) {
15338
+ throwStaleLockHandoffConflict(payloadRecord2(row.current_payload), prepared);
15339
+ }
15340
+ return receipt;
15341
+ }
14910
15342
  async list(type) {
14911
15343
  return (await this.listRecords(type)).map((record) => record.payload);
14912
15344
  }
@@ -15176,6 +15608,28 @@ class PostgresJsonRecordStore {
15176
15608
  }
15177
15609
  return value;
15178
15610
  }
15611
+ async insertImmutableAuditHistory(value, context = {}) {
15612
+ await this.ensureSchema();
15613
+ const inserted = await this.options.client.query(`INSERT INTO ${this.tableName} (
15614
+ service, object_type, object_id, payload, updated_at,
15615
+ deleted_at, source_machine_id, version
15616
+ ) VALUES ($1, $2, $3, $4::jsonb, $5::timestamptz, NULL, $6, NULL)
15617
+ ON CONFLICT (service, object_type, object_id) DO NOTHING
15618
+ RETURNING object_id`, [
15619
+ this.service,
15620
+ "audit_history",
15621
+ value.id,
15622
+ jsonbParam(value),
15623
+ value.created_at,
15624
+ context.requestId ?? this.sourceMachineId ?? null
15625
+ ]);
15626
+ if (inserted.rows.length > 0)
15627
+ return "inserted";
15628
+ const existing = await this.get("audit_history", value.id);
15629
+ if (existing && auditHistoryRowsAreFieldIdentical(existing, value))
15630
+ return "identical";
15631
+ throw new Error(divergentAuditHistoryReplayError(value.id));
15632
+ }
15179
15633
  async upsertTaskWithPlanMembershipGuard(value, guardedPlanIds, explicitProject, context = {}) {
15180
15634
  const planIds = [...new Set(guardedPlanIds.filter(Boolean))].sort();
15181
15635
  if (planIds.length === 0)
@@ -16291,16 +16745,17 @@ async function findCommit(sha, store) {
16291
16745
  async function addGitRef(input, store, context) {
16292
16746
  if (!await store.get("tasks", input.task_id))
16293
16747
  throw new Error(`Task not found: ${input.task_id}`);
16748
+ const existing = (await store.list("refs")).filter((ref) => ref.task_id === input.task_id && ref.ref_type === input.ref_type && ref.name === input.name).sort((left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id))[0];
16294
16749
  const timestamp2 = new Date().toISOString();
16295
16750
  const gitRef = {
16296
- id: randomUUID3(),
16751
+ id: existing?.id ?? deterministicUuid("todos:git-ref:v1", input.task_id, input.ref_type, input.name),
16297
16752
  task_id: input.task_id,
16298
16753
  ref_type: input.ref_type,
16299
16754
  name: input.name,
16300
- url: input.url ?? null,
16301
- provider: input.provider ?? null,
16755
+ url: input.url ?? existing?.url ?? null,
16756
+ provider: input.provider ?? existing?.provider ?? null,
16302
16757
  metadata: input.metadata ?? {},
16303
- created_at: timestamp2,
16758
+ created_at: existing?.created_at ?? timestamp2,
16304
16759
  updated_at: timestamp2
16305
16760
  };
16306
16761
  await store.upsert("refs", gitRef, context);
@@ -16667,6 +17122,11 @@ async function importSnapshot(snapshot, store, context) {
16667
17122
  result.errors.push(...validateSnapshotRoutingDestinationConflicts(snapshot.projects, snapshot.taskLists, existingProjects, existingTaskLists));
16668
17123
  if (result.errors.length > 0)
16669
17124
  return result;
17125
+ const auditHistory = await preflightAuditHistoryImport2(snapshot.auditHistory, snapshot.tombstones ?? [], store);
17126
+ result.errors.push(...auditHistory.errors);
17127
+ if (result.errors.length > 0)
17128
+ return result;
17129
+ result.skipped += auditHistory.identical;
16670
17130
  const entries = [
16671
17131
  ...snapshot.tasks.map((row) => ["tasks", row]),
16672
17132
  ...snapshot.projects.map((row) => ["projects", row]),
@@ -16675,9 +17135,20 @@ async function importSnapshot(snapshot, store, context) {
16675
17135
  ...snapshot.agents.map((row) => ["agents", row]),
16676
17136
  ...snapshot.taskLists.map((row) => ["task_lists", row]),
16677
17137
  ...snapshot.templates.map((row) => ["templates", row]),
16678
- ...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row]),
16679
- ...snapshot.auditHistory.map((row) => ["audit_history", row])
17138
+ ...(snapshot.templateTasks ?? []).map((row) => ["template_tasks", row])
16680
17139
  ];
17140
+ for (const row of auditHistory.rowsToInsert) {
17141
+ try {
17142
+ const outcome = await store.insertImmutableAuditHistory(row, context);
17143
+ if (outcome === "inserted")
17144
+ result.inserted += 1;
17145
+ else
17146
+ result.skipped += 1;
17147
+ } catch (error) {
17148
+ result.errors.push(error instanceof Error ? error.message : String(error));
17149
+ return result;
17150
+ }
17151
+ }
16681
17152
  for (const [type, row] of entries) {
16682
17153
  try {
16683
17154
  const existing = await store.get(type, row.id);
@@ -16711,6 +17182,32 @@ async function importSnapshot(snapshot, store, context) {
16711
17182
  }
16712
17183
  return result;
16713
17184
  }
17185
+ async function preflightAuditHistoryImport2(rows, tombstones, store) {
17186
+ const errors = tombstones.filter((tombstone) => tombstone.object_type === "audit_history").map((tombstone) => forbiddenAuditHistoryTombstoneError(tombstone.object_id));
17187
+ const rowsToInsert = [];
17188
+ const seen = new Map;
17189
+ let identical = 0;
17190
+ for (const row of rows) {
17191
+ const prior = seen.get(row.id);
17192
+ if (prior) {
17193
+ if (auditHistoryRowsAreFieldIdentical(prior, row))
17194
+ identical += 1;
17195
+ else
17196
+ errors.push(divergentAuditHistoryReplayError(row.id));
17197
+ continue;
17198
+ }
17199
+ seen.set(row.id, row);
17200
+ const existing = await store.get("audit_history", row.id);
17201
+ if (!existing) {
17202
+ rowsToInsert.push(row);
17203
+ } else if (auditHistoryRowsAreFieldIdentical(existing, row)) {
17204
+ identical += 1;
17205
+ } else {
17206
+ errors.push(divergentAuditHistoryReplayError(row.id));
17207
+ }
17208
+ }
17209
+ return { rowsToInsert, identical, errors };
17210
+ }
16714
17211
  async function requireRecord(type, id, store) {
16715
17212
  const record = await store.get(type, id);
16716
17213
  if (!record)
@@ -17769,7 +18266,7 @@ function isCommentRedactionBackfillComplete(result) {
17769
18266
  return !result.dry_run && result.conflicts === 0 && result.remaining_candidates === 0;
17770
18267
  }
17771
18268
  // src/storage/s3-artifacts.ts
17772
- import { createHash as createHash5, createHmac as createHmac2 } from "crypto";
18269
+ import { createHash as createHash6, createHmac as createHmac2 } from "crypto";
17773
18270
  function createTodosS3ArtifactStore(options) {
17774
18271
  const requestFetch = options.fetch ?? fetch;
17775
18272
  const now3 = options.now ?? (() => new Date);
@@ -17941,7 +18438,7 @@ function toAmzDate(date) {
17941
18438
  return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
17942
18439
  }
17943
18440
  function sha256Hex(value) {
17944
- return createHash5("sha256").update(value).digest("hex");
18441
+ return createHash6("sha256").update(value).digest("hex");
17945
18442
  }
17946
18443
  function hmac(key, value) {
17947
18444
  return createHmac2("sha256", key).update(value).digest();