@hasna/todos 0.15.13 → 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 (42) hide show
  1. package/dist/cli/cloud-router.d.ts.map +1 -1
  2. package/dist/cli/index.js +12117 -10745
  3. package/dist/contracts.js +1 -1
  4. package/dist/index.js +287 -53
  5. package/dist/mcp/index.js +2181 -810
  6. package/dist/mcp.js +1 -1
  7. package/dist/project-registration.js +12 -13
  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 +19661 -18290
  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/storage.js +11 -12
  23. package/dist/task-manifest/authority.d.ts +3 -1
  24. package/dist/task-manifest/authority.d.ts.map +1 -1
  25. package/dist/task-manifest/backend.d.ts +18 -1
  26. package/dist/task-manifest/backend.d.ts.map +1 -1
  27. package/dist/task-manifest/http.d.ts +2 -1
  28. package/dist/task-manifest/http.d.ts.map +1 -1
  29. package/dist/task-manifest/index.d.ts +1 -1
  30. package/dist/task-manifest/index.d.ts.map +1 -1
  31. package/dist/task-manifest/postgres.d.ts +3 -1
  32. package/dist/task-manifest/postgres.d.ts.map +1 -1
  33. package/dist/task-manifest/schema-sql.d.ts +3 -1
  34. package/dist/task-manifest/schema-sql.d.ts.map +1 -1
  35. package/dist/task-manifest/schema.d.ts +2 -1
  36. package/dist/task-manifest/schema.d.ts.map +1 -1
  37. package/dist/task-manifest/sqlite.d.ts +4 -2
  38. package/dist/task-manifest/sqlite.d.ts.map +1 -1
  39. package/dist/task-manifest/types.d.ts +22 -1
  40. package/dist/task-manifest/types.d.ts.map +1 -1
  41. package/dist/task-manifest.js +275 -40
  42. package/package.json +1 -1
@@ -4922,6 +4922,14 @@ var compensationSchema = exports_external.object({
4922
4922
  idempotency_key: identifier,
4923
4923
  if_binding_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
4924
4924
  }).strict();
4925
+ var bindingLookupSchema = exports_external.object({
4926
+ authority: exports_external.string().min(1).max(64),
4927
+ route: exports_external.string().min(1).max(128),
4928
+ schema_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
4929
+ tenant_id: identifier,
4930
+ plan_id: uuid,
4931
+ max_items: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
4932
+ }).strict();
4925
4933
  function parseTodosTaskManifest(input) {
4926
4934
  const parsed = schema.safeParse(input);
4927
4935
  if (!parsed.success) {
@@ -4987,6 +4995,36 @@ function parseTodosTaskManifestCompensation(input) {
4987
4995
  }
4988
4996
  return parsed.data;
4989
4997
  }
4998
+ function parseTodosTaskManifestBindingLookup(input) {
4999
+ const parsed = bindingLookupSchema.safeParse(input);
5000
+ if (!parsed.success) {
5001
+ 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 });
5002
+ }
5003
+ return parsed.data;
5004
+ }
5005
+
5006
+ // src/task-manifest/backend.ts
5007
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
5008
+ function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
5009
+ if (rows.length === 0) {
5010
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BINDING_NOT_FOUND", `Managed task-manifest binding not found for plan: ${planId}`, { plan_id: planId });
5011
+ }
5012
+ if (rows.length !== 1) {
5013
+ 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 });
5014
+ }
5015
+ const row = rows[0];
5016
+ const bindingVersion = Number(row.binding_version);
5017
+ const state = row.state;
5018
+ 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_PATTERN.test(row.apply_receipt_id) || !Number.isSafeInteger(bindingVersion) || bindingVersion < 1 || state !== "applied" && state !== "compensated") {
5019
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest binding and immutable apply receipt disagree", { plan_id: planId });
5020
+ }
5021
+ return {
5022
+ plan_id: planId,
5023
+ apply_receipt_id: row.apply_receipt_id,
5024
+ binding_version: bindingVersion,
5025
+ state
5026
+ };
5027
+ }
4990
5028
 
4991
5029
  // src/task-manifest/reference-guard.ts
