@hasna/todos 0.15.24 → 0.15.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2123,7 +2123,7 @@ var package_default;
2123
2123
  var init_package = __esm(() => {
2124
2124
  package_default = {
2125
2125
  name: "@hasna/todos",
2126
- version: "0.15.24",
2126
+ version: "0.15.26",
2127
2127
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
2128
2128
  type: "module",
2129
2129
  main: "dist/index.js",
@@ -2172,7 +2172,8 @@ var init_package = __esm(() => {
2172
2172
  }
2173
2173
  },
2174
2174
  workspaces: [
2175
- "dashboard"
2175
+ "dashboard",
2176
+ "ai"
2176
2177
  ],
2177
2178
  files: [
2178
2179
  "dist",
@@ -2195,6 +2196,8 @@ var init_package = __esm(() => {
2195
2196
  "dev:serve": "bun run src/server/index.ts",
2196
2197
  "verify:release": "bun run scripts/verify-public-release.ts --mode=review",
2197
2198
  "verify:release-review": "bun run scripts/verify-npm-release-agent-review.ts",
2199
+ "verify:attested-container-candidate": "bun run scripts/attested-container-candidate.ts verify",
2200
+ "test:attested-container-candidate": "bun test scripts/attested-container-candidate.test.ts",
2198
2201
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
2199
2202
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
2200
2203
  postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
@@ -2241,12 +2244,14 @@ var init_package = __esm(() => {
2241
2244
  zod: "^3.24.2"
2242
2245
  },
2243
2246
  overrides: {
2247
+ ajv: "8.20.0",
2244
2248
  "fast-uri": "3.1.2"
2245
2249
  },
2246
2250
  devDependencies: {
2247
2251
  "@types/bun": "^1.2.4",
2248
2252
  "@types/react": "^18.3.18",
2249
2253
  "bun-types": "1.3.9",
2254
+ "hasna-deployment-contracts": "npm:@hasna/contracts@0.10.4",
2250
2255
  typescript: "^5.7.3"
2251
2256
  }
2252
2257
  };
@@ -5785,6 +5790,39 @@ async function cloudPrGroupEvents(client, groupId, options = {}) {
5785
5790
  throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} did not return a complete closed authoritative remote event page; ` + "local SQLite fallback is disabled", { cause: error });
5786
5791
  }
5787
5792
  }
5793
+ function unwrapTaskManifestEnvelope(raw, key, route) {
5794
+ if (!raw || typeof raw !== "object" || Array.isArray(raw) || !(key in raw)) {
5795
+ throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} returned a non-authoritative task-manifest response envelope; ` + "local SQLite fallback is disabled");
5796
+ }
5797
+ return raw[key];
5798
+ }
5799
+ async function cloudTaskManifestCapability(client) {
5800
+ const route = "/v1/task-manifest/capability";
5801
+ return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.get("/task-manifest/capability")), "capability", route);
5802
+ }
5803
+ async function cloudApplyTaskManifest(client, input) {
5804
+ const route = "/v1/task-manifest/apply";
5805
+ return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/apply", input)), "result", route);
5806
+ }
5807
+ async function cloudReadExactTaskManifest(client, receiptId) {
5808
+ const route = "/v1/task-manifest/read-exact";
5809
+ return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/read-exact", { receipt_id: receiptId })), "result", route);
5810
+ }
5811
+ async function cloudLookupTaskManifestBinding(client, input) {
5812
+ const route = "/v1/task-manifest/bindings/lookup";
5813
+ return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/bindings/lookup", input)), "result", route);
5814
+ }
5815
+ async function cloudCompensateTaskManifest(client, input) {
5816
+ const route = "/v1/task-manifest/compensate";
5817
+ return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/compensate", input)), "result", route);
5818
+ }
5819
+ async function cloudMarkTaskManifestOutboxDelivered(client, outboxId) {
5820
+ const route = "/v1/task-manifest/outbox/delivered";
5821
+ const delivered = unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/outbox/delivered", { outbox_id: outboxId })), "delivered", route);
5822
+ if (delivered !== true) {
5823
+ throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} did not confirm task-manifest outbox delivery; ` + "local SQLite fallback is disabled");
5824
+ }
5825
+ }
5788
5826
  function unwrapTask(raw) {
5789
5827
  if (raw && typeof raw === "object" && "task" in raw) {
5790
5828
  return raw.task;
@@ -7756,6 +7794,7 @@ var init_stage_a = __esm(() => {
7756
7794
  "sync",
7757
7795
  "tag",
7758
7796
  "task",
7797
+ "task-manifest",
7759
7798
  "template-export",
7760
7799
  "template-history",
7761
7800
  "template-import",
@@ -7881,6 +7920,7 @@ var init_stage_a = __esm(() => {
7881
7920
  "task",
7882
7921
  "task-lists",
7883
7922
  "stale-lock-handoff",
7923
+ "task-manifest",
7884
7924
  "template-export",
7885
7925
  "template-import",
7886
7926
  "template-preview",
@@ -32676,11 +32716,14 @@ function resolveAdapter(options) {
32676
32716
  const client = getTodosCloudClient(options.env);
32677
32717
  return client ? createHttpTodosAiReadAdapter(client) : createLocalTodosAiReadAdapter(options.database);
32678
32718
  }
32679
- function resolvePermission(options) {
32719
+ function resolvePermission(options, profile) {
32680
32720
  if (options.workspacePermission)
32681
32721
  return options.workspacePermission;
32682
32722
  const path = options.workspacePath ?? process.cwd();
32683
- return (permission) => checkWorkspacePermission({ path, tool: permission }).allowed;
32723
+ return (permission, tool) => {
32724
+ const check = checkWorkspacePermission({ path, tool: permission });
32725
+ return check.allowed || profile === "minimal" && tool === "get_task" && permission === "read" && check.status.matched_root === null && check.status.profile.preset === "restricted";
32726
+ };
32684
32727
  }
32685
32728
  function configuredProfileIsKnown(value) {
32686
32729
  if (value === undefined || value.trim() === "")
@@ -32692,7 +32735,7 @@ function createTodosAiToolSource(options = {}) {
32692
32735
  const adapter = resolveAdapter(options);
32693
32736
  const configuredProfile = options.env ? options.env["TODOS_PROFILE"] : process.env["TODOS_PROFILE"];
32694
32737
  const profile = options.accessProfile ?? resolveAccessProfile(configuredProfile ?? "minimal");
32695
- const permission = resolvePermission(options);
32738
+ const permission = resolvePermission(options, profile);
32696
32739
  const enabled = new Set(TODOS_AI_READ_TOOL_NAMES.filter((name) => shouldRegisterToolForProfile(name, profile) && permission(name === "get_task" ? "read" : "list", name)));
32697
32740
  const hostAllowsUpdate = (options.accessProfile !== undefined || configuredProfileIsKnown(configuredProfile)) && shouldRegisterToolForProfile("update_task", profile) && permission("write", "update_task");
32698
32741
  const approvalVerifier = options.approvalVerifier ?? adapter.verifyApproval;
@@ -46085,6 +46128,13 @@ var init_schema2 = __esm(() => {
46085
46128
  }).strict();
46086
46129
  });
46087
46130
 
46131
+ // src/task-manifest/plan-slug.ts
46132
+ function taskManifestPlanSlug(manifest, planId) {
46133
+ const base = normalizeSlug(manifest.plan.key) || normalizeSlug(manifest.plan.name) || "plan";
46134
+ return `${base}-${planId}`;
46135
+ }
46136
+ var init_plan_slug = () => {};
46137
+
46088
46138
  // src/task-manifest/backend.ts
46089
46139
  function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
46090
46140
  if (rows.length === 0) {
@@ -46113,6 +46163,67 @@ var init_backend = __esm(() => {
46113
46163
  });
46114
46164
 
46115
46165
  // src/task-manifest/reference-guard.ts
46166
+ function quoteSqliteIdentifier2(value) {
46167
+ return `"${value.replaceAll('"', '""')}"`;
46168
+ }
46169
+ function placeholders3(count) {
46170
+ return Array.from({ length: count }, () => "?").join(",");
46171
+ }
46172
+ function sqliteManagedReferencePredicate(table, field, target, managed) {
46173
+ const taskPlaceholders = placeholders3(managed.task_ids.length);
46174
+ if (table === "tasks" && field === "plan_id" && target === "plans") {
46175
+ return {
46176
+ sql: `id IN (${taskPlaceholders})`,
46177
+ values: managed.task_ids
46178
+ };
46179
+ }
46180
+ if (table === "task_dependencies" && target === "tasks" && (field === "task_id" || field === "depends_on")) {
46181
+ return {
46182
+ sql: `task_id IN (${taskPlaceholders}) AND depends_on IN (${taskPlaceholders})`,
46183
+ values: [...managed.task_ids, ...managed.task_ids]
46184
+ };
46185
+ }
46186
+ if (table === "task_comments" && field === "task_id" && target === "tasks") {
46187
+ return managed.comment_ids.length > 0 ? { sql: `id IN (${placeholders3(managed.comment_ids.length)})`, values: managed.comment_ids } : { sql: "0", values: [] };
46188
+ }
46189
+ if (table === "task_verifications" && field === "task_id" && target === "tasks") {
46190
+ return managed.verification_ids.length > 0 ? { sql: `id IN (${placeholders3(managed.verification_ids.length)})`, values: managed.verification_ids } : { sql: "0", values: [] };
46191
+ }
46192
+ if (table === "task_tags" && field === "task_id" && target === "tasks") {
46193
+ return {
46194
+ sql: `task_id IN (${taskPlaceholders})`,
46195
+ values: managed.task_ids
46196
+ };
46197
+ }
46198
+ return null;
46199
+ }
46200
+ function findSqliteTaskManifestForeignReference(db, managed) {
46201
+ const tables = db.query(`SELECT name FROM sqlite_master
46202
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`).all();
46203
+ for (const { name: table } of tables) {
46204
+ const foreignKeys = db.query(`PRAGMA foreign_key_list(${quoteSqliteIdentifier2(table)})`).all();
46205
+ for (const foreignKey of foreignKeys) {
46206
+ if (foreignKey.table !== "tasks" && foreignKey.table !== "plans")
46207
+ continue;
46208
+ const target = foreignKey.table;
46209
+ const targetIds = target === "tasks" ? managed.task_ids : [managed.plan_id];
46210
+ const owned = sqliteManagedReferencePredicate(table, foreignKey.from, target, managed);
46211
+ const sql = `SELECT 1 AS found FROM ${quoteSqliteIdentifier2(table)}
46212
+ WHERE ${quoteSqliteIdentifier2(foreignKey.from)} IN (${placeholders3(targetIds.length)})
46213
+ ${owned ? `AND NOT (${owned.sql})` : ""}
46214
+ LIMIT 1`;
46215
+ if (db.query(sql).get(...targetIds, ...owned?.values ?? [])) {
46216
+ return {
46217
+ surface: table,
46218
+ field: foreignKey.from,
46219
+ target,
46220
+ on_delete: foreignKey.on_delete.toUpperCase()
46221
+ };
46222
+ }
46223
+ }
46224
+ }
46225
+ return null;
46226
+ }
46116
46227
  function postgresTaskManifestForeignReferenceSql(tableName) {
46117
46228
  return `SELECT object_type, object_id
46118
46229
  FROM ${tableName}
@@ -46142,6 +46253,89 @@ function postgresTaskManifestForeignReferenceSql(tableName) {
46142
46253
  function sqlString(value) {
46143
46254
  return `'${value.replaceAll("'", "''")}'`;
46144
46255
  }
46256
+ function sqliteTodosTaskManifestSchemaSql() {
46257
+ return `
46258
+ CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
46259
+ receipt_id TEXT PRIMARY KEY,
46260
+ tenant_id TEXT NOT NULL,
46261
+ authority TEXT NOT NULL CHECK(authority = 'todos'),
46262
+ route TEXT NOT NULL,
46263
+ schema_version INTEGER NOT NULL CHECK(schema_version = 1),
46264
+ kind TEXT NOT NULL CHECK(kind IN ('apply', 'compensate')),
46265
+ operation_id TEXT NOT NULL,
46266
+ idempotency_key TEXT NOT NULL,
46267
+ request_digest TEXT NOT NULL,
46268
+ result_digest TEXT NOT NULL,
46269
+ binding_version INTEGER NOT NULL,
46270
+ apply_receipt_id TEXT,
46271
+ manifest_json TEXT,
46272
+ result_json TEXT NOT NULL,
46273
+ created_at TEXT NOT NULL,
46274
+ UNIQUE(kind, idempotency_key)
46275
+ );
46276
+ CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
46277
+ operation_id TEXT PRIMARY KEY,
46278
+ tenant_id TEXT NOT NULL,
46279
+ idempotency_key TEXT NOT NULL UNIQUE,
46280
+ request_digest TEXT NOT NULL,
46281
+ result_digest TEXT NOT NULL,
46282
+ apply_receipt_id TEXT NOT NULL UNIQUE REFERENCES todos_task_manifest_receipts(receipt_id),
46283
+ manifest_json TEXT NOT NULL,
46284
+ result_json TEXT NOT NULL,
46285
+ state TEXT NOT NULL CHECK(state IN ('applied', 'compensated')),
46286
+ version INTEGER NOT NULL,
46287
+ compensation_receipt_id TEXT,
46288
+ created_at TEXT NOT NULL,
46289
+ updated_at TEXT NOT NULL
46290
+ );
46291
+ CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
46292
+ id TEXT PRIMARY KEY,
46293
+ apply_receipt_id TEXT NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
46294
+ topic TEXT NOT NULL,
46295
+ payload TEXT NOT NULL,
46296
+ payload_digest TEXT NOT NULL,
46297
+ status TEXT NOT NULL CHECK(status IN ('pending', 'delivered', 'cancelled')),
46298
+ attempts INTEGER NOT NULL DEFAULT 0,
46299
+ created_at TEXT NOT NULL,
46300
+ delivered_at TEXT
46301
+ );
46302
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_outbox_receipt
46303
+ ON todos_task_manifest_outbox(apply_receipt_id, status);
46304
+ CREATE TRIGGER IF NOT EXISTS todos_task_manifest_receipts_immutable_update
46305
+ BEFORE UPDATE ON todos_task_manifest_receipts BEGIN
46306
+ SELECT RAISE(ABORT, 'todos task manifest receipts are immutable');
46307
+ END;
46308
+ CREATE TRIGGER IF NOT EXISTS todos_task_manifest_receipts_immutable_delete
46309
+ BEFORE DELETE ON todos_task_manifest_receipts BEGIN
46310
+ SELECT RAISE(ABORT, 'todos task manifest receipts are immutable');
46311
+ END;
46312
+ `;
46313
+ }
46314
+ function sqliteTableHasColumn(db, tableName, columnName) {
46315
+ const columns = db.query(`PRAGMA table_info("${tableName}")`).all();
46316
+ return columns.some((column) => column.name === columnName);
46317
+ }
46318
+ function ensureSqliteTodosTaskManifestSchema(db, tenantId) {
46319
+ db.exec(sqliteTodosTaskManifestSchemaSql());
46320
+ const tenantDefault = sqlString(tenantId);
46321
+ for (const tableName of [
46322
+ "todos_task_manifest_receipts",
46323
+ "todos_task_manifest_bindings"
46324
+ ]) {
46325
+ if (!sqliteTableHasColumn(db, tableName, "tenant_id")) {
46326
+ db.exec(`ALTER TABLE "${tableName}" ADD COLUMN tenant_id TEXT NOT NULL DEFAULT ${tenantDefault}`);
46327
+ }
46328
+ }
46329
+ db.exec(`
46330
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_receipts_tenant
46331
+ ON todos_task_manifest_receipts(tenant_id, receipt_id, kind);
46332
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_bindings_tenant_plan
46333
+ ON todos_task_manifest_bindings(
46334
+ tenant_id,
46335
+ json_extract(result_json, '$.graph.plan_id')
46336
+ );
46337
+ `);
46338
+ }
46145
46339
  function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
46146
46340
  const tenantDefault = sqlString(tenantId);
46147
46341
  return [
@@ -46218,9 +46412,411 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
46218
46412
  }
46219
46413
 
46220
46414
  // src/task-manifest/sqlite.ts
46415
+ function fault(faults, point) {
46416
+ if (faults.points.has(point))
46417
+ throw new Error(`Injected task-manifest fault at ${point}`);
46418
+ }
46419
+ function parseApplyResult(value, duplicate) {
46420
+ return { ...JSON.parse(value), duplicate };
46421
+ }
46422
+
46423
+ class SqliteTodosTaskManifestBackend {
46424
+ db;
46425
+ tenantId;
46426
+ kind = "sqlite";
46427
+ constructor(db, tenantId = "default") {
46428
+ this.db = db;
46429
+ this.tenantId = tenantId;
46430
+ ensureSqliteTodosTaskManifestSchema(db, tenantId);
46431
+ }
46432
+ async serialized(run) {
46433
+ const previous = sqliteTails.get(this.db) ?? Promise.resolve();
46434
+ let release;
46435
+ const current = new Promise((resolve17) => {
46436
+ release = resolve17;
46437
+ });
46438
+ const tail = previous.then(() => current);
46439
+ sqliteTails.set(this.db, tail);
46440
+ await previous;
46441
+ try {
46442
+ this.db.exec("BEGIN IMMEDIATE");
46443
+ try {
46444
+ const result = run();
46445
+ this.db.exec("COMMIT");
46446
+ return result;
46447
+ } catch (error) {
46448
+ this.db.exec("ROLLBACK");
46449
+ throw error;
46450
+ }
46451
+ } finally {
46452
+ release();
46453
+ if (sqliteTails.get(this.db) === tail)
46454
+ sqliteTails.delete(this.db);
46455
+ }
46456
+ }
46457
+ async apply(input, faults) {
46458
+ return this.serialized(() => {
46459
+ const { manifest } = input;
46460
+ const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = ? AND operation_id = ? LIMIT 1").get(this.tenantId, manifest.operation_id);
46461
+ if (binding) {
46462
+ if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
46463
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Operation is already bound to a different request");
46464
+ }
46465
+ if (binding["state"] !== "applied") {
46466
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "Operation was already compensated");
46467
+ }
46468
+ return parseApplyResult(String(binding["result_json"]), true);
46469
+ }
46470
+ const idempotency = this.db.query("SELECT operation_id, request_digest FROM todos_task_manifest_bindings WHERE tenant_id = ? AND idempotency_key = ? LIMIT 1").get(this.tenantId, manifest.idempotency_key);
46471
+ if (idempotency)
46472
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used by another operation");
46473
+ if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
46474
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "New manifest binding version must be 0");
46475
+ }
46476
+ if (!this.db.query("SELECT 1 AS found FROM projects WHERE id = ? LIMIT 1").get(manifest.project_id)) {
46477
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Project does not exist");
46478
+ }
46479
+ if (manifest.task_list_id && !this.db.query("SELECT 1 AS found FROM task_lists WHERE id = ? AND project_id = ? LIMIT 1").get(manifest.task_list_id, manifest.project_id)) {
46480
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Task list does not belong to the project");
46481
+ }
46482
+ const allIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids];
46483
+ for (const id of allIds) {
46484
+ for (const table of ["plans", "tasks", "task_comments", "task_verifications"]) {
46485
+ if (this.db.query(`SELECT 1 AS found FROM ${table} WHERE id = ? LIMIT 1`).get(id)) {
46486
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Deterministic id already exists: ${id}`);
46487
+ }
46488
+ }
46489
+ }
46490
+ this.db.query(`INSERT INTO plans (id, project_id, name, description, status, task_list_id, slug, created_at, updated_at)
46491
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.graph.plan_id, manifest.project_id, manifest.plan.name, manifest.plan.description ?? null, manifest.plan.status ?? "active", manifest.task_list_id ?? null, taskManifestPlanSlug(manifest, input.graph.plan_id), input.now, input.now);
46492
+ fault(faults, "after_plan_write");
46493
+ for (const task2 of manifest.tasks) {
46494
+ const taskId = input.graph.task_ids[task2.key];
46495
+ this.db.query(`INSERT INTO tasks (
46496
+ id, project_id, title, description, status, priority, assigned_to, tags, metadata,
46497
+ version, plan_id, task_list_id, created_at, updated_at, created_by
46498
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?)`).run(taskId, manifest.project_id, task2.title, task2.description ?? null, task2.status ?? "pending", task2.priority ?? "medium", task2.assigned_to ?? null, JSON.stringify(task2.tags ?? []), canonicalJson2(task2.metadata ?? {}), input.graph.plan_id, manifest.task_list_id ?? null, input.now, input.now, task2.created_by ?? null);
46499
+ for (const tag of [...new Set(task2.tags ?? [])].sort()) {
46500
+ this.db.query("INSERT INTO task_tags (task_id, tag) VALUES (?, ?)").run(taskId, tag);
46501
+ }
46502
+ }
46503
+ fault(faults, "after_task_write");
46504
+ for (const edge of manifest.dependencies ?? []) {
46505
+ this.db.query("INSERT INTO task_dependencies (task_id, depends_on) VALUES (?, ?)").run(input.graph.task_ids[edge.task], input.graph.task_ids[edge.depends_on]);
46506
+ }
46507
+ fault(faults, "after_dependency_write");
46508
+ let commentIndex = 0;
46509
+ for (const task2 of manifest.tasks)
46510
+ for (const comment2 of task2.comments ?? []) {
46511
+ this.db.query(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, created_at, type, progress_pct)
46512
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.graph.comment_ids[commentIndex++], input.graph.task_ids[task2.key], comment2.agent_id ?? null, comment2.session_id ?? null, comment2.content, input.now, comment2.type ?? "comment", comment2.progress_pct ?? null);
46513
+ }
46514
+ fault(faults, "after_comment_write");
46515
+ let verificationIndex = 0;
46516
+ for (const task2 of manifest.tasks)
46517
+ for (const verification2 of task2.verifications ?? []) {
46518
+ this.db.query(`INSERT INTO task_verifications (
46519
+ id, task_id, command, status, output_summary, artifact_path, agent_id, run_at, created_at
46520
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.graph.verification_ids[verificationIndex++], input.graph.task_ids[task2.key], verification2.command, verification2.status ?? "unknown", verification2.output_summary ?? null, verification2.artifact_path ?? null, verification2.agent_id ?? null, input.now, input.now);
46521
+ }
46522
+ fault(faults, "after_verification_write");
46523
+ const readback = this.readback(input.graph);
46524
+ const expected = {
46525
+ plans: 1,
46526
+ tasks: manifest.tasks.length,
46527
+ dependencies: manifest.dependencies?.length ?? 0,
46528
+ comments: input.graph.comment_ids.length,
46529
+ verifications: input.graph.verification_ids.length,
46530
+ complete: true
46531
+ };
46532
+ if (canonicalJson2(readback) !== canonicalJson2(expected)) {
46533
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_READBACK_MISMATCH", "Exact graph readback did not match", { expected, readback });
46534
+ }
46535
+ const receipt = {
46536
+ receipt_id: input.receipt_id,
46537
+ authority: "todos",
46538
+ route: "todos.task-manifest.v1",
46539
+ schema_version: 1,
46540
+ kind: "apply",
46541
+ operation_id: manifest.operation_id,
46542
+ idempotency_key: manifest.idempotency_key,
46543
+ request_digest: input.request_digest,
46544
+ result_digest: input.result_digest,
46545
+ binding_version: 1,
46546
+ apply_receipt_id: null,
46547
+ created_at: input.now
46548
+ };
46549
+ const result = {
46550
+ duplicate: false,
46551
+ receipt,
46552
+ graph: input.graph,
46553
+ readback,
46554
+ outbox_ids: input.outbox.map((entry2) => entry2.id),
46555
+ result_digest: input.result_digest
46556
+ };
46557
+ const resultJson2 = canonicalJson2(result);
46558
+ const manifestJson = canonicalJson2(manifest);
46559
+ this.db.query(`INSERT INTO todos_task_manifest_receipts (
46560
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
46561
+ request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
46562
+ ) VALUES (?, ?, 'todos', 'todos.task-manifest.v1', 1, 'apply', ?, ?, ?, ?, 1, NULL, ?, ?, ?)`).run(input.receipt_id, this.tenantId, manifest.operation_id, manifest.idempotency_key, input.request_digest, input.result_digest, manifestJson, resultJson2, input.now);
46563
+ for (const entry2 of input.outbox) {
46564
+ this.db.query(`INSERT INTO todos_task_manifest_outbox (
46565
+ id, apply_receipt_id, topic, payload, payload_digest, status, created_at
46566
+ ) VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(entry2.id, input.receipt_id, entry2.topic, canonicalJson2(entry2.payload), entry2.digest, input.now);
46567
+ }
46568
+ fault(faults, "after_outbox_write");
46569
+ this.db.query(`INSERT INTO todos_task_manifest_bindings (
46570
+ operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
46571
+ manifest_json, result_json, state, version, created_at, updated_at
46572
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'applied', 1, ?, ?)`).run(manifest.operation_id, this.tenantId, manifest.idempotency_key, input.request_digest, input.result_digest, input.receipt_id, manifestJson, resultJson2, input.now, input.now);
46573
+ fault(faults, "after_receipt_write");
46574
+ return result;
46575
+ });
46576
+ }
46577
+ async readExact(receiptId3) {
46578
+ const row = this.db.query("SELECT result_json FROM todos_task_manifest_receipts WHERE tenant_id = ? AND receipt_id = ? AND kind = 'apply' LIMIT 1").get(this.tenantId, receiptId3);
46579
+ if (!row)
46580
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId3}`);
46581
+ return parseApplyResult(row.result_json, false);
46582
+ }
46583
+ async lookupBindingByPlanId(planId) {
46584
+ const rows = this.db.query(`
46585
+ SELECT
46586
+ b.apply_receipt_id AS apply_receipt_id,
46587
+ b.state AS state,
46588
+ b.version AS binding_version,
46589
+ b.tenant_id AS binding_tenant_id,
46590
+ b.operation_id AS binding_operation_id,
46591
+ json_extract(b.result_json, '$.graph.plan_id') AS binding_plan_id,
46592
+ r.tenant_id AS receipt_tenant_id,
46593
+ r.authority AS receipt_authority,
46594
+ r.route AS receipt_route,
46595
+ r.schema_version AS receipt_schema_version,
46596
+ r.kind AS receipt_kind,
46597
+ r.operation_id AS receipt_operation_id,
46598
+ json_extract(r.result_json, '$.graph.plan_id') AS receipt_plan_id
46599
+ FROM todos_task_manifest_bindings b
46600
+ LEFT JOIN todos_task_manifest_receipts r
46601
+ ON r.receipt_id = b.apply_receipt_id
46602
+ AND r.tenant_id = b.tenant_id
46603
+ WHERE b.tenant_id = ?
46604
+ AND json_extract(b.result_json, '$.graph.plan_id') = ?
46605
+ LIMIT 2
46606
+ `).all(this.tenantId, planId);
46607
+ return validateTaskManifestBindingLookupRows(rows, this.tenantId, planId);
46608
+ }
46609
+ async markOutboxDelivered(outboxId, deliveredAt) {
46610
+ await this.serialized(() => {
46611
+ const result = this.db.query(`UPDATE todos_task_manifest_outbox
46612
+ SET status = 'delivered', delivered_at = ?, attempts = attempts + 1
46613
+ WHERE id = ? AND status = 'pending'
46614
+ AND EXISTS (
46615
+ SELECT 1 FROM todos_task_manifest_receipts r
46616
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
46617
+ AND r.tenant_id = ?
46618
+ AND r.authority = 'todos'
46619
+ AND r.route = 'todos.task-manifest.v1'
46620
+ AND r.schema_version = 1
46621
+ AND r.kind = 'apply'
46622
+ )`).run(deliveredAt, outboxId, this.tenantId);
46623
+ if (result.changes === 1)
46624
+ return;
46625
+ const existing = this.db.query(`SELECT o.status
46626
+ FROM todos_task_manifest_outbox o
46627
+ JOIN todos_task_manifest_receipts r
46628
+ ON r.receipt_id = o.apply_receipt_id
46629
+ WHERE r.tenant_id = ?
46630
+ AND r.authority = 'todos'
46631
+ AND r.route = 'todos.task-manifest.v1'
46632
+ AND r.schema_version = 1
46633
+ AND r.kind = 'apply'
46634
+ AND o.id = ?
46635
+ LIMIT 1`).get(this.tenantId, outboxId);
46636
+ if (existing?.status === "delivered")
46637
+ return;
46638
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
46639
+ });
46640
+ }
46641
+ async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
46642
+ return this.serialized(() => {
46643
+ const existing = this.db.query(`SELECT apply_receipt_id, request_digest, result_json
46644
+ FROM todos_task_manifest_receipts
46645
+ WHERE tenant_id = ? AND kind = 'compensate' AND idempotency_key = ?
46646
+ LIMIT 1`).get(this.tenantId, input.idempotency_key);
46647
+ if (existing) {
46648
+ if (existing.apply_receipt_id !== input.receipt_id || existing.request_digest !== requestDigest) {
46649
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation idempotency key is already used");
46650
+ }
46651
+ return { ...JSON.parse(existing.result_json), duplicate: true };
46652
+ }
46653
+ const row = this.db.query("SELECT * FROM todos_task_manifest_receipts WHERE tenant_id = ? AND receipt_id = ? AND kind = 'apply' LIMIT 1").get(this.tenantId, input.receipt_id);
46654
+ if (!row)
46655
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
46656
+ const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = ? AND operation_id = ? LIMIT 1").get(this.tenantId, String(row["operation_id"]));
46657
+ if (!binding || Number(binding["version"]) !== input.if_binding_version) {
46658
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
46659
+ }
46660
+ if (binding["state"] !== "applied")
46661
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not in applied state");
46662
+ const delivered = this.db.query(`SELECT o.id FROM todos_task_manifest_outbox o
46663
+ JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
46664
+ WHERE r.tenant_id = ? AND o.apply_receipt_id = ? AND o.status = 'delivered'
46665
+ LIMIT 1`).get(this.tenantId, input.receipt_id);
46666
+ if (delivered)
46667
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
46668
+ const applyResult = parseApplyResult(String(row["result_json"]), false);
46669
+ const manifest = JSON.parse(String(row["manifest_json"]));
46670
+ const expectedEffects = [
46671
+ {
46672
+ topic: "todos.task-manifest.applied",
46673
+ payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
46674
+ },
46675
+ ...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
46676
+ ];
46677
+ const storedOutbox = this.db.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
46678
+ FROM todos_task_manifest_outbox
46679
+ WHERE apply_receipt_id = ?
46680
+ AND EXISTS (
46681
+ SELECT 1 FROM todos_task_manifest_receipts r
46682
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
46683
+ AND r.tenant_id = ?
46684
+ )
46685
+ ORDER BY id`).all(input.receipt_id, this.tenantId);
46686
+ if (storedOutbox.length !== expectedEffects.length) {
46687
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
46688
+ }
46689
+ const outboxById = new Map(storedOutbox.map((entry2) => [String(entry2["id"]), entry2]));
46690
+ for (const [index, expectedEffect] of expectedEffects.entries()) {
46691
+ const stored = outboxById.get(applyResult.outbox_ids[index]);
46692
+ const expectedPayload = canonicalJson2(expectedEffect.payload);
46693
+ if (!stored || stored["topic"] !== expectedEffect.topic || stored["payload"] !== expectedPayload || stored["payload_digest"] !== canonicalDigest(expectedEffect) || stored["status"] !== "pending" || Number(stored["attempts"]) !== 0 || stored["delivered_at"] !== null) {
46694
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
46695
+ }
46696
+ }
46697
+ const taskIds = Object.values(applyResult.graph.task_ids);
46698
+ const placeholders4 = taskIds.map(() => "?").join(",");
46699
+ const foreignReference = findSqliteTaskManifestForeignReference(this.db, {
46700
+ plan_id: applyResult.graph.plan_id,
46701
+ task_ids: taskIds,
46702
+ dependency_ids: applyResult.graph.dependency_ids,
46703
+ comment_ids: applyResult.graph.comment_ids,
46704
+ verification_ids: applyResult.graph.verification_ids
46705
+ });
46706
+ if (foreignReference) {
46707
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: foreign reference at ${foreignReference.surface}.${foreignReference.field} would be changed by ${foreignReference.on_delete}`, { ...foreignReference });
46708
+ }
46709
+ const actualReadback = this.readback(applyResult.graph);
46710
+ if (canonicalJson2(actualReadback) !== canonicalJson2(applyResult.readback)) {
46711
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: graph changed since apply", { actualReadback });
46712
+ }
46713
+ const plan = this.db.query("SELECT project_id, name, description, status, task_list_id, slug FROM plans WHERE id = ? LIMIT 1").get(applyResult.graph.plan_id);
46714
+ if (!plan || plan["project_id"] !== manifest.project_id || plan["name"] !== manifest.plan.name || plan["description"] !== (manifest.plan.description ?? null) || plan["status"] !== (manifest.plan.status ?? "active") || plan["task_list_id"] !== (manifest.task_list_id ?? null) || plan["slug"] !== taskManifestPlanSlug(manifest, applyResult.graph.plan_id)) {
46715
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: plan changed since apply");
46716
+ }
46717
+ for (const task2 of manifest.tasks) {
46718
+ const stored = this.db.query("SELECT title, description, status, priority, assigned_to, tags, metadata, created_by FROM tasks WHERE id = ? LIMIT 1").get(applyResult.graph.task_ids[task2.key]);
46719
+ if (!stored || stored["title"] !== task2.title || stored["description"] !== (task2.description ?? null) || stored["status"] !== (task2.status ?? "pending") || stored["priority"] !== (task2.priority ?? "medium") || stored["assigned_to"] !== (task2.assigned_to ?? null) || stored["tags"] !== JSON.stringify(task2.tags ?? []) || stored["metadata"] !== canonicalJson2(task2.metadata ?? {}) || stored["created_by"] !== (task2.created_by ?? null)) {
46720
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: task ${task2.key} changed since apply`);
46721
+ }
46722
+ const storedTags = this.db.query("SELECT tag FROM task_tags WHERE task_id = ? ORDER BY tag").all(applyResult.graph.task_ids[task2.key]).map((row2) => row2.tag);
46723
+ const expectedTags = [...new Set(task2.tags ?? [])].sort();
46724
+ if (canonicalJson2(storedTags) !== canonicalJson2(expectedTags)) {
46725
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: task ${task2.key} tags changed since apply`);
46726
+ }
46727
+ }
46728
+ const storedDependencies = this.db.query(`SELECT task_id, depends_on FROM task_dependencies
46729
+ WHERE task_id IN (${placeholders4}) AND depends_on IN (${placeholders4}) ORDER BY task_id, depends_on`).all(...taskIds, ...taskIds);
46730
+ const expectedDependencies = (manifest.dependencies ?? []).map((edge) => ({
46731
+ task_id: applyResult.graph.task_ids[edge.task],
46732
+ depends_on: applyResult.graph.task_ids[edge.depends_on]
46733
+ })).sort((left, right) => left.task_id.localeCompare(right.task_id) || left.depends_on.localeCompare(right.depends_on));
46734
+ if (canonicalJson2(storedDependencies) !== canonicalJson2(expectedDependencies)) {
46735
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: dependencies changed since apply");
46736
+ }
46737
+ let expectedCommentIndex = 0;
46738
+ for (const task2 of manifest.tasks)
46739
+ for (const comment2 of task2.comments ?? []) {
46740
+ const stored = this.db.query(`SELECT task_id, agent_id, session_id, content, type, progress_pct
46741
+ FROM task_comments WHERE id = ? LIMIT 1`).get(applyResult.graph.comment_ids[expectedCommentIndex]);
46742
+ const expected = {
46743
+ task_id: applyResult.graph.task_ids[task2.key],
46744
+ agent_id: comment2.agent_id ?? null,
46745
+ session_id: comment2.session_id ?? null,
46746
+ content: comment2.content,
46747
+ type: comment2.type ?? "comment",
46748
+ progress_pct: comment2.progress_pct ?? null
46749
+ };
46750
+ if (!stored || canonicalJson2(stored) !== canonicalJson2(expected)) {
46751
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: comment changed since apply");
46752
+ }
46753
+ expectedCommentIndex += 1;
46754
+ }
46755
+ let expectedVerificationIndex = 0;
46756
+ for (const task2 of manifest.tasks)
46757
+ for (const verification2 of task2.verifications ?? []) {
46758
+ const stored = this.db.query(`SELECT task_id, command, status, output_summary, artifact_path, agent_id
46759
+ FROM task_verifications WHERE id = ? LIMIT 1`).get(applyResult.graph.verification_ids[expectedVerificationIndex]);
46760
+ const expected = {
46761
+ task_id: applyResult.graph.task_ids[task2.key],
46762
+ command: verification2.command,
46763
+ status: verification2.status ?? "unknown",
46764
+ output_summary: verification2.output_summary ?? null,
46765
+ artifact_path: verification2.artifact_path ?? null,
46766
+ agent_id: verification2.agent_id ?? null
46767
+ };
46768
+ if (!stored || canonicalJson2(stored) !== canonicalJson2(expected)) {
46769
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: verification changed since apply");
46770
+ }
46771
+ expectedVerificationIndex += 1;
46772
+ }
46773
+ this.db.query(`UPDATE todos_task_manifest_outbox
46774
+ SET status = 'cancelled'
46775
+ WHERE apply_receipt_id = ? AND status = 'pending'
46776
+ AND EXISTS (
46777
+ SELECT 1 FROM todos_task_manifest_receipts r
46778
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
46779
+ AND r.tenant_id = ?
46780
+ )`).run(input.receipt_id, this.tenantId);
46781
+ for (const table of ["task_tags", "task_dependencies", "task_comments", "task_verifications"]) {
46782
+ const column = table === "task_dependencies" ? "task_id" : "task_id";
46783
+ this.db.query(`DELETE FROM ${table} WHERE ${column} IN (${placeholders4})`).run(...taskIds);
46784
+ }
46785
+ this.db.query(`DELETE FROM tasks WHERE id IN (${placeholders4})`).run(...taskIds);
46786
+ this.db.query("DELETE FROM plans WHERE id = ?").run(applyResult.graph.plan_id);
46787
+ const readback = this.readback(applyResult.graph);
46788
+ const result = { duplicate: false, receipt, absent: true, readback };
46789
+ const resultJson2 = canonicalJson2(result);
46790
+ this.db.query(`INSERT INTO todos_task_manifest_receipts (
46791
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
46792
+ request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
46793
+ ) VALUES (?, ?, 'todos', 'todos.task-manifest.v1', 1, 'compensate', ?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(compensationReceiptId, this.tenantId, receipt.operation_id, input.idempotency_key, requestDigest, receipt.result_digest, receipt.binding_version, input.receipt_id, resultJson2, now4);
46794
+ const updated = this.db.query(`UPDATE todos_task_manifest_bindings SET state = 'compensated', version = ?, compensation_receipt_id = ?, updated_at = ?
46795
+ WHERE tenant_id = ? AND operation_id = ? AND state = 'applied' AND version = ?`).run(receipt.binding_version, compensationReceiptId, now4, this.tenantId, receipt.operation_id, input.if_binding_version);
46796
+ if (updated.changes !== 1) {
46797
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding changed during compensation");
46798
+ }
46799
+ return result;
46800
+ });
46801
+ }
46802
+ readback(graph) {
46803
+ const taskIds = Object.values(graph.task_ids);
46804
+ const placeholders4 = taskIds.map(() => "?").join(",");
46805
+ const count = (sql, ...values) => Number(this.db.query(sql).get(...values).count);
46806
+ return {
46807
+ plans: count("SELECT count(*) AS count FROM plans WHERE id = ?", graph.plan_id),
46808
+ tasks: count(`SELECT count(*) AS count FROM tasks WHERE id IN (${placeholders4})`, ...taskIds),
46809
+ dependencies: count(`SELECT count(*) AS count FROM task_dependencies WHERE task_id IN (${placeholders4}) AND depends_on IN (${placeholders4})`, ...taskIds, ...taskIds),
46810
+ comments: count(`SELECT count(*) AS count FROM task_comments WHERE id IN (${graph.comment_ids.map(() => "?").join(",") || "NULL"})`, ...graph.comment_ids),
46811
+ verifications: count(`SELECT count(*) AS count FROM task_verifications WHERE id IN (${graph.verification_ids.map(() => "?").join(",") || "NULL"})`, ...graph.verification_ids),
46812
+ complete: true
46813
+ };
46814
+ }
46815
+ }
46221
46816
  var sqliteTails;
