@hasna/todos 0.15.14 → 0.15.15

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 (41) hide show
  1. package/dist/cli/cloud-router.d.ts.map +1 -1
  2. package/dist/cli/index.js +12103 -10728
  3. package/dist/contracts.js +1 -1
  4. package/dist/index.js +276 -41
  5. package/dist/mcp/index.js +2170 -798
  6. package/dist/mcp.js +1 -1
  7. package/dist/project-registration.js +1 -1
  8. package/dist/registry.js +1 -1
  9. package/dist/release-provenance.json +5 -5
  10. package/dist/sdk/index.d.ts +1 -1
  11. package/dist/sdk/index.d.ts.map +1 -1
  12. package/dist/sdk/index.js +7 -0
  13. package/dist/sdk/v1.generated.d.ts +23 -0
  14. package/dist/sdk/v1.generated.d.ts.map +1 -1
  15. package/dist/server/cloud.d.ts +3 -0
  16. package/dist/server/cloud.d.ts.map +1 -1
  17. package/dist/server/index.js +19650 -18278
  18. package/dist/server/openapi.d.ts +134 -0
  19. package/dist/server/openapi.d.ts.map +1 -1
  20. package/dist/server/v1.d.ts +2 -1
  21. package/dist/server/v1.d.ts.map +1 -1
  22. package/dist/task-manifest/authority.d.ts +3 -1
  23. package/dist/task-manifest/authority.d.ts.map +1 -1
  24. package/dist/task-manifest/backend.d.ts +18 -1
  25. package/dist/task-manifest/backend.d.ts.map +1 -1
  26. package/dist/task-manifest/http.d.ts +2 -1
  27. package/dist/task-manifest/http.d.ts.map +1 -1
  28. package/dist/task-manifest/index.d.ts +1 -1
  29. package/dist/task-manifest/index.d.ts.map +1 -1
  30. package/dist/task-manifest/postgres.d.ts +3 -1
  31. package/dist/task-manifest/postgres.d.ts.map +1 -1
  32. package/dist/task-manifest/schema-sql.d.ts +3 -1
  33. package/dist/task-manifest/schema-sql.d.ts.map +1 -1
  34. package/dist/task-manifest/schema.d.ts +2 -1
  35. package/dist/task-manifest/schema.d.ts.map +1 -1
  36. package/dist/task-manifest/sqlite.d.ts +4 -2
  37. package/dist/task-manifest/sqlite.d.ts.map +1 -1
  38. package/dist/task-manifest/types.d.ts +22 -1
  39. package/dist/task-manifest/types.d.ts.map +1 -1
  40. package/dist/task-manifest.js +275 -40
  41. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12492,7 +12492,7 @@ var init_dispatches = __esm(() => {
12492
12492
  // package.json
12493
12493
  var package_default = {
12494
12494
  name: "@hasna/todos",
12495
- version: "0.15.14",
12495
+ version: "0.15.15",
12496
12496
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
12497
12497
  type: "module",
12498
12498
  main: "dist/index.js",
@@ -39177,6 +39177,14 @@ var compensationSchema = exports_external.object({
39177
39177
  idempotency_key: identifier,
39178
39178
  if_binding_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
39179
39179
  }).strict();
39180
+ var bindingLookupSchema = exports_external.object({
39181
+ authority: exports_external.string().min(1).max(64),
39182
+ route: exports_external.string().min(1).max(128),
39183
+ schema_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
39184
+ tenant_id: identifier,
39185
+ plan_id: uuid2,
39186
+ max_items: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
39187
+ }).strict();
39180
39188
  function parseTodosTaskManifest(input) {
39181
39189
  const parsed = schema.safeParse(input);
39182
39190
  if (!parsed.success) {
@@ -39242,6 +39250,36 @@ function parseTodosTaskManifestCompensation(input) {
39242
39250
  }
39243
39251
  return parsed.data;
39244
39252
  }
39253
+ function parseTodosTaskManifestBindingLookup(input) {
39254
+ const parsed = bindingLookupSchema.safeParse(input);
39255
+ if (!parsed.success) {
39256
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", `Invalid task-manifest binding lookup: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`, { issues: parsed.error.issues });
39257
+ }
39258
+ return parsed.data;
39259
+ }
39260
+
39261
+ // src/task-manifest/backend.ts
39262
+ var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
39263
+ function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
39264
+ if (rows.length === 0) {
39265
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BINDING_NOT_FOUND", `Managed task-manifest binding not found for plan: ${planId}`, { plan_id: planId });
39266
+ }
39267
+ if (rows.length !== 1) {
39268
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest plan lookup matched more than one binding", { plan_id: planId, matched_items: rows.length, max_items: 1 });
39269
+ }
39270
+ const row = rows[0];
39271
+ const bindingVersion = Number(row.binding_version);
39272
+ const state = row.state;
39273
+ if (row.binding_tenant_id !== tenantId || row.receipt_tenant_id !== tenantId || row.binding_plan_id !== planId || row.receipt_plan_id !== planId || row.receipt_authority !== "todos" || row.receipt_route !== "todos.task-manifest.v1" || Number(row.receipt_schema_version) !== 1 || row.receipt_kind !== "apply" || row.binding_operation_id !== row.receipt_operation_id || typeof row.apply_receipt_id !== "string" || !UUID_PATTERN2.test(row.apply_receipt_id) || !Number.isSafeInteger(bindingVersion) || bindingVersion < 1 || state !== "applied" && state !== "compensated") {
39274
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest binding and immutable apply receipt disagree", { plan_id: planId });
39275
+ }
39276
+ return {
39277
+ plan_id: planId,
39278
+ apply_receipt_id: row.apply_receipt_id,
39279
+ binding_version: bindingVersion,
39280
+ state
39281
+ };
39282
+ }
39245
39283
 
39246
39284
  // src/task-manifest/reference-guard.ts
39247
39285
  function quoteSqliteIdentifier2(value) {
@@ -39331,10 +39369,14 @@ function postgresTaskManifestForeignReferenceSql(tableName) {
39331
39369
  }
39332
39370
 
39333
39371
  // src/task-manifest/schema-sql.ts
39372
+ function sqlString(value) {
39373
+ return `'${value.replaceAll("'", "''")}'`;
39374
+ }
39334
39375
  function sqliteTodosTaskManifestSchemaSql() {
39335
39376
  return `
39336
39377
  CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
39337
39378
  receipt_id TEXT PRIMARY KEY,
39379
+ tenant_id TEXT NOT NULL,
39338
39380
  authority TEXT NOT NULL CHECK(authority = 'todos'),
39339
39381
  route TEXT NOT NULL,
39340
39382
  schema_version INTEGER NOT NULL CHECK(schema_version = 1),
@@ -39352,6 +39394,7 @@ function sqliteTodosTaskManifestSchemaSql() {
39352
39394
  );
39353
39395
  CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
39354
39396
  operation_id TEXT PRIMARY KEY,
39397
+ tenant_id TEXT NOT NULL,
39355
39398
  idempotency_key TEXT NOT NULL UNIQUE,
39356
39399
  request_digest TEXT NOT NULL,
39357
39400
  result_digest TEXT NOT NULL,
@@ -39387,10 +39430,37 @@ function sqliteTodosTaskManifestSchemaSql() {
39387
39430
  END;
39388
39431
  `;
39389
39432
  }
39390
- function postgresTodosTaskManifestSchemaSql() {
39433
+ function sqliteTableHasColumn(db, tableName, columnName) {
39434
+ const columns = db.query(`PRAGMA table_info("${tableName}")`).all();
39435
+ return columns.some((column) => column.name === columnName);
39436
+ }
39437
+ function ensureSqliteTodosTaskManifestSchema(db, tenantId) {
39438
+ db.exec(sqliteTodosTaskManifestSchemaSql());
39439
+ const tenantDefault = sqlString(tenantId);
39440
+ for (const tableName of [
39441
+ "todos_task_manifest_receipts",
39442
+ "todos_task_manifest_bindings"
39443
+ ]) {
39444
+ if (!sqliteTableHasColumn(db, tableName, "tenant_id")) {
39445
+ db.exec(`ALTER TABLE "${tableName}" ADD COLUMN tenant_id TEXT NOT NULL DEFAULT ${tenantDefault}`);
39446
+ }
39447
+ }
39448
+ db.exec(`
39449
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_receipts_tenant
39450
+ ON todos_task_manifest_receipts(tenant_id, receipt_id, kind);
39451
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_bindings_tenant_plan
39452
+ ON todos_task_manifest_bindings(
39453
+ tenant_id,
39454
+ json_extract(result_json, '$.graph.plan_id')
39455
+ );
39456
+ `);
39457
+ }
39458
+ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
39459
+ const tenantDefault = sqlString(tenantId);
39391
39460
  return [
39392
39461
  `CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
39393
39462
  receipt_id text PRIMARY KEY,
39463
+ tenant_id text NOT NULL,
39394
39464
  authority text NOT NULL CHECK(authority = 'todos'),
39395
39465
  route text NOT NULL,
39396
39466
  schema_version integer NOT NULL CHECK(schema_version = 1),
@@ -39406,8 +39476,13 @@ function postgresTodosTaskManifestSchemaSql() {
39406
39476
  created_at timestamptz NOT NULL,
39407
39477
  UNIQUE(kind, idempotency_key)
39408
39478
  )`,
39479
+ `ALTER TABLE todos_task_manifest_receipts
39480
+ ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
39481
+ `ALTER TABLE todos_task_manifest_receipts
39482
+ ALTER COLUMN tenant_id DROP DEFAULT`,
39409
39483
  `CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
39410
39484
  operation_id text PRIMARY KEY,
39485
+ tenant_id text NOT NULL,
39411
39486
  idempotency_key text NOT NULL UNIQUE,
39412
39487
  request_digest text NOT NULL,
39413
39488
  result_digest text NOT NULL,
@@ -39420,6 +39495,10 @@ function postgresTodosTaskManifestSchemaSql() {
39420
39495
  created_at timestamptz NOT NULL,
39421
39496
  updated_at timestamptz NOT NULL
39422
39497
  )`,
39498
+ `ALTER TABLE todos_task_manifest_bindings
39499
+ ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
39500
+ `ALTER TABLE todos_task_manifest_bindings
39501
+ ALTER COLUMN tenant_id DROP DEFAULT`,
39423
39502
  `CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
39424
39503
  id text PRIMARY KEY,
39425
39504
  apply_receipt_id text NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
@@ -39433,6 +39512,13 @@ function postgresTodosTaskManifestSchemaSql() {
39433
39512
  )`,
39434
39513
  `CREATE INDEX IF NOT EXISTS todos_task_manifest_outbox_receipt_idx
39435
39514
  ON todos_task_manifest_outbox(apply_receipt_id, status)`,
39515
+ `CREATE INDEX IF NOT EXISTS todos_task_manifest_receipts_tenant_idx
39516
+ ON todos_task_manifest_receipts(tenant_id, receipt_id, kind)`,
39517
+ `CREATE INDEX IF NOT EXISTS todos_task_manifest_bindings_tenant_plan_idx
39518
+ ON todos_task_manifest_bindings(
39519
+ tenant_id,
39520
+ ((result_json #>> '{graph,plan_id}'))
39521
+ )`,
39436
39522
  `CREATE OR REPLACE FUNCTION todos_task_manifest_receipts_immutable()
39437
39523
  RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
39438
39524
  RAISE EXCEPTION 'todos task manifest receipts are immutable';
@@ -39456,10 +39542,12 @@ function parseApplyResult(value, duplicate) {
39456
39542
 
39457
39543
  class SqliteTodosTaskManifestBackend {
39458
39544
  db;
39545
+ tenantId;
39459
39546
  kind = "sqlite";
39460
- constructor(db) {
39547
+ constructor(db, tenantId = "default") {
39461
39548
  this.db = db;
39462
- db.exec(sqliteTodosTaskManifestSchemaSql());
39549
+ this.tenantId = tenantId;
39550
+ ensureSqliteTodosTaskManifestSchema(db, tenantId);
39463
39551
  }
39464
39552
  async serialized(run) {
39465
39553
  const previous = sqliteTails.get(this.db) ?? Promise.resolve();
@@ -39489,7 +39577,7 @@ class SqliteTodosTaskManifestBackend {
39489
39577
  async apply(input, faults) {
39490
39578
  return this.serialized(() => {
39491
39579
  const { manifest } = input;
39492
- const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = ? LIMIT 1").get(manifest.operation_id);
39580
+ 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);
39493
39581
  if (binding) {
39494
39582
  if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
39495
39583
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Operation is already bound to a different request");
@@ -39499,7 +39587,7 @@ class SqliteTodosTaskManifestBackend {
39499
39587
  }
39500
39588
  return parseApplyResult(String(binding["result_json"]), true);
39501
39589
  }
39502
- const idempotency = this.db.query("SELECT operation_id, request_digest FROM todos_task_manifest_bindings WHERE idempotency_key = ? LIMIT 1").get(manifest.idempotency_key);
39590
+ 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);
39503
39591
  if (idempotency)
39504
39592
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used by another operation");
39505
39593
  if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
@@ -39589,9 +39677,9 @@ class SqliteTodosTaskManifestBackend {
39589
39677
  const resultJson = canonicalJson(result);
39590
39678
  const manifestJson = canonicalJson(manifest);
39591
39679
  this.db.query(`INSERT INTO todos_task_manifest_receipts (
39592
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
39680
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
39593
39681
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
39594
- ) VALUES (?, 'todos', 'todos.task-manifest.v1', 1, 'apply', ?, ?, ?, ?, 1, NULL, ?, ?, ?)`).run(input.receipt_id, manifest.operation_id, manifest.idempotency_key, input.request_digest, input.result_digest, manifestJson, resultJson, input.now);
39682
+ ) 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, resultJson, input.now);
39595
39683
  for (const entry2 of input.outbox) {
39596
39684
  this.db.query(`INSERT INTO todos_task_manifest_outbox (
39597
39685
  id, apply_receipt_id, topic, payload, payload_digest, status, created_at
@@ -39599,24 +39687,55 @@ class SqliteTodosTaskManifestBackend {
39599
39687
  }
39600
39688
  fault(faults, "after_outbox_write");
39601
39689
  this.db.query(`INSERT INTO todos_task_manifest_bindings (
39602
- operation_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
39690
+ operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
39603
39691
  manifest_json, result_json, state, version, created_at, updated_at
39604
- ) VALUES (?, ?, ?, ?, ?, ?, ?, 'applied', 1, ?, ?)`).run(manifest.operation_id, manifest.idempotency_key, input.request_digest, input.result_digest, input.receipt_id, manifestJson, resultJson, input.now, input.now);
39692
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'applied', 1, ?, ?)`).run(manifest.operation_id, this.tenantId, manifest.idempotency_key, input.request_digest, input.result_digest, input.receipt_id, manifestJson, resultJson, input.now, input.now);
39605
39693
  fault(faults, "after_receipt_write");
39606
39694
  return result;
39607
39695
  });
39608
39696
  }
39609
39697
  async readExact(receiptId2) {
39610
- const row = this.db.query("SELECT result_json FROM todos_task_manifest_receipts WHERE receipt_id = ? AND kind = 'apply' LIMIT 1").get(receiptId2);
39698
+ 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, receiptId2);
39611
39699
  if (!row)
39612
39700
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId2}`);
39613
39701
  return parseApplyResult(row.result_json, false);
39614
39702
  }
39703
+ async lookupBindingByPlanId(planId) {
39704
+ const rows = this.db.query(`
39705
+ SELECT
39706
+ b.apply_receipt_id AS apply_receipt_id,
39707
+ b.state AS state,
39708
+ b.version AS binding_version,
39709
+ b.tenant_id AS binding_tenant_id,
39710
+ b.operation_id AS binding_operation_id,
39711
+ json_extract(b.result_json, '$.graph.plan_id') AS binding_plan_id,
39712
+ r.tenant_id AS receipt_tenant_id,
39713
+ r.authority AS receipt_authority,
39714
+ r.route AS receipt_route,
39715
+ r.schema_version AS receipt_schema_version,
39716
+ r.kind AS receipt_kind,
39717
+ r.operation_id AS receipt_operation_id,
39718
+ json_extract(r.result_json, '$.graph.plan_id') AS receipt_plan_id
39719
+ FROM todos_task_manifest_bindings b
39720
+ LEFT JOIN todos_task_manifest_receipts r
39721
+ ON r.receipt_id = b.apply_receipt_id
39722
+ AND r.tenant_id = b.tenant_id
39723
+ WHERE b.tenant_id = ?
39724
+ AND json_extract(b.result_json, '$.graph.plan_id') = ?
39725
+ LIMIT 2
39726
+ `).all(this.tenantId, planId);
39727
+ return validateTaskManifestBindingLookupRows(rows, this.tenantId, planId);
39728
+ }
39615
39729
  async markOutboxDelivered(outboxId, deliveredAt) {
39616
39730
  await this.serialized(() => {
39617
39731
  const result = this.db.query(`UPDATE todos_task_manifest_outbox
39618
39732
  SET status = 'delivered', delivered_at = ?, attempts = attempts + 1
39619
- WHERE id = ? AND status = 'pending'`).run(deliveredAt, outboxId);
39733
+ WHERE id = ? AND status = 'pending'
39734
+ AND EXISTS (
39735
+ SELECT 1 FROM todos_task_manifest_receipts r
39736
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
39737
+ AND r.tenant_id = ?
39738
+ )`).run(deliveredAt, outboxId, this.tenantId);
39620
39739
  if (result.changes !== 1) {
39621
39740
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
39622
39741
  }
@@ -39625,23 +39744,28 @@ class SqliteTodosTaskManifestBackend {
39625
39744
  async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
39626
39745
  return this.serialized(() => {
39627
39746
  const existing = this.db.query(`SELECT apply_receipt_id, request_digest, result_json
39628
- FROM todos_task_manifest_receipts WHERE kind = 'compensate' AND idempotency_key = ? LIMIT 1`).get(input.idempotency_key);
39747
+ FROM todos_task_manifest_receipts
39748
+ WHERE tenant_id = ? AND kind = 'compensate' AND idempotency_key = ?
39749
+ LIMIT 1`).get(this.tenantId, input.idempotency_key);
39629
39750
  if (existing) {
39630
39751
  if (existing.apply_receipt_id !== input.receipt_id || existing.request_digest !== requestDigest) {
39631
39752
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation idempotency key is already used");
39632
39753
  }
39633
39754
  return { ...JSON.parse(existing.result_json), duplicate: true };
39634
39755
  }
39635
- const row = this.db.query("SELECT * FROM todos_task_manifest_receipts WHERE receipt_id = ? AND kind = 'apply' LIMIT 1").get(input.receipt_id);
39756
+ 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);
39636
39757
  if (!row)
39637
39758
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
39638
- const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = ? LIMIT 1").get(String(row["operation_id"]));
39759
+ 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"]));
39639
39760
  if (!binding || Number(binding["version"]) !== input.if_binding_version) {
39640
39761
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
39641
39762
  }
39642
39763
  if (binding["state"] !== "applied")
39643
39764
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not in applied state");
39644
- const delivered = this.db.query("SELECT id FROM todos_task_manifest_outbox WHERE apply_receipt_id = ? AND status = 'delivered' LIMIT 1").get(input.receipt_id);
39765
+ const delivered = this.db.query(`SELECT o.id FROM todos_task_manifest_outbox o
39766
+ JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
39767
+ WHERE r.tenant_id = ? AND o.apply_receipt_id = ? AND o.status = 'delivered'
39768
+ LIMIT 1`).get(this.tenantId, input.receipt_id);
39645
39769
  if (delivered)
39646
39770
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
39647
39771
  const applyResult = parseApplyResult(String(row["result_json"]), false);
@@ -39654,7 +39778,14 @@ class SqliteTodosTaskManifestBackend {
39654
39778
  ...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
39655
39779
  ];
39656
39780
  const storedOutbox = this.db.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
39657
- FROM todos_task_manifest_outbox WHERE apply_receipt_id = ? ORDER BY id`).all(input.receipt_id);
39781
+ FROM todos_task_manifest_outbox
39782
+ WHERE apply_receipt_id = ?
39783
+ AND EXISTS (
39784
+ SELECT 1 FROM todos_task_manifest_receipts r
39785
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
39786
+ AND r.tenant_id = ?
39787
+ )
39788
+ ORDER BY id`).all(input.receipt_id, this.tenantId);
39658
39789
  if (storedOutbox.length !== expectedEffects.length) {
39659
39790
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
39660
39791
  }
@@ -39742,7 +39873,14 @@ class SqliteTodosTaskManifestBackend {
39742
39873
  }
39743
39874
  expectedVerificationIndex += 1;
39744
39875
  }
39745
- this.db.query("UPDATE todos_task_manifest_outbox SET status = 'cancelled' WHERE apply_receipt_id = ? AND status = 'pending'").run(input.receipt_id);
39876
+ this.db.query(`UPDATE todos_task_manifest_outbox
39877
+ SET status = 'cancelled'
39878
+ WHERE apply_receipt_id = ? AND status = 'pending'
39879
+ AND EXISTS (
39880
+ SELECT 1 FROM todos_task_manifest_receipts r
39881
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
39882
+ AND r.tenant_id = ?
39883
+ )`).run(input.receipt_id, this.tenantId);
39746
39884
  for (const table of ["task_tags", "task_dependencies", "task_comments", "task_verifications"]) {
39747
39885
  const column = table === "task_dependencies" ? "task_id" : "task_id";
39748
39886
  this.db.query(`DELETE FROM ${table} WHERE ${column} IN (${placeholders2})`).run(...taskIds);
@@ -39753,11 +39891,11 @@ class SqliteTodosTaskManifestBackend {
39753
39891
  const result = { duplicate: false, receipt, absent: true, readback };
39754
39892
  const resultJson = canonicalJson(result);
39755
39893
  this.db.query(`INSERT INTO todos_task_manifest_receipts (
39756
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
39894
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
39757
39895
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
39758
- ) VALUES (?, 'todos', 'todos.task-manifest.v1', 1, 'compensate', ?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(compensationReceiptId, receipt.operation_id, input.idempotency_key, requestDigest, receipt.result_digest, receipt.binding_version, input.receipt_id, resultJson, now4);
39896
+ ) 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, resultJson, now4);
39759
39897
  const updated = this.db.query(`UPDATE todos_task_manifest_bindings SET state = 'compensated', version = ?, compensation_receipt_id = ?, updated_at = ?
39760
- WHERE operation_id = ? AND state = 'applied' AND version = ?`).run(receipt.binding_version, compensationReceiptId, now4, receipt.operation_id, input.if_binding_version);
39898
+ 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);
39761
39899
  if (updated.changes !== 1) {
39762
39900
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding changed during compensation");
39763
39901
  }
@@ -39896,17 +40034,19 @@ class PostgresTodosTaskManifestBackend {
39896
40034
  kind = "postgresql";
39897
40035
  service;
39898
40036
  tableName;
40037
+ tenantId;
39899
40038
  schemaReady = null;
39900
40039
  constructor(client, options = {}) {
39901
40040
  this.client = client;
39902
40041
  this.service = options.service ?? "todos";
39903
40042
  this.tableName = safeIdentifier2(options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE, "tableName");
40043
+ this.tenantId = options.tenantId ?? "default";
39904
40044
  }
39905
40045
  async ensureSchema() {
39906
40046
  this.schemaReady ??= (async () => {
39907
40047
  for (const sql of postgresTodosSyncSchemaSql(this.tableName))
39908
40048
  await this.client.query(sql);
39909
- for (const sql of postgresTodosTaskManifestSchemaSql())
40049
+ for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
39910
40050
  await this.client.query(sql);
39911
40051
  })();
39912
40052
  await this.schemaReady;
@@ -39928,7 +40068,7 @@ class PostgresTodosTaskManifestBackend {
39928
40068
  const { manifest } = input;
39929
40069
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${manifest.operation_id}`]);
39930
40070
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fidempotency\x1F${manifest.idempotency_key}`]);
39931
- const existing = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = $1 LIMIT 1 FOR UPDATE", [manifest.operation_id]);
40071
+ const existing = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND operation_id = $2 LIMIT 1 FOR UPDATE", [this.tenantId, manifest.operation_id]);
39932
40072
  if (existing.rows[0]) {
39933
40073
  const binding = existing.rows[0];
39934
40074
  if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
@@ -39939,7 +40079,7 @@ class PostgresTodosTaskManifestBackend {
39939
40079
  }
39940
40080
  return { ...parseJson2(binding["result_json"]), duplicate: true };
39941
40081
  }
39942
- const reused = await tx.query("SELECT operation_id FROM todos_task_manifest_bindings WHERE idempotency_key = $1 LIMIT 1", [manifest.idempotency_key]);
40082
+ const reused = await tx.query("SELECT operation_id FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND idempotency_key = $2 LIMIT 1", [this.tenantId, manifest.idempotency_key]);
39943
40083
  if (reused.rows[0])
39944
40084
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used");
39945
40085
  if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
@@ -40049,10 +40189,11 @@ class PostgresTodosTaskManifestBackend {
40049
40189
  const manifestJson = canonicalJson(manifest);
40050
40190
  const resultJson = canonicalJson(result);
40051
40191
  await tx.query(`INSERT INTO todos_task_manifest_receipts (
40052
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
40192
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
40053
40193
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
40054
- ) VALUES ($1, 'todos', 'todos.task-manifest.v1', 1, 'apply', $2, $3, $4, $5, 1, NULL, $6::jsonb, $7::jsonb, $8)`, [
40194
+ ) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'apply', $3, $4, $5, $6, 1, NULL, $7::jsonb, $8::jsonb, $9)`, [
40055
40195
  input.receipt_id,
40196
+ this.tenantId,
40056
40197
  manifest.operation_id,
40057
40198
  manifest.idempotency_key,
40058
40199
  input.request_digest,
@@ -40075,10 +40216,11 @@ class PostgresTodosTaskManifestBackend {
40075
40216
  }
40076
40217
  fault2(faults, "after_outbox_write");
40077
40218
  await tx.query(`INSERT INTO todos_task_manifest_bindings (
40078
- operation_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
40219
+ operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
40079
40220
  manifest_json, result_json, state, version, created_at, updated_at
40080
- ) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, 'applied', 1, $8, $8)`, [
40221
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, 'applied', 1, $9, $9)`, [
40081
40222
  manifest.operation_id,
40223
+ this.tenantId,
40082
40224
  manifest.idempotency_key,
40083
40225
  input.request_digest,
40084
40226
  input.result_digest,
@@ -40093,17 +40235,50 @@ class PostgresTodosTaskManifestBackend {
40093
40235
  }
40094
40236
  async readExact(receiptId2) {
40095
40237
  await this.ensureSchema();
40096
- const result = await this.client.query("SELECT result_json FROM todos_task_manifest_receipts WHERE receipt_id = $1 AND kind = 'apply' LIMIT 1", [receiptId2]);
40238
+ const result = await this.client.query("SELECT result_json FROM todos_task_manifest_receipts WHERE tenant_id = $1 AND receipt_id = $2 AND kind = 'apply' LIMIT 1", [this.tenantId, receiptId2]);
40097
40239
  if (!result.rows[0])
40098
40240
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId2}`);
40099
40241
  return { ...parseJson2(result.rows[0]["result_json"]), duplicate: false };
40100
40242
  }
40243
+ async lookupBindingByPlanId(planId) {
40244
+ await this.ensureSchema();
40245
+ const result = await this.client.query(`
40246
+ SELECT
40247
+ b.apply_receipt_id AS apply_receipt_id,
40248
+ b.state AS state,
40249
+ b.version AS binding_version,
40250
+ b.tenant_id AS binding_tenant_id,
40251
+ b.operation_id AS binding_operation_id,
40252
+ b.result_json #>> '{graph,plan_id}' AS binding_plan_id,
40253
+ r.tenant_id AS receipt_tenant_id,
40254
+ r.authority AS receipt_authority,
40255
+ r.route AS receipt_route,
40256
+ r.schema_version AS receipt_schema_version,
40257
+ r.kind AS receipt_kind,
40258
+ r.operation_id AS receipt_operation_id,
40259
+ r.result_json #>> '{graph,plan_id}' AS receipt_plan_id
40260
+ FROM todos_task_manifest_bindings b
40261
+ LEFT JOIN todos_task_manifest_receipts r
40262
+ ON r.receipt_id = b.apply_receipt_id
40263
+ AND r.tenant_id = b.tenant_id
40264
+ WHERE b.tenant_id = $1
40265
+ AND b.result_json #>> '{graph,plan_id}' = $2
40266
+ LIMIT 2
40267
+ `, [this.tenantId, planId]);
40268
+ return validateTaskManifestBindingLookupRows(result.rows, this.tenantId, planId);
40269
+ }
40101
40270
  async markOutboxDelivered(outboxId, deliveredAt) {
40102
40271
  await this.ensureSchema();
40103
40272
  await this.client.transaction(async (tx) => {
40104
40273
  const result = await tx.query(`UPDATE todos_task_manifest_outbox
40105
40274
  SET status = 'delivered', delivered_at = $1, attempts = attempts + 1
40106
- WHERE id = $2 AND status = 'pending' RETURNING id`, [deliveredAt, outboxId]);
40275
+ WHERE id = $2 AND status = 'pending'
40276
+ AND EXISTS (
40277
+ SELECT 1 FROM todos_task_manifest_receipts r
40278
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
40279
+ AND r.tenant_id = $3
40280
+ )
40281
+ RETURNING id`, [deliveredAt, outboxId, this.tenantId]);
40107
40282
  if (!result.rows[0])
40108
40283
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
40109
40284
  });
@@ -40113,7 +40288,10 @@ class PostgresTodosTaskManifestBackend {
40113
40288
  return this.client.transaction(async (tx) => {
40114
40289
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${receipt.operation_id}`]);
40115
40290
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fcompensation-idempotency\x1F${input.idempotency_key}`]);
40116
- const previous = await tx.query("SELECT apply_receipt_id, request_digest, result_json FROM todos_task_manifest_receipts WHERE kind = 'compensate' AND idempotency_key = $1 LIMIT 1", [input.idempotency_key]);
40291
+ const previous = await tx.query(`SELECT apply_receipt_id, request_digest, result_json
40292
+ FROM todos_task_manifest_receipts
40293
+ WHERE tenant_id = $1 AND kind = 'compensate' AND idempotency_key = $2
40294
+ LIMIT 1`, [this.tenantId, input.idempotency_key]);
40117
40295
  if (previous.rows[0]) {
40118
40296
  const row = previous.rows[0];
40119
40297
  if (row["apply_receipt_id"] !== input.receipt_id || row["request_digest"] !== requestDigest) {
@@ -40121,18 +40299,21 @@ class PostgresTodosTaskManifestBackend {
40121
40299
  }
40122
40300
  return { ...parseJson2(row["result_json"]), duplicate: true };
40123
40301
  }
40124
- const applyRows = await tx.query("SELECT * FROM todos_task_manifest_receipts WHERE receipt_id = $1 AND kind = 'apply' LIMIT 1", [input.receipt_id]);
40302
+ const applyRows = await tx.query("SELECT * FROM todos_task_manifest_receipts WHERE tenant_id = $1 AND receipt_id = $2 AND kind = 'apply' LIMIT 1", [this.tenantId, input.receipt_id]);
40125
40303
  const applyRow = applyRows.rows[0];
40126
40304
  if (!applyRow)
40127
40305
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
40128
- const bindingRows = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = $1 LIMIT 1 FOR UPDATE", [receipt.operation_id]);
40306
+ const bindingRows = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND operation_id = $2 LIMIT 1 FOR UPDATE", [this.tenantId, receipt.operation_id]);
40129
40307
  const binding = bindingRows.rows[0];
40130
40308
  if (!binding || Number(binding["version"]) !== input.if_binding_version) {
40131
40309
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
40132
40310
  }
40133
40311
  if (binding["state"] !== "applied")
40134
40312
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
40135
- const delivered = await tx.query("SELECT id FROM todos_task_manifest_outbox WHERE apply_receipt_id = $1 AND status = 'delivered' LIMIT 1", [input.receipt_id]);
40313
+ const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
40314
+ JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
40315
+ WHERE r.tenant_id = $1 AND o.apply_receipt_id = $2 AND o.status = 'delivered'
40316
+ LIMIT 1`, [this.tenantId, input.receipt_id]);
40136
40317
  if (delivered.rows[0])
40137
40318
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
40138
40319
  const applyResult = parseJson2(applyRow["result_json"]);
@@ -40145,7 +40326,14 @@ class PostgresTodosTaskManifestBackend {
40145
40326
  ...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
40146
40327
  ];
40147
40328
  const outboxRows = await tx.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
40148
- FROM todos_task_manifest_outbox WHERE apply_receipt_id = $1 ORDER BY id`, [input.receipt_id]);
40329
+ FROM todos_task_manifest_outbox
40330
+ WHERE apply_receipt_id = $1
40331
+ AND EXISTS (
40332
+ SELECT 1 FROM todos_task_manifest_receipts r
40333
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
40334
+ AND r.tenant_id = $2
40335
+ )
40336
+ ORDER BY id`, [input.receipt_id, this.tenantId]);
40149
40337
  if (outboxRows.rows.length !== expectedEffects.length) {
40150
40338
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
40151
40339
  }
@@ -40245,7 +40433,14 @@ class PostgresTodosTaskManifestBackend {
40245
40433
  }
40246
40434
  if (stored.rows.length !== managedIds.length)
40247
40435
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph is incomplete");
40248
- await tx.query("UPDATE todos_task_manifest_outbox SET status = 'cancelled' WHERE apply_receipt_id = $1 AND status = 'pending'", [input.receipt_id]);
40436
+ await tx.query(`UPDATE todos_task_manifest_outbox
40437
+ SET status = 'cancelled'
40438
+ WHERE apply_receipt_id = $1 AND status = 'pending'
40439
+ AND EXISTS (
40440
+ SELECT 1 FROM todos_task_manifest_receipts r
40441
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
40442
+ AND r.tenant_id = $2
40443
+ )`, [input.receipt_id, this.tenantId]);
40249
40444
  const typedIds = [
40250
40445
  ["dependencies", applyResult.graph.dependency_ids],
40251
40446
  ["comments", applyResult.graph.comment_ids],
@@ -40262,10 +40457,11 @@ class PostgresTodosTaskManifestBackend {
40262
40457
  const readback = await this.readback(tx, applyResult.graph);
40263
40458
  const result = { duplicate: false, receipt, absent: true, readback };
40264
40459
  await tx.query(`INSERT INTO todos_task_manifest_receipts (
40265
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
40460
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
40266
40461
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
40267
- ) VALUES ($1, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $2, $3, $4, $5, $6, $7, NULL, $8::jsonb, $9)`, [
40462
+ ) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10)`, [
40268
40463
  compensationReceiptId,
40464
+ this.tenantId,
40269
40465
  receipt.operation_id,
40270
40466
  input.idempotency_key,
40271
40467
  requestDigest,
@@ -40277,10 +40473,11 @@ class PostgresTodosTaskManifestBackend {
40277
40473
  ]);
40278
40474
  const updated = await tx.query(`UPDATE todos_task_manifest_bindings
40279
40475
  SET state = 'compensated', version = $1, compensation_receipt_id = $2, updated_at = $3
40280
- WHERE operation_id = $4 AND state = 'applied' AND version = $5 RETURNING operation_id`, [
40476
+ WHERE tenant_id = $4 AND operation_id = $5 AND state = 'applied' AND version = $6 RETURNING operation_id`, [
40281
40477
  receipt.binding_version,
40282
40478
  compensationReceiptId,
40283
40479
  now4,
40480
+ this.tenantId,
40284
40481
  receipt.operation_id,
40285
40482
  input.if_binding_version
40286
40483
  ]);
@@ -40320,6 +40517,13 @@ var FAULT_POINTS = [
40320
40517
  "after_outbox_write",
40321
40518
  "after_receipt_write"
40322
40519
  ];
40520
+ function resolveTenantId(value) {
40521
+ const tenantId = value ?? "default";
40522
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenantId)) {
40523
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
40524
+ }
40525
+ return tenantId;
40526
+ }
40323
40527
  function normalize(input, now4) {
40324
40528
  const parsed = parseTodosTaskManifest(input);
40325
40529
  const requestBytes = Buffer.byteLength(canonicalJson(parsed), "utf8");
@@ -40407,15 +40611,18 @@ function sanitizeManifest(manifest) {
40407
40611
  class PackageOwnedTodosTaskManifestAuthority {
40408
40612
  backend;
40409
40613
  options;
40614
+ tenantId;
40410
40615
  constructor(backend, options = {}) {
40411
40616
  this.backend = backend;
40412
40617
  this.options = options;
40618
+ this.tenantId = resolveTenantId(options.tenantId);
40413
40619
  }
40414
40620
  async capability() {
40415
40621
  return {
40416
40622
  authority: "todos",
40417
40623
  route: TODOS_TASK_MANIFEST_ROUTE,
40418
40624
  schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
40625
+ tenant_id: this.tenantId,
40419
40626
  backend: this.backend.kind,
40420
40627
  deterministic_ids: true,
40421
40628
  immutable_receipts: true,
@@ -40454,6 +40661,22 @@ class PackageOwnedTodosTaskManifestAuthority {
40454
40661
  }
40455
40662
  return this.backend.readExact(receiptId2).then((result) => this.bounded(result));
40456
40663
  }
40664
+ async lookupBinding(input) {
40665
+ const request = parseTodosTaskManifestBindingLookup(input);
40666
+ if (request.max_items !== 1) {
40667
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", "max_items must be exactly 1 for task-manifest binding lookup", { max_items: request.max_items, max_items_limit: 1 });
40668
+ }
40669
+ if (request.authority !== "todos" || request.route !== TODOS_TASK_MANIFEST_ROUTE || request.schema_version !== TODOS_TASK_MANIFEST_SCHEMA_VERSION || request.tenant_id !== this.tenantId) {
40670
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAPABILITY_MISMATCH", "Task-manifest binding lookup does not match this authority identity");
40671
+ }
40672
+ return this.bounded({
40673
+ authority: "todos",
40674
+ route: TODOS_TASK_MANIFEST_ROUTE,
40675
+ schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
40676
+ tenant_id: this.tenantId,
40677
+ ...await this.backend.lookupBindingByPlanId(request.plan_id)
40678
+ });
40679
+ }
40457
40680
  markOutboxDelivered(outboxId) {
40458
40681
  if (!outboxId || outboxId.length > 200) {
40459
40682
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "outboxId must be a bounded exact identifier");
@@ -40493,13 +40716,15 @@ function createSqliteTodosTaskManifestAuthority(options) {
40493
40716
  if (!options?.database) {
40494
40717
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An explicit SQLite Database is required");
40495
40718
  }
40496
- return new PackageOwnedTodosTaskManifestAuthority(new SqliteTodosTaskManifestBackend(options.database), options);
40719
+ const tenantId = resolveTenantId(options.tenantId);
40720
+ return new PackageOwnedTodosTaskManifestAuthority(new SqliteTodosTaskManifestBackend(options.database, tenantId), { ...options, tenantId });
40497
40721
  }
40498
40722
  function createPostgresTodosTaskManifestAuthority(client, options = {}) {
40499
40723
  if (!client || typeof client.transaction !== "function") {
40500
40724
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An authoritative PostgreSQL transaction(callback) client is required");
40501
40725
  }
40502
- return new PackageOwnedTodosTaskManifestAuthority(new PostgresTodosTaskManifestBackend(client, options), options);
40726
+ const tenantId = resolveTenantId(options.tenantId);
40727
+ return new PackageOwnedTodosTaskManifestAuthority(new PostgresTodosTaskManifestBackend(client, { ...options, tenantId }), { ...options, tenantId });
40503
40728
  }
40504
40729
  // src/task-manifest/http.ts
40505
40730
  var JSON_HEADERS2 = { "Content-Type": "application/json" };
@@ -40513,6 +40738,7 @@ function status(error) {
40513
40738
  case "TODOS_TASK_MANIFEST_FOREIGN_REFERENCE":
40514
40739
  return 400;
40515
40740
  case "TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND":
40741
+ case "TODOS_TASK_MANIFEST_BINDING_NOT_FOUND":
40516
40742
  return 404;
40517
40743
  case "TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE":
40518
40744
  return 503;
@@ -40575,6 +40801,11 @@ async function handleTodosTaskManifestHttpRequest(request, url, authority, baseP
40575
40801
  }
40576
40802
  return json2({ result: await authority.readExact(input.receipt_id) });
40577
40803
  }
40804
+ if (action === "bindings/lookup") {
40805
+ return json2({
40806
+ result: await authority.lookupBinding(await body(request))
40807
+ });
40808
+ }
40578
40809
  if (action === "compensate") {
40579
40810
  return json2({ result: await authority.compensate(await body(request)) }, 201);
40580
40811
  }
@@ -40634,6 +40865,9 @@ class TodosTaskManifestHttpClient {
40634
40865
  async readExact(receiptId2) {
40635
40866
  return (await this.request("/read-exact", { method: "POST", body: JSON.stringify({ receipt_id: receiptId2 }) })).result;
40636
40867
  }
40868
+ async lookupBinding(input) {
40869
+ return (await this.request("/bindings/lookup", { method: "POST", body: JSON.stringify(input) })).result;
40870
+ }
40637
40871
  async markOutboxDelivered(outboxId) {
40638
40872
  await this.request("/outbox/delivered", { method: "POST", body: JSON.stringify({ outbox_id: outboxId }) });
40639
40873
  }
@@ -61987,6 +62221,7 @@ export {
61987
62221
  pauseFocusSession,
61988
62222
  patrolTasks,
61989
62223
  parseTodosTaskManifestCompensation,
62224
+ parseTodosTaskManifestBindingLookup,
61990
62225
  parseTodosTaskManifest,
61991
62226
  parseTmuxTarget,
61992
62227
  parseStorageMode,