4992
5030
  function quoteSqliteIdentifier(value) {
@@ -5076,10 +5114,14 @@ function postgresTaskManifestForeignReferenceSql(tableName) {
5076
5114
  }
5077
5115
 
5078
5116
  // src/task-manifest/schema-sql.ts
5117
+ function sqlString(value) {
5118
+ return `'${value.replaceAll("'", "''")}'`;
5119
+ }
5079
5120
  function sqliteTodosTaskManifestSchemaSql() {
5080
5121
  return `
5081
5122
  CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
5082
5123
  receipt_id TEXT PRIMARY KEY,
5124
+ tenant_id TEXT NOT NULL,
5083
5125
  authority TEXT NOT NULL CHECK(authority = 'todos'),
5084
5126
  route TEXT NOT NULL,
5085
5127
  schema_version INTEGER NOT NULL CHECK(schema_version = 1),
@@ -5097,6 +5139,7 @@ function sqliteTodosTaskManifestSchemaSql() {
5097
5139
  );
5098
5140
  CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
5099
5141
  operation_id TEXT PRIMARY KEY,
5142
+ tenant_id TEXT NOT NULL,
5100
5143
  idempotency_key TEXT NOT NULL UNIQUE,
5101
5144
  request_digest TEXT NOT NULL,
5102
5145
  result_digest TEXT NOT NULL,
@@ -5132,10 +5175,37 @@ function sqliteTodosTaskManifestSchemaSql() {
5132
5175
  END;
5133
5176
  `;
5134
5177
  }
5135
- function postgresTodosTaskManifestSchemaSql() {
5178
+ function sqliteTableHasColumn(db, tableName, columnName) {
5179
+ const columns = db.query(`PRAGMA table_info("${tableName}")`).all();
5180
+ return columns.some((column) => column.name === columnName);
5181
+ }
5182
+ function ensureSqliteTodosTaskManifestSchema(db, tenantId) {
5183
+ db.exec(sqliteTodosTaskManifestSchemaSql());
5184
+ const tenantDefault = sqlString(tenantId);
5185
+ for (const tableName of [
5186
+ "todos_task_manifest_receipts",
5187
+ "todos_task_manifest_bindings"
5188
+ ]) {
5189
+ if (!sqliteTableHasColumn(db, tableName, "tenant_id")) {
5190
+ db.exec(`ALTER TABLE "${tableName}" ADD COLUMN tenant_id TEXT NOT NULL DEFAULT ${tenantDefault}`);
5191
+ }
5192
+ }
5193
+ db.exec(`
5194
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_receipts_tenant
5195
+ ON todos_task_manifest_receipts(tenant_id, receipt_id, kind);
5196
+ CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_bindings_tenant_plan
5197
+ ON todos_task_manifest_bindings(
5198
+ tenant_id,
5199
+ json_extract(result_json, '$.graph.plan_id')
5200
+ );
5201
+ `);
5202
+ }
5203
+ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
5204
+ const tenantDefault = sqlString(tenantId);
5136
5205
  return [
5137
5206
  `CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
5138
5207
  receipt_id text PRIMARY KEY,
5208
+ tenant_id text NOT NULL,
5139
5209
  authority text NOT NULL CHECK(authority = 'todos'),
5140
5210
  route text NOT NULL,
5141
5211
  schema_version integer NOT NULL CHECK(schema_version = 1),
@@ -5151,8 +5221,13 @@ function postgresTodosTaskManifestSchemaSql() {
5151
5221
  created_at timestamptz NOT NULL,
5152
5222
  UNIQUE(kind, idempotency_key)
5153
5223
  )`,
5224
+ `ALTER TABLE todos_task_manifest_receipts
5225
+ ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
5226
+ `ALTER TABLE todos_task_manifest_receipts
5227
+ ALTER COLUMN tenant_id DROP DEFAULT`,
5154
5228
  `CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
5155
5229
  operation_id text PRIMARY KEY,
5230
+ tenant_id text NOT NULL,
5156
5231
  idempotency_key text NOT NULL UNIQUE,
5157
5232
  request_digest text NOT NULL,
5158
5233
  result_digest text NOT NULL,
@@ -5165,6 +5240,10 @@ function postgresTodosTaskManifestSchemaSql() {
5165
5240
  created_at timestamptz NOT NULL,
5166
5241
  updated_at timestamptz NOT NULL
5167
5242
  )`,
5243
+ `ALTER TABLE todos_task_manifest_bindings
5244
+ ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
5245
+ `ALTER TABLE todos_task_manifest_bindings
5246
+ ALTER COLUMN tenant_id DROP DEFAULT`,
5168
5247
  `CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
5169
5248
  id text PRIMARY KEY,
5170
5249
  apply_receipt_id text NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
@@ -5178,6 +5257,13 @@ function postgresTodosTaskManifestSchemaSql() {
5178
5257
  )`,
5179
5258
  `CREATE INDEX IF NOT EXISTS todos_task_manifest_outbox_receipt_idx
5180
5259
  ON todos_task_manifest_outbox(apply_receipt_id, status)`,
5260
+ `CREATE INDEX IF NOT EXISTS todos_task_manifest_receipts_tenant_idx
5261
+ ON todos_task_manifest_receipts(tenant_id, receipt_id, kind)`,
5262
+ `CREATE INDEX IF NOT EXISTS todos_task_manifest_bindings_tenant_plan_idx
5263
+ ON todos_task_manifest_bindings(
5264
+ tenant_id,
5265
+ ((result_json #>> '{graph,plan_id}'))
5266
+ )`,
5181
5267
  `CREATE OR REPLACE FUNCTION todos_task_manifest_receipts_immutable()
5182
5268
  RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
5183
5269
  RAISE EXCEPTION 'todos task manifest receipts are immutable';
@@ -5201,10 +5287,12 @@ function parseApplyResult(value, duplicate) {
5201
5287
 
5202
5288
  class SqliteTodosTaskManifestBackend {
5203
5289
  db;
5290
+ tenantId;
5204
5291
  kind = "sqlite";
5205
- constructor(db) {
5292
+ constructor(db, tenantId = "default") {
5206
5293
  this.db = db;
5207
- db.exec(sqliteTodosTaskManifestSchemaSql());
5294
+ this.tenantId = tenantId;
5295
+ ensureSqliteTodosTaskManifestSchema(db, tenantId);
5208
5296
  }
5209
5297
  async serialized(run) {
5210
5298
  const previous = sqliteTails.get(this.db) ?? Promise.resolve();
@@ -5234,7 +5322,7 @@ class SqliteTodosTaskManifestBackend {
5234
5322
  async apply(input, faults) {
5235
5323
  return this.serialized(() => {
5236
5324
  const { manifest } = input;
5237
- const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = ? LIMIT 1").get(manifest.operation_id);
5325
+ 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);
5238
5326
  if (binding) {
5239
5327
  if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
5240
5328
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Operation is already bound to a different request");
@@ -5244,7 +5332,7 @@ class SqliteTodosTaskManifestBackend {
5244
5332
  }
5245
5333
  return parseApplyResult(String(binding["result_json"]), true);
5246
5334
  }
5247
- const idempotency = this.db.query("SELECT operation_id, request_digest FROM todos_task_manifest_bindings WHERE idempotency_key = ? LIMIT 1").get(manifest.idempotency_key);
5335
+ 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);
5248
5336
  if (idempotency)
5249
5337
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used by another operation");
5250
5338
  if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
@@ -5334,9 +5422,9 @@ class SqliteTodosTaskManifestBackend {
5334
5422
  const resultJson = canonicalJson(result);
5335
5423
  const manifestJson = canonicalJson(manifest);
5336
5424
  this.db.query(`INSERT INTO todos_task_manifest_receipts (
5337
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
5425
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
5338
5426
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
5339
- ) 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);
5427
+ ) 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);
5340
5428
  for (const entry of input.outbox) {
5341
5429
  this.db.query(`INSERT INTO todos_task_manifest_outbox (
5342
5430
  id, apply_receipt_id, topic, payload, payload_digest, status, created_at
@@ -5344,24 +5432,55 @@ class SqliteTodosTaskManifestBackend {
5344
5432
  }
5345
5433
  fault(faults, "after_outbox_write");
5346
5434
  this.db.query(`INSERT INTO todos_task_manifest_bindings (
5347
- operation_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
5435
+ operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
5348
5436
  manifest_json, result_json, state, version, created_at, updated_at
5349
- ) 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);
5437
+ ) 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);
5350
5438
  fault(faults, "after_receipt_write");