46222
46817
  var init_sqlite2 = __esm(() => {
46223
46818
  init_canonical();
46819
+ init_plan_slug();
46224
46820
  init_backend();
46225
46821
  init_types5();
46226
46822
  sqliteTails = new WeakMap;
@@ -46239,7 +46835,7 @@ function parseJson2(value) {
46239
46835
  function timestamp2(value) {
46240
46836
  return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
46241
46837
  }
46242
- function fault(faults, point) {
46838
+ function fault2(faults, point) {
46243
46839
  if (faults.points.has(point))
46244
46840
  throw new Error(`Injected task-manifest fault at ${point}`);
46245
46841
  }
@@ -46321,7 +46917,7 @@ function taskPayload(manifest, task2, taskId, planId, now4) {
46321
46917
  function planPayload(input) {
46322
46918
  return {
46323
46919
  id: input.graph.plan_id,
46324
- slug: null,
46920
+ slug: taskManifestPlanSlug(input.manifest, input.graph.plan_id),
46325
46921
  project_id: input.manifest.project_id,
46326
46922
  task_list_id: input.manifest.task_list_id ?? null,
46327
46923
  agent_id: null,
@@ -46334,7 +46930,7 @@ function planPayload(input) {
46334
46930
  synced_at: null
46335
46931
  };
46336
46932
  }
46337
- function placeholders3(start, count) {
46933
+ function placeholders4(start, count) {
46338
46934
  return Array.from({ length: count }, (_, index) => `$${start + index}`).join(",");
46339
46935
  }
46340
46936
 
@@ -46408,15 +47004,15 @@ class PostgresTodosTaskManifestBackend {
46408
47004
  }
46409
47005
  const objectIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids, ...input.graph.dependency_ids];
46410
47006
  const conflict = await tx.query(`SELECT object_id FROM ${this.tableName}
46411
- WHERE service = $1 AND object_id IN (${placeholders3(2, objectIds.length)}) LIMIT 1`, [this.service, ...objectIds]);
47007
+ WHERE service = $1 AND object_id IN (${placeholders4(2, objectIds.length)}) LIMIT 1`, [this.service, ...objectIds]);
46412
47008
  if (conflict.rows[0])
46413
47009
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "A deterministic graph id already exists");
46414
47010
  await this.insertSync(tx, "plans", input.graph.plan_id, planPayload(input), input.now);
46415
- fault(faults, "after_plan_write");
47011
+ fault2(faults, "after_plan_write");
46416
47012
  for (const task2 of manifest.tasks) {
46417
47013
  await this.insertSync(tx, "tasks", input.graph.task_ids[task2.key], taskPayload(manifest, task2, input.graph.task_ids[task2.key], input.graph.plan_id, input.now), input.now);
46418
47014
  }
46419
- fault(faults, "after_task_write");
47015
+ fault2(faults, "after_task_write");
46420
47016
  for (const [index, edge] of (manifest.dependencies ?? []).entries()) {
46421
47017
  await this.insertSync(tx, "dependencies", input.graph.dependency_ids[index], {
46422
47018
  id: input.graph.dependency_ids[index],
@@ -46426,7 +47022,7 @@ class PostgresTodosTaskManifestBackend {
46426
47022
  updated_at: input.now
46427
47023
  }, input.now);
46428
47024
  }
46429
- fault(faults, "after_dependency_write");
47025
+ fault2(faults, "after_dependency_write");
46430
47026
  let commentIndex = 0;
46431
47027
  for (const task2 of manifest.tasks)
46432
47028
  for (const comment2 of task2.comments ?? []) {
@@ -46442,7 +47038,7 @@ class PostgresTodosTaskManifestBackend {
46442
47038
  created_at: input.now
46443
47039
  }, input.now);
46444
47040
  }
46445
- fault(faults, "after_comment_write");
47041
+ fault2(faults, "after_comment_write");
46446
47042
  let verificationIndex = 0;
46447
47043
  for (const task2 of manifest.tasks)
46448
47044
  for (const verification2 of task2.verifications ?? []) {
@@ -46460,7 +47056,7 @@ class PostgresTodosTaskManifestBackend {
46460
47056
  updated_at: input.now
46461
47057
  }, input.now);
46462
47058
  }
46463
- fault(faults, "after_verification_write");
47059
+ fault2(faults, "after_verification_write");
46464
47060
  const readback = await this.readback(tx, input.graph);
46465
47061
  const expected = {
46466
47062
  plans: 1,
@@ -46523,7 +47119,7 @@ class PostgresTodosTaskManifestBackend {
46523
47119
  input.now
46524
47120
  ]);
46525
47121
  }
46526
- fault(faults, "after_outbox_write");
47122
+ fault2(faults, "after_outbox_write");
46527
47123
  await tx.query(`INSERT INTO todos_task_manifest_bindings (
46528
47124
  operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
46529
47125
  manifest_json, result_json, state, version, created_at, updated_at
@@ -46538,7 +47134,7 @@ class PostgresTodosTaskManifestBackend {
46538
47134
  resultJson2,
46539
47135
  input.now
46540
47136
  ]);
46541
- fault(faults, "after_receipt_write");
47137
+ fault2(faults, "after_receipt_write");
46542
47138
  return result;
46543
47139
  });
46544
47140
  }
@@ -46778,7 +47374,7 @@ class PostgresTodosTaskManifestBackend {
46778
47374
  }
46779
47375
  const managedIds = [...expectedPayloads.keys()];
46780
47376
  const stored = await tx.query(`SELECT object_type, object_id, payload FROM ${this.tableName}
46781
- WHERE service = $1 AND object_id IN (${placeholders3(2, managedIds.length)})`, [this.service, ...managedIds]);
47377
+ WHERE service = $1 AND object_id IN (${placeholders4(2, managedIds.length)})`, [this.service, ...managedIds]);
46782
47378
  for (const row of stored.rows) {
46783
47379
  const expected = expectedPayloads.get(String(row["object_id"]));
46784
47380
  if (!expected || expected.type !== row["object_type"] || expected.payload !== canonicalJson2(parseJson2(row["payload"]))) {
@@ -46815,7 +47411,7 @@ class PostgresTodosTaskManifestBackend {
46815
47411
  if (!ids.length)
46816
47412
  continue;
46817
47413
  await tx.query(`DELETE FROM ${this.tableName} WHERE service = $1 AND object_type = $2
46818
- AND object_id IN (${placeholders3(3, ids.length)})`, [this.service, objectType2, ...ids]);
47414
+ AND object_id IN (${placeholders4(3, ids.length)})`, [this.service, objectType2, ...ids]);
46819
47415
  }
46820
47416
  const readback = await this.readback(tx, applyResult.graph);
46821
47417
  const result = { duplicate: false, receipt, absent: true, readback };
@@ -46855,7 +47451,7 @@ class PostgresTodosTaskManifestBackend {
46855
47451
  return 0;
46856
47452
  const result = await tx.query(`SELECT count(*) AS count FROM ${this.tableName}
46857
47453
  WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
46858
- AND object_id IN (${placeholders3(3, ids.length)})`, [this.service, objectType2, ...ids]);
47454
+ AND object_id IN (${placeholders4(3, ids.length)})`, [this.service, objectType2, ...ids]);
46859
47455
  return Number(result.rows[0]?.count ?? 0);
46860
47456
  };
46861
47457
  return {
@@ -46870,6 +47466,7 @@ class PostgresTodosTaskManifestBackend {
46870
47466
  }
46871
47467
  var init_postgres3 = __esm(() => {
46872
47468
  init_canonical();
47469
+ init_plan_slug();
46873
47470
  init_backend();
46874
47471
  init_postgres_sync();
46875
47472
  init_types5();
@@ -47072,6 +47669,13 @@ class PackageOwnedTodosTaskManifestAuthority {
47072
47669
  return result;
47073
47670
  }
47074
47671
  }
47672
+ function createSqliteTodosTaskManifestAuthority(options) {
47673
+ if (!options?.database) {
47674
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An explicit SQLite Database is required");
47675
+ }
47676
+ const tenantId = resolveTenantId(options.tenantId);
47677
+ return new PackageOwnedTodosTaskManifestAuthority(new SqliteTodosTaskManifestBackend(options.database, tenantId), { ...options, tenantId });
47678
+ }
47075
47679
  function createPostgresTodosTaskManifestAuthority(client, options = {}) {
47076
47680
  if (!client || typeof client.transaction !== "function") {
47077
47681
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An authoritative PostgreSQL transaction(callback) client is required");
@@ -60183,18 +60787,18 @@ function createSessionRecoveryHandoff(input, db) {
60183
60787
  LIMIT ?
60184
60788
  `).all(...params, limit);
60185
60789
  const taskIds = tasks.map((task2) => task2.id);
60186
- const placeholders4 = taskIds.map(() => "?").join(",");
60790
+ const placeholders5 = taskIds.map(() => "?").join(",");
60187
60791
  const files = taskIds.length ? d.query(`
60188
60792
  SELECT DISTINCT path
60189
60793
  FROM task_files
60190
- WHERE task_id IN (${placeholders4}) AND status != 'removed'
60794
+ WHERE task_id IN (${placeholders5}) AND status != 'removed'
60191
60795
  ORDER BY updated_at DESC, path
60192
60796
  LIMIT ?
60193
60797
  `).all(...taskIds, limit).map((row) => row.path) : [];
60194
60798
  const runs = taskIds.length ? d.query(`
60195
60799
  SELECT id
60196
60800
  FROM task_runs
60197
- WHERE task_id IN (${placeholders4})
60801
+ WHERE task_id IN (${placeholders5})
60198
60802
  ORDER BY started_at DESC, created_at DESC
60199
60803
  LIMIT ?
60200
60804
  `).all(...taskIds, limit).map((row) => row.id) : [];
@@ -69765,8 +70369,8 @@ function findPath(sourceId, targetId, opts, db) {
69765
70369
  let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
69766
70370
  const params = [current.id];
69767
70371
  if (opts?.relation_types && opts.relation_types.length > 0) {
69768
- const placeholders4 = opts.relation_types.map(() => "?").join(",");
69769
- sql += ` AND relation_type IN (${placeholders4})`;
70372
+ const placeholders5 = opts.relation_types.map(() => "?").join(",");
70373
+ sql += ` AND relation_type IN (${placeholders5})`;
69770
70374
  params.push(...opts.relation_types);
69771
70375
  }
69772
70376
  const edges = d.query(sql).all(...params).map(rowToEdge);
@@ -69801,8 +70405,8 @@ function getImpactAnalysis(entityId, opts, db) {
69801
70405
  let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
69802
70406
  const params = [current.id];
69803
70407
  if (opts?.relation_types && opts.relation_types.length > 0) {
69804
- const placeholders4 = opts.relation_types.map(() => "?").join(",");
69805
- sql += ` AND relation_type IN (${placeholders4})`;
70408
+ const placeholders5 = opts.relation_types.map(() => "?").join(",");
70409
+ sql += ` AND relation_type IN (${placeholders5})`;
69806
70410
  params.push(...opts.relation_types);
69807
70411
  }
69808
70412
  const edges = d.query(sql).all(...params).map(rowToEdge);
@@ -90964,6 +91568,156 @@ var init_pr_group_commands = __esm(() => {
90964
91568
  init_helpers();
90965
91569
  });
90966
91570
 
91571
+ // src/cli/commands/task-manifest-commands.ts
91572
+ var exports_task_manifest_commands = {};
91573
+ __export(exports_task_manifest_commands, {
91574
+ registerTaskManifestCommands: () => registerTaskManifestCommands
91575
+ });
91576
+ import chalk32 from "chalk";
91577
+ import { readFileSync as readFileSync26 } from "fs";
91578
+ function globalOptions11(program2) {
91579
+ const command = program2;
91580
+ return command.optsWithGlobals?.() ?? program2.opts();
91581
+ }
91582
+ function jsonRequested(program2, opts) {
91583
+ return opts.json === true || globalOptions11(program2)["json"] === true;
91584
+ }
91585
+ function parseJsonFile(path) {
91586
+ try {
91587
+ return JSON.parse(readFileSync26(path, "utf8"));
91588
+ } catch (error2) {
91589
+ throw new Error(`task-manifest input must be a readable JSON file: ${error2 instanceof Error ? error2.message : String(error2)}`);
91590
+ }
91591
+ }
91592
+ function parseInteger2(value, label, min) {
91593
+ const parsed = Number(value);
91594
+ if (!Number.isSafeInteger(parsed) || parsed < min) {
91595
+ throw new Error(`${label} must be an integer >= ${min}`);
91596
+ }
91597
+ return parsed;
91598
+ }
91599
+ async function localAuthority(tenantId) {
91600
+ return createSqliteTodosTaskManifestAuthority({
91601
+ database: getDatabase(),
91602
+ ...tenantId ? { tenantId } : {}
91603
+ });
91604
+ }
91605
+ async function currentTenantId(remote, authority, explicit) {
91606
+ if (explicit)
91607
+ return explicit;
91608
+ const capability = remote ? await cloudTaskManifestCapability(remote) : await authority.capability();
91609
+ return capability.tenant_id;
91610
+ }
91611
+ function registerTaskManifestCommands(program2) {
91612
+ const taskManifest = program2.command("task-manifest").description("Apply and inspect package-owned task-manifest safe mutations");
91613
+ taskManifest.command("capability").description("Show the current task-manifest authority capability").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (opts) => {
91614
+ try {
91615
+ const remote = getTodosCloudClient();
91616
+ const result = remote ? await cloudTaskManifestCapability(remote) : await (await localAuthority(opts.tenantId)).capability();
91617
+ if (jsonRequested(program2, opts)) {
91618
+ output({ capability: result }, true);
91619
+ return;
91620
+ }
91621
+ console.log(`${chalk32.bold(result.route)} tenant=${result.tenant_id} backend=${result.backend}`);
91622
+ } catch (error2) {
91623
+ handleError(error2);
91624
+ }
91625
+ });
91626
+ taskManifest.command("apply").description("Apply a task-manifest JSON file exactly once by operation/idempotency key").requiredOption("--file <path>", "Task manifest JSON file").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (opts) => {
91627
+ try {
91628
+ const manifest = parseJsonFile(opts.file);
91629
+ const remote = getTodosCloudClient();
91630
+ const result = remote ? await cloudApplyTaskManifest(remote, manifest) : await (await localAuthority(opts.tenantId)).apply(manifest);
91631
+ if (jsonRequested(program2, opts)) {
91632
+ output({ result }, true);
91633
+ return;
91634
+ }
91635
+ console.log(`${result.duplicate ? "duplicate" : "applied"} receipt=${result.receipt.receipt_id}`);
91636
+ console.log(`plan=${result.graph.plan_id} tasks=${Object.keys(result.graph.task_ids).length}`);
91637
+ } catch (error2) {
91638
+ handleError(error2);
91639
+ }
91640
+ });
91641
+ taskManifest.command("read-exact <receipt-id>").description("Read one immutable task-manifest apply receipt by full receipt id").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (receiptId3, opts) => {
91642
+ try {
91643
+ const remote = getTodosCloudClient();
91644
+ const result = remote ? await cloudReadExactTaskManifest(remote, receiptId3) : await (await localAuthority(opts.tenantId)).readExact(receiptId3);
91645
+ if (jsonRequested(program2, opts)) {
91646
+ output({ result }, true);
91647
+ return;
91648
+ }
91649
+ console.log(`receipt=${result.receipt.receipt_id} duplicate=${String(result.duplicate)}`);
91650
+ console.log(`readback=${JSON.stringify(result.readback)}`);
91651
+ } catch (error2) {
91652
+ handleError(error2);
91653
+ }
91654
+ });
91655
+ taskManifest.command("lookup").description("Recover one exact managed apply receipt from a full plan id").requiredOption("--plan-id <uuid>", "Full managed plan UUID").option("--tenant-id <id>", "Authority tenant id; defaults to capability tenant").option("-j, --json", "Output as JSON").action(async (opts) => {
91656
+ try {
91657
+ const remote = getTodosCloudClient();
91658
+ const authority = remote ? null : await localAuthority(opts.tenantId);
91659
+ const tenantId = await currentTenantId(remote, authority, opts.tenantId);
91660
+ const request = {
91661
+ authority: "todos",
91662
+ route: TODOS_TASK_MANIFEST_ROUTE,
91663
+ schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
91664
+ tenant_id: tenantId,
91665
+ plan_id: opts.planId,
91666
+ max_items: 1
91667
+ };
91668
+ const result = remote ? await cloudLookupTaskManifestBinding(remote, request) : await authority.lookupBinding(request);
91669
+ if (jsonRequested(program2, opts)) {
91670
+ output({ result }, true);
91671
+ return;
91672
+ }
91673
+ console.log(`plan=${result.plan_id} receipt=${result.apply_receipt_id} state=${result.state}`);
91674
+ } catch (error2) {
91675
+ handleError(error2);
91676
+ }
91677
+ });
91678
+ taskManifest.command("compensate").description("Compensate an untouched task-manifest graph with CAS protection").requiredOption("--receipt-id <uuid>", "Full apply receipt UUID").requiredOption("--idempotency-key <key>", "Stable compensation idempotency key").requiredOption("--if-binding-version <n>", "Expected current binding version").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (opts) => {
91679
+ try {
91680
+ const request = {
91681
+ receipt_id: opts.receiptId,
91682
+ idempotency_key: opts.idempotencyKey,
91683
+ if_binding_version: parseInteger2(opts.ifBindingVersion, "--if-binding-version", 1)
91684
+ };
91685
+ const remote = getTodosCloudClient();
91686
+ const result = remote ? await cloudCompensateTaskManifest(remote, request) : await (await localAuthority(opts.tenantId)).compensate(request);
91687
+ if (jsonRequested(program2, opts)) {
91688
+ output({ result }, true);
91689
+ return;
91690
+ }
91691
+ console.log(`${result.duplicate ? "duplicate" : "compensated"} receipt=${result.receipt.receipt_id}`);
91692
+ console.log(`absent=${String(result.absent)} readback=${JSON.stringify(result.readback)}`);
91693
+ } catch (error2) {
91694
+ handleError(error2);
91695
+ }
91696
+ });
91697
+ taskManifest.command("outbox-delivered <outbox-id>").description("Mark one task-manifest outbox effect delivered").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (outboxId, opts) => {
91698
+ try {
91699
+ const remote = getTodosCloudClient();
91700
+ if (remote)
91701
+ await cloudMarkTaskManifestOutboxDelivered(remote, outboxId);
91702
+ else
91703
+ await (await localAuthority(opts.tenantId)).markOutboxDelivered(outboxId);
91704
+ if (jsonRequested(program2, opts)) {
91705
+ output({ delivered: true }, true);
91706
+ return;
91707
+ }
91708
+ console.log(`delivered=${outboxId}`);
91709
+ } catch (error2) {
91710
+ handleError(error2);
91711
+ }
91712
+ });
91713
+ }
91714
+ var init_task_manifest_commands = __esm(() => {
91715
+ init_database();
91716
+ init_task_manifest();
91717
+ init_cloud_router();
91718
+ init_helpers();
91719
+ });
91720
+
90967
91721
  // src/lib/cli-help.ts
90968
91722
  function optionEntry(option) {
90969
91723
  return {
@@ -91227,7 +91981,7 @@ var exports_help_commands = {};
91227
91981
  __export(exports_help_commands, {
91228
91982
  registerHelpCommands: () => registerHelpCommands
91229
91983
  });
91230
- function globalOptions11(program2) {
91984
+ function globalOptions12(program2) {
91231
91985
  const command = program2;
91232
91986
  return command.optsWithGlobals?.() ?? program2.opts();
91233
91987
  }
@@ -91246,7 +92000,7 @@ function registerHelpCommands(program2, route = "local", remoteCapabilities = ne
91246
92000
  });
91247
92001
  program2.command("manual").description("Print the complete local CLI manual").option("--format <format>", "markdown or json", "markdown").option("-j, --json", "Output as JSON").action((opts) => {
91248
92002
  try {
91249
- const globalOpts = globalOptions11(program2);
92003
+ const globalOpts = globalOptions12(program2);
91250
92004
  const manual = createCliManual(program2, {
91251
92005
  isCommandVisible: (command) => isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities),
91252
92006
  localOnly: route === "local"
@@ -91381,6 +92135,7 @@ var [
91381
92135
  { registerStorageCommands: registerStorageCommands2 },
91382
92136
  { registerScaleHardeningCommands: registerScaleHardeningCommands2 },
91383
92137
  { registerPrGroupCommands: registerPrGroupCommands2 },
92138
+ { registerTaskManifestCommands: registerTaskManifestCommands2 },
91384
92139
  { registerHelpCommands: registerHelpCommands2 }
91385
92140
  ] = await Promise.all([
91386
92141
  Promise.resolve().then(() => (init_helpers(), exports_helpers)),
@@ -91414,6 +92169,7 @@ var [
91414
92169
  Promise.resolve().then(() => (init_storage_commands(), exports_storage_commands)),
91415
92170
  Promise.resolve().then(() => (init_scale_hardening_commands(), exports_scale_hardening_commands)),
91416
92171
  Promise.resolve().then(() => (init_pr_group_commands(), exports_pr_group_commands)),
92172
+ Promise.resolve().then(() => (init_task_manifest_commands(), exports_task_manifest_commands)),
91417
92173
  Promise.resolve().then(() => (init_help_commands(), exports_help_commands))
91418
92174
  ]);
91419
92175
  registerTaskCommands2(program2);
@@ -91446,6 +92202,7 @@ registerLocalBackupCommands2(program2);
91446
92202
  registerStorageCommands2(program2);
91447
92203
  registerScaleHardeningCommands2(program2);
91448
92204
  registerPrGroupCommands2(program2);
92205
+ registerTaskManifestCommands2(program2);
91449
92206
  await registerOptionalEventsCommands(program2);
91450
92207
  registerHelpCommands2(program2, authority.route, remoteCommandCapabilities);
91451
92208
  applyTodosCliHelpVisibility(program2, authority.route, remoteCommandCapabilities);