5351
5439
  return result;
5352
5440
  });
5353
5441
  }
5354
5442
  async readExact(receiptId) {
5355
- const row = this.db.query("SELECT result_json FROM todos_task_manifest_receipts WHERE receipt_id = ? AND kind = 'apply' LIMIT 1").get(receiptId);
5443
+ 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, receiptId);
5356
5444
  if (!row)
5357
5445
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId}`);
5358
5446
  return parseApplyResult(row.result_json, false);
5359
5447
  }
5448
+ async lookupBindingByPlanId(planId) {
5449
+ const rows = this.db.query(`
5450
+ SELECT
5451
+ b.apply_receipt_id AS apply_receipt_id,
5452
+ b.state AS state,
5453
+ b.version AS binding_version,
5454
+ b.tenant_id AS binding_tenant_id,
5455
+ b.operation_id AS binding_operation_id,
5456
+ json_extract(b.result_json, '$.graph.plan_id') AS binding_plan_id,
5457
+ r.tenant_id AS receipt_tenant_id,
5458
+ r.authority AS receipt_authority,
5459
+ r.route AS receipt_route,
5460
+ r.schema_version AS receipt_schema_version,
5461
+ r.kind AS receipt_kind,
5462
+ r.operation_id AS receipt_operation_id,
5463
+ json_extract(r.result_json, '$.graph.plan_id') AS receipt_plan_id
5464
+ FROM todos_task_manifest_bindings b
5465
+ LEFT JOIN todos_task_manifest_receipts r
5466
+ ON r.receipt_id = b.apply_receipt_id
5467
+ AND r.tenant_id = b.tenant_id
5468
+ WHERE b.tenant_id = ?
5469
+ AND json_extract(b.result_json, '$.graph.plan_id') = ?
5470
+ LIMIT 2
5471
+ `).all(this.tenantId, planId);
5472
+ return validateTaskManifestBindingLookupRows(rows, this.tenantId, planId);
5473
+ }
5360
5474
  async markOutboxDelivered(outboxId, deliveredAt) {
5361
5475
  await this.serialized(() => {
5362
5476
  const result = this.db.query(`UPDATE todos_task_manifest_outbox
5363
5477
  SET status = 'delivered', delivered_at = ?, attempts = attempts + 1
5364
- WHERE id = ? AND status = 'pending'`).run(deliveredAt, outboxId);
5478
+ WHERE id = ? AND status = 'pending'
5479
+ AND EXISTS (
5480
+ SELECT 1 FROM todos_task_manifest_receipts r
5481
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
5482
+ AND r.tenant_id = ?
5483
+ )`).run(deliveredAt, outboxId, this.tenantId);
5365
5484
  if (result.changes !== 1) {
5366
5485
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
5367
5486
  }
@@ -5370,23 +5489,28 @@ class SqliteTodosTaskManifestBackend {
5370
5489
  async compensate(input, receipt, compensationReceiptId, requestDigest, now) {
5371
5490
  return this.serialized(() => {
5372
5491
  const existing = this.db.query(`SELECT apply_receipt_id, request_digest, result_json
5373
- FROM todos_task_manifest_receipts WHERE kind = 'compensate' AND idempotency_key = ? LIMIT 1`).get(input.idempotency_key);
5492
+ FROM todos_task_manifest_receipts
5493
+ WHERE tenant_id = ? AND kind = 'compensate' AND idempotency_key = ?
5494
+ LIMIT 1`).get(this.tenantId, input.idempotency_key);
5374
5495
  if (existing) {
5375
5496
  if (existing.apply_receipt_id !== input.receipt_id || existing.request_digest !== requestDigest) {
5376
5497
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation idempotency key is already used");
5377
5498
  }
5378
5499
  return { ...JSON.parse(existing.result_json), duplicate: true };
5379
5500
  }
5380
- const row = this.db.query("SELECT * FROM todos_task_manifest_receipts WHERE receipt_id = ? AND kind = 'apply' LIMIT 1").get(input.receipt_id);
5501
+ 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);
5381
5502
  if (!row)
5382
5503
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
5383
- const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = ? LIMIT 1").get(String(row["operation_id"]));
5504
+ 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"]));
5384
5505
  if (!binding || Number(binding["version"]) !== input.if_binding_version) {
5385
5506
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
5386
5507
  }
5387
5508
  if (binding["state"] !== "applied")
5388
5509
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not in applied state");
5389
- 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);
5510
+ const delivered = this.db.query(`SELECT o.id FROM todos_task_manifest_outbox o
5511
+ JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
5512
+ WHERE r.tenant_id = ? AND o.apply_receipt_id = ? AND o.status = 'delivered'
5513
+ LIMIT 1`).get(this.tenantId, input.receipt_id);
5390
5514
  if (delivered)
5391
5515
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
5392
5516
  const applyResult = parseApplyResult(String(row["result_json"]), false);
@@ -5399,7 +5523,14 @@ class SqliteTodosTaskManifestBackend {
5399
5523
  ...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
5400
5524
  ];
5401
5525
  const storedOutbox = this.db.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
5402
- FROM todos_task_manifest_outbox WHERE apply_receipt_id = ? ORDER BY id`).all(input.receipt_id);
5526
+ FROM todos_task_manifest_outbox
5527
+ WHERE apply_receipt_id = ?
5528
+ AND EXISTS (
5529
+ SELECT 1 FROM todos_task_manifest_receipts r
5530
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
5531
+ AND r.tenant_id = ?
5532
+ )
5533
+ ORDER BY id`).all(input.receipt_id, this.tenantId);
5403
5534
  if (storedOutbox.length !== expectedEffects.length) {
5404
5535
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
5405
5536
  }
@@ -5487,7 +5618,14 @@ class SqliteTodosTaskManifestBackend {
5487
5618
  }
5488
5619
  expectedVerificationIndex += 1;
5489
5620
  }
5490
- this.db.query("UPDATE todos_task_manifest_outbox SET status = 'cancelled' WHERE apply_receipt_id = ? AND status = 'pending'").run(input.receipt_id);
5621
+ this.db.query(`UPDATE todos_task_manifest_outbox
5622
+ SET status = 'cancelled'
5623
+ WHERE apply_receipt_id = ? AND status = 'pending'
5624
+ AND EXISTS (
5625
+ SELECT 1 FROM todos_task_manifest_receipts r
5626
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
5627
+ AND r.tenant_id = ?
5628
+ )`).run(input.receipt_id, this.tenantId);
5491
5629
  for (const table of ["task_tags", "task_dependencies", "task_comments", "task_verifications"]) {
5492
5630
  const column = table === "task_dependencies" ? "task_id" : "task_id";
5493
5631
  this.db.query(`DELETE FROM ${table} WHERE ${column} IN (${placeholders2})`).run(...taskIds);
@@ -5498,11 +5636,11 @@ class SqliteTodosTaskManifestBackend {
5498
5636
  const result = { duplicate: false, receipt, absent: true, readback };
5499
5637
  const resultJson = canonicalJson(result);
5500
5638
  this.db.query(`INSERT INTO todos_task_manifest_receipts (
5501
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
5639
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
5502
5640
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
5503
- ) 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, now);
5641
+ ) 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, now);
5504
5642
  const updated = this.db.query(`UPDATE todos_task_manifest_bindings SET state = 'compensated', version = ?, compensation_receipt_id = ?, updated_at = ?
5505
- WHERE operation_id = ? AND state = 'applied' AND version = ?`).run(receipt.binding_version, compensationReceiptId, now, receipt.operation_id, input.if_binding_version);
5643
+ WHERE tenant_id = ? AND operation_id = ? AND state = 'applied' AND version = ?`).run(receipt.binding_version, compensationReceiptId, now, this.tenantId, receipt.operation_id, input.if_binding_version);
5506
5644
  if (updated.changes !== 1) {
5507
5645
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding changed during compensation");
5508
5646
  }
@@ -6045,17 +6183,19 @@ class PostgresTodosTaskManifestBackend {
6045
6183
  kind = "postgresql";
6046
6184
  service;
6047
6185
  tableName;
6186
+ tenantId;
6048
6187
  schemaReady = null;
6049
6188
  constructor(client, options = {}) {
6050
6189
  this.client = client;
6051
6190
  this.service = options.service ?? "todos";
6052
6191
  this.tableName = safeIdentifier(options.tableName ?? DEFAULT_TODOS_POSTGRES_SYNC_TABLE, "tableName");
6192
+ this.tenantId = options.tenantId ?? "default";
6053
6193
  }
6054
6194
  async ensureSchema() {
6055
6195
  this.schemaReady ??= (async () => {
6056
6196
  for (const sql of postgresTodosSyncSchemaSql(this.tableName))
6057
6197
  await this.client.query(sql);
6058
- for (const sql of postgresTodosTaskManifestSchemaSql())
6198
+ for (const sql of postgresTodosTaskManifestSchemaSql(this.tenantId))
6059
6199
  await this.client.query(sql);
6060
6200
  })();
6061
6201
  await this.schemaReady;
@@ -6077,7 +6217,7 @@ class PostgresTodosTaskManifestBackend {
6077
6217
  const { manifest } = input;
6078
6218
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${manifest.operation_id}`]);
6079
6219
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fidempotency\x1F${manifest.idempotency_key}`]);
6080
- const existing = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = $1 LIMIT 1 FOR UPDATE", [manifest.operation_id]);
6220
+ 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]);
6081
6221
  if (existing.rows[0]) {
6082
6222
  const binding = existing.rows[0];
6083
6223
  if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
@@ -6088,7 +6228,7 @@ class PostgresTodosTaskManifestBackend {
6088
6228
  }
6089
6229
  return { ...parseJson(binding["result_json"]), duplicate: true };
6090
6230
  }
6091
- const reused = await tx.query("SELECT operation_id FROM todos_task_manifest_bindings WHERE idempotency_key = $1 LIMIT 1", [manifest.idempotency_key]);
6231
+ 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]);
6092
6232
  if (reused.rows[0])
6093
6233
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used");
6094
6234
  if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
@@ -6198,10 +6338,11 @@ class PostgresTodosTaskManifestBackend {
6198
6338
  const manifestJson = canonicalJson(manifest);
6199
6339
  const resultJson = canonicalJson(result);
6200
6340
  await tx.query(`INSERT INTO todos_task_manifest_receipts (
6201
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
6341
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
6202
6342
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
6203
- ) VALUES ($1, 'todos', 'todos.task-manifest.v1', 1, 'apply', $2, $3, $4, $5, 1, NULL, $6::jsonb, $7::jsonb, $8)`, [
6343
+ ) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'apply', $3, $4, $5, $6, 1, NULL, $7::jsonb, $8::jsonb, $9)`, [
6204
6344
  input.receipt_id,
6345
+ this.tenantId,
6205
6346
  manifest.operation_id,
6206
6347
  manifest.idempotency_key,
6207
6348
  input.request_digest,
@@ -6224,10 +6365,11 @@ class PostgresTodosTaskManifestBackend {
6224
6365
  }
6225
6366
  fault2(faults, "after_outbox_write");
6226
6367
  await tx.query(`INSERT INTO todos_task_manifest_bindings (
6227
- operation_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
6368
+ operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
6228
6369
  manifest_json, result_json, state, version, created_at, updated_at
6229
- ) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, 'applied', 1, $8, $8)`, [
6370
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, 'applied', 1, $9, $9)`, [
6230
6371
  manifest.operation_id,
6372
+ this.tenantId,
6231
6373
  manifest.idempotency_key,
6232
6374
  input.request_digest,
6233
6375
  input.result_digest,
@@ -6242,17 +6384,50 @@ class PostgresTodosTaskManifestBackend {
6242
6384
  }
6243
6385
  async readExact(receiptId) {
6244
6386
  await this.ensureSchema();
6245
- const result = await this.client.query("SELECT result_json FROM todos_task_manifest_receipts WHERE receipt_id = $1 AND kind = 'apply' LIMIT 1", [receiptId]);
6387
+ 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, receiptId]);
6246
6388
  if (!result.rows[0])
6247
6389
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId}`);
6248
6390
  return { ...parseJson(result.rows[0]["result_json"]), duplicate: false };
6249
6391
  }
6392
+ async lookupBindingByPlanId(planId) {
6393
+ await this.ensureSchema();
6394
+ const result = await this.client.query(`
6395
+ SELECT
6396
+ b.apply_receipt_id AS apply_receipt_id,
6397
+ b.state AS state,
6398
+ b.version AS binding_version,
6399
+ b.tenant_id AS binding_tenant_id,
6400
+ b.operation_id AS binding_operation_id,
6401
+ b.result_json #>> '{graph,plan_id}' AS binding_plan_id,
6402
+ r.tenant_id AS receipt_tenant_id,
6403
+ r.authority AS receipt_authority,
6404
+ r.route AS receipt_route,
6405
+ r.schema_version AS receipt_schema_version,
6406
+ r.kind AS receipt_kind,
6407
+ r.operation_id AS receipt_operation_id,
6408
+ r.result_json #>> '{graph,plan_id}' AS receipt_plan_id
6409
+ FROM todos_task_manifest_bindings b
6410
+ LEFT JOIN todos_task_manifest_receipts r
6411
+ ON r.receipt_id = b.apply_receipt_id
6412
+ AND r.tenant_id = b.tenant_id
6413
+ WHERE b.tenant_id = $1
6414
+ AND b.result_json #>> '{graph,plan_id}' = $2
6415
+ LIMIT 2
6416
+ `, [this.tenantId, planId]);
6417
+ return validateTaskManifestBindingLookupRows(result.rows, this.tenantId, planId);
6418
+ }
6250
6419
  async markOutboxDelivered(outboxId, deliveredAt) {
6251
6420
  await this.ensureSchema();
6252
6421
  await this.client.transaction(async (tx) => {
6253
6422
  const result = await tx.query(`UPDATE todos_task_manifest_outbox
6254
6423
  SET status = 'delivered', delivered_at = $1, attempts = attempts + 1
6255
- WHERE id = $2 AND status = 'pending' RETURNING id`, [deliveredAt, outboxId]);
6424
+ WHERE id = $2 AND status = 'pending'
6425
+ AND EXISTS (
6426
+ SELECT 1 FROM todos_task_manifest_receipts r
6427
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
6428
+ AND r.tenant_id = $3
6429
+ )
6430
+ RETURNING id`, [deliveredAt, outboxId, this.tenantId]);
6256
6431
  if (!result.rows[0])
6257
6432
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
6258
6433
  });
@@ -6262,7 +6437,10 @@ class PostgresTodosTaskManifestBackend {
6262
6437
  return this.client.transaction(async (tx) => {
6263
6438
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${receipt.operation_id}`]);
6264
6439
  await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fcompensation-idempotency\x1F${input.idempotency_key}`]);
6265
- 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]);
6440
+ const previous = await tx.query(`SELECT apply_receipt_id, request_digest, result_json
6441
+ FROM todos_task_manifest_receipts
6442
+ WHERE tenant_id = $1 AND kind = 'compensate' AND idempotency_key = $2
6443
+ LIMIT 1`, [this.tenantId, input.idempotency_key]);
6266
6444
  if (previous.rows[0]) {
6267
6445
  const row = previous.rows[0];
6268
6446
  if (row["apply_receipt_id"] !== input.receipt_id || row["request_digest"] !== requestDigest) {
@@ -6270,18 +6448,21 @@ class PostgresTodosTaskManifestBackend {
6270
6448
  }
6271
6449
  return { ...parseJson(row["result_json"]), duplicate: true };
6272
6450
  }
6273
- const applyRows = await tx.query("SELECT * FROM todos_task_manifest_receipts WHERE receipt_id = $1 AND kind = 'apply' LIMIT 1", [input.receipt_id]);
6451
+ 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]);
6274
6452
  const applyRow = applyRows.rows[0];
6275
6453
  if (!applyRow)
6276
6454
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
6277
- const bindingRows = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE operation_id = $1 LIMIT 1 FOR UPDATE", [receipt.operation_id]);
6455
+ 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]);
6278
6456
  const binding = bindingRows.rows[0];
6279
6457
  if (!binding || Number(binding["version"]) !== input.if_binding_version) {
6280
6458
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
6281
6459
  }
6282
6460
  if (binding["state"] !== "applied")
6283
6461
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
6284
- 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]);
6462
+ const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
6463
+ JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
6464
+ WHERE r.tenant_id = $1 AND o.apply_receipt_id = $2 AND o.status = 'delivered'
6465
+ LIMIT 1`, [this.tenantId, input.receipt_id]);
6285
6466
  if (delivered.rows[0])
6286
6467
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
6287
6468
  const applyResult = parseJson(applyRow["result_json"]);
@@ -6294,7 +6475,14 @@ class PostgresTodosTaskManifestBackend {
6294
6475
  ...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
6295
6476
  ];
6296
6477
  const outboxRows = await tx.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
6297
- FROM todos_task_manifest_outbox WHERE apply_receipt_id = $1 ORDER BY id`, [input.receipt_id]);
6478
+ FROM todos_task_manifest_outbox
6479
+ WHERE apply_receipt_id = $1
6480
+ AND EXISTS (
6481
+ SELECT 1 FROM todos_task_manifest_receipts r
6482
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
6483
+ AND r.tenant_id = $2
6484
+ )
6485
+ ORDER BY id`, [input.receipt_id, this.tenantId]);
6298
6486
  if (outboxRows.rows.length !== expectedEffects.length) {
6299
6487
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
6300
6488
  }
@@ -6394,7 +6582,14 @@ class PostgresTodosTaskManifestBackend {
6394
6582
  }
6395
6583
  if (stored.rows.length !== managedIds.length)
6396
6584
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: managed graph is incomplete");
6397
- await tx.query("UPDATE todos_task_manifest_outbox SET status = 'cancelled' WHERE apply_receipt_id = $1 AND status = 'pending'", [input.receipt_id]);
6585
+ await tx.query(`UPDATE todos_task_manifest_outbox
6586
+ SET status = 'cancelled'
6587
+ WHERE apply_receipt_id = $1 AND status = 'pending'
6588
+ AND EXISTS (
6589
+ SELECT 1 FROM todos_task_manifest_receipts r
6590
+ WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
6591
+ AND r.tenant_id = $2
6592
+ )`, [input.receipt_id, this.tenantId]);
6398
6593
  const typedIds = [
6399
6594
  ["dependencies", applyResult.graph.dependency_ids],
6400
6595
  ["comments", applyResult.graph.comment_ids],
@@ -6411,10 +6606,11 @@ class PostgresTodosTaskManifestBackend {
6411
6606
  const readback = await this.readback(tx, applyResult.graph);
6412
6607
  const result = { duplicate: false, receipt, absent: true, readback };
6413
6608
  await tx.query(`INSERT INTO todos_task_manifest_receipts (
6414
- receipt_id, authority, route, schema_version, kind, operation_id, idempotency_key,
6609
+ receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
6415
6610
  request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
6416
- ) VALUES ($1, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $2, $3, $4, $5, $6, $7, NULL, $8::jsonb, $9)`, [
6611
+ ) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $3, $4, $5, $6, $7, $8, NULL, $9::jsonb, $10)`, [
6417
6612
  compensationReceiptId,
6613
+ this.tenantId,
6418
6614
  receipt.operation_id,
6419
6615
  input.idempotency_key,
6420
6616
  requestDigest,
@@ -6426,10 +6622,11 @@ class PostgresTodosTaskManifestBackend {
6426
6622
  ]);
6427
6623
  const updated = await tx.query(`UPDATE todos_task_manifest_bindings
6428
6624
  SET state = 'compensated', version = $1, compensation_receipt_id = $2, updated_at = $3
6429
- WHERE operation_id = $4 AND state = 'applied' AND version = $5 RETURNING operation_id`, [
6625
+ WHERE tenant_id = $4 AND operation_id = $5 AND state = 'applied' AND version = $6 RETURNING operation_id`, [
6430
6626
  receipt.binding_version,
6431
6627
  compensationReceiptId,
6432
6628
  now,
6629
+ this.tenantId,
6433
6630
  receipt.operation_id,
6434
6631
  input.if_binding_version
6435
6632
  ]);
@@ -6469,6 +6666,13 @@ var FAULT_POINTS = [
6469
6666
  "after_outbox_write",
6470
6667
  "after_receipt_write"
6471
6668
  ];
6669
+ function resolveTenantId(value) {
6670
+ const tenantId = value ?? "default";
6671
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(tenantId)) {
6672
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "tenantId must be a bounded exact authority identifier");
6673
+ }
6674
+ return tenantId;
6675
+ }
6472
6676
  function normalize(input, now) {
6473
6677
  const parsed = parseTodosTaskManifest(input);
6474
6678
  const requestBytes = Buffer.byteLength(canonicalJson(parsed), "utf8");
@@ -6556,15 +6760,18 @@ function sanitizeManifest(manifest) {
6556
6760
  class PackageOwnedTodosTaskManifestAuthority {
6557
6761
  backend;
6558
6762
  options;
6763
+ tenantId;
6559
6764
  constructor(backend, options = {}) {
6560
6765
  this.backend = backend;
6561
6766
  this.options = options;
6767
+ this.tenantId = resolveTenantId(options.tenantId);
6562
6768
  }
6563
6769
  async capability() {
6564
6770
  return {
6565
6771
  authority: "todos",
6566
6772
  route: TODOS_TASK_MANIFEST_ROUTE,
6567
6773
  schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
6774
+ tenant_id: this.tenantId,
6568
6775
  backend: this.backend.kind,
6569
6776
  deterministic_ids: true,
6570
6777
  immutable_receipts: true,
@@ -6603,6 +6810,22 @@ class PackageOwnedTodosTaskManifestAuthority {
6603
6810
  }
6604
6811
  return this.backend.readExact(receiptId).then((result) => this.bounded(result));
6605
6812
  }
6813
+ async lookupBinding(input) {
6814
+ const request = parseTodosTaskManifestBindingLookup(input);
6815
+ if (request.max_items !== 1) {
6816
+ 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 });
6817
+ }
6818
+ if (request.authority !== "todos" || request.route !== TODOS_TASK_MANIFEST_ROUTE || request.schema_version !== TODOS_TASK_MANIFEST_SCHEMA_VERSION || request.tenant_id !== this.tenantId) {
6819
+ throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAPABILITY_MISMATCH", "Task-manifest binding lookup does not match this authority identity");
6820
+ }
6821
+ return this.bounded({
6822
+ authority: "todos",
6823
+ route: TODOS_TASK_MANIFEST_ROUTE,
6824
+ schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
6825
+ tenant_id: this.tenantId,
6826
+ ...await this.backend.lookupBindingByPlanId(request.plan_id)
6827
+ });
6828
+ }
6606
6829
  markOutboxDelivered(outboxId) {
6607
6830
  if (!outboxId || outboxId.length > 200) {
6608
6831
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "outboxId must be a bounded exact identifier");
@@ -6642,13 +6865,15 @@ function createSqliteTodosTaskManifestAuthority(options) {
6642
6865
  if (!options?.database) {
6643
6866
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An explicit SQLite Database is required");
6644
6867
  }
6645
- return new PackageOwnedTodosTaskManifestAuthority(new SqliteTodosTaskManifestBackend(options.database), options);
6868
+ const tenantId = resolveTenantId(options.tenantId);
6869
+ return new PackageOwnedTodosTaskManifestAuthority(new SqliteTodosTaskManifestBackend(options.database, tenantId), { ...options, tenantId });
6646
6870
  }
6647
6871
  function createPostgresTodosTaskManifestAuthority(client, options = {}) {
6648
6872
  if (!client || typeof client.transaction !== "function") {
6649
6873
  throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An authoritative PostgreSQL transaction(callback) client is required");
6650
6874
  }
6651
- return new PackageOwnedTodosTaskManifestAuthority(new PostgresTodosTaskManifestBackend(client, options), options);
6875
+ const tenantId = resolveTenantId(options.tenantId);
6876
+ return new PackageOwnedTodosTaskManifestAuthority(new PostgresTodosTaskManifestBackend(client, { ...options, tenantId }), { ...options, tenantId });
6652
6877
  }
6653
6878
  // src/task-manifest/http.ts
6654
6879
  var JSON_HEADERS = { "Content-Type": "application/json" };
@@ -6662,6 +6887,7 @@ function status(error) {
6662
6887
  case "TODOS_TASK_MANIFEST_FOREIGN_REFERENCE":
6663
6888
  return 400;
6664
6889
  case "TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND":
6890
+ case "TODOS_TASK_MANIFEST_BINDING_NOT_FOUND":
6665
6891
  return 404;
6666
6892
  case "TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE":
6667
6893
  return 503;
@@ -6724,6 +6950,11 @@ async function handleTodosTaskManifestHttpRequest(request, url, authority, baseP
6724
6950
  }
6725
6951
  return json({ result: await authority.readExact(input.receipt_id) });
6726
6952
  }
6953
+ if (action === "bindings/lookup") {
6954
+ return json({
6955
+ result: await authority.lookupBinding(await body(request))
6956
+ });
6957
+ }
6727
6958
  if (action === "compensate") {
6728
6959
  return json({ result: await authority.compensate(await body(request)) }, 201);
6729
6960
  }
@@ -6783,6 +7014,9 @@ class TodosTaskManifestHttpClient {
6783
7014
  async readExact(receiptId) {
6784
7015
  return (await this.request("/read-exact", { method: "POST", body: JSON.stringify({ receipt_id: receiptId }) })).result;
6785
7016
  }
7017
+ async lookupBinding(input) {
7018
+ return (await this.request("/bindings/lookup", { method: "POST", body: JSON.stringify(input) })).result;
7019
+ }
6786
7020
  async markOutboxDelivered(outboxId) {
6787
7021
  await this.request("/outbox/delivered", { method: "POST", body: JSON.stringify({ outbox_id: outboxId }) });
6788
7022
  }
@@ -6797,6 +7031,7 @@ export {
6797
7031
  sqliteTodosTaskManifestSchemaSql,
6798
7032
  postgresTodosTaskManifestSchemaSql,
6799
7033
  parseTodosTaskManifestCompensation,
7034
+ parseTodosTaskManifestBindingLookup,
6800
7035
  parseTodosTaskManifest,
6801
7036
  handleTodosTaskManifestHttpRequest,
6802
7037
  canonicalDigest as digestTodosTaskManifest,