@synapsor/runner 1.6.1 → 1.6.3

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 (53) hide show
  1. package/CHANGELOG.md +98 -3
  2. package/README.md +33 -19
  3. package/SECURITY.md +10 -2
  4. package/THREAT_MODEL.md +27 -4
  5. package/dist/authoring.mjs +53 -3
  6. package/dist/cli.d.ts +19 -2
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/local-ui.d.ts +85 -1
  9. package/dist/local-ui.d.ts.map +1 -1
  10. package/dist/runner.mjs +33542 -20382
  11. package/dist/runtime.mjs +3581 -237
  12. package/dist/shadow.mjs +2423 -65
  13. package/docs/README.md +22 -2
  14. package/docs/agent-guided-setup.md +239 -0
  15. package/docs/approval-roles-and-operator-identity.md +353 -0
  16. package/docs/auto-boundary-and-scoped-explore.md +32 -6
  17. package/docs/capability-authoring.md +19 -0
  18. package/docs/current-scope.md +20 -1
  19. package/docs/cursor-plugin.md +2 -2
  20. package/docs/dsl-json-parity.md +75 -0
  21. package/docs/dsl-reference.md +10 -0
  22. package/docs/fresh-developer-usability.md +155 -80
  23. package/docs/getting-started-own-database.md +5 -4
  24. package/docs/guarded-crud-writeback.md +19 -0
  25. package/docs/guided-onboarding.md +331 -0
  26. package/docs/human-attention-notifications.md +367 -0
  27. package/docs/limitations.md +19 -3
  28. package/docs/local-mode.md +16 -2
  29. package/docs/migrating-to-synapsor-spec.md +38 -0
  30. package/docs/production.md +39 -0
  31. package/docs/release-notes.md +87 -4
  32. package/docs/runner-config-reference.md +132 -0
  33. package/docs/running-a-runner-fleet.md +16 -1
  34. package/docs/security-boundary.md +24 -0
  35. package/docs/store-lifecycle.md +20 -1
  36. package/docs/supervised-automatic-apply.md +267 -0
  37. package/docs/troubleshooting-first-run.md +35 -0
  38. package/examples/app-owned-writeback/command-handler.mjs +0 -0
  39. package/examples/fitflow-guided-onboarding/README.md +43 -0
  40. package/examples/fitflow-guided-onboarding/app/page.tsx +8 -0
  41. package/examples/fitflow-guided-onboarding/docker-compose.yml +16 -0
  42. package/examples/fitflow-guided-onboarding/package.json +18 -0
  43. package/examples/fitflow-guided-onboarding/prisma/schema.prisma +98 -0
  44. package/examples/fitflow-guided-onboarding/seed/postgres.sql +401 -0
  45. package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +0 -0
  46. package/examples/operator-oidc/issuer.mjs +188 -0
  47. package/examples/reference-support-billing-app/scripts/run-demo.sh +0 -0
  48. package/examples/support-billing-agent/scripts/run-demo.sh +0 -0
  49. package/examples/support-billing-agent/scripts/run-evaluation.sh +0 -0
  50. package/examples/support-plan-credit/README.md +6 -2
  51. package/package.json +11 -9
  52. package/schemas/schema-candidate-review.schema.json +42 -0
  53. package/schemas/synapsor.runner.schema.json +227 -4
package/dist/shadow.mjs CHANGED
@@ -1469,6 +1469,9 @@ var ProposalStore = class {
1469
1469
  shadow_study_cases: this.countTable("shadow_study_cases"),
1470
1470
  shadow_outcomes: this.countTable("shadow_outcomes"),
1471
1471
  worker_queue: this.countTable("worker_queue"),
1472
+ attention_events: this.countTable("attention_events"),
1473
+ attention_items: this.countTable("attention_items"),
1474
+ notification_deliveries: this.countTable("notification_deliveries"),
1472
1475
  policy_recommendations: this.countTable("policy_recommendations"),
1473
1476
  page_count: pageCount,
1474
1477
  page_size: pageSize,
@@ -1808,15 +1811,95 @@ var ProposalStore = class {
1808
1811
  updated_at TEXT NOT NULL
1809
1812
  );
1810
1813
 
1814
+ CREATE TABLE IF NOT EXISTS attention_events (
1815
+ event_id TEXT PRIMARY KEY,
1816
+ schema_version TEXT NOT NULL,
1817
+ event_type TEXT NOT NULL,
1818
+ severity TEXT NOT NULL,
1819
+ occurred_at TEXT NOT NULL,
1820
+ environment TEXT NOT NULL,
1821
+ proposal_id TEXT,
1822
+ job_id TEXT,
1823
+ operation_id TEXT,
1824
+ correlation_id TEXT,
1825
+ capability TEXT,
1826
+ contract_digest TEXT,
1827
+ attention_key TEXT,
1828
+ attention_required INTEGER NOT NULL,
1829
+ immediate_default INTEGER NOT NULL,
1830
+ summary TEXT NOT NULL,
1831
+ approval_source TEXT,
1832
+ worker_state TEXT,
1833
+ failure_class TEXT,
1834
+ expires_at TEXT,
1835
+ workbench_path TEXT,
1836
+ details_json TEXT NOT NULL,
1837
+ payload_hash TEXT NOT NULL,
1838
+ created_at TEXT NOT NULL
1839
+ );
1840
+
1841
+ CREATE TABLE IF NOT EXISTS attention_items (
1842
+ attention_id TEXT PRIMARY KEY,
1843
+ attention_key TEXT NOT NULL UNIQUE,
1844
+ status TEXT NOT NULL,
1845
+ severity TEXT NOT NULL,
1846
+ environment TEXT NOT NULL,
1847
+ event_type TEXT NOT NULL,
1848
+ capability TEXT,
1849
+ contract_digest TEXT,
1850
+ title TEXT NOT NULL,
1851
+ occurrence_count INTEGER NOT NULL,
1852
+ first_event_id TEXT NOT NULL,
1853
+ latest_event_id TEXT NOT NULL,
1854
+ first_seen_at TEXT NOT NULL,
1855
+ last_seen_at TEXT NOT NULL,
1856
+ acknowledged_by TEXT,
1857
+ acknowledged_at TEXT,
1858
+ acknowledgement_identity_json TEXT,
1859
+ acknowledgement_decision_hash TEXT,
1860
+ acknowledgement_signature TEXT,
1861
+ acknowledgement_integrity_hash TEXT,
1862
+ resolved_at TEXT,
1863
+ expires_at TEXT,
1864
+ FOREIGN KEY (first_event_id) REFERENCES attention_events(event_id),
1865
+ FOREIGN KEY (latest_event_id) REFERENCES attention_events(event_id)
1866
+ );
1867
+
1868
+ CREATE TABLE IF NOT EXISTS notification_deliveries (
1869
+ delivery_id TEXT PRIMARY KEY,
1870
+ sink_id TEXT NOT NULL,
1871
+ event_id TEXT NOT NULL,
1872
+ attention_id TEXT,
1873
+ status TEXT NOT NULL,
1874
+ attempts INTEGER NOT NULL DEFAULT 0,
1875
+ max_attempts INTEGER NOT NULL,
1876
+ next_attempt_at TEXT NOT NULL,
1877
+ lease_owner TEXT,
1878
+ lease_id TEXT,
1879
+ lease_expires_at TEXT,
1880
+ last_error_code TEXT,
1881
+ external_reference TEXT,
1882
+ delivered_at TEXT,
1883
+ created_at TEXT NOT NULL,
1884
+ updated_at TEXT NOT NULL,
1885
+ UNIQUE(sink_id, event_id),
1886
+ FOREIGN KEY (event_id) REFERENCES attention_events(event_id),
1887
+ FOREIGN KEY (attention_id) REFERENCES attention_items(attention_id)
1888
+ );
1889
+
1811
1890
  CREATE TABLE IF NOT EXISTS worker_queue (
1812
1891
  proposal_id TEXT PRIMARY KEY,
1813
1892
  status TEXT NOT NULL,
1893
+ execution_mode TEXT DEFAULT 'legacy',
1894
+ contract_digest TEXT,
1814
1895
  attempts INTEGER NOT NULL DEFAULT 0,
1815
1896
  max_attempts INTEGER NOT NULL,
1816
1897
  next_attempt_at TEXT NOT NULL,
1817
1898
  lease_owner TEXT,
1899
+ lease_id TEXT,
1818
1900
  lease_expires_at TEXT,
1819
1901
  last_error_code TEXT,
1902
+ terminal_outcome TEXT,
1820
1903
  created_at TEXT NOT NULL,
1821
1904
  updated_at TEXT NOT NULL,
1822
1905
  FOREIGN KEY (proposal_id) REFERENCES proposals(proposal_id)
@@ -1838,6 +1921,12 @@ var ProposalStore = class {
1838
1921
  CREATE INDEX IF NOT EXISTS idx_cloud_outbox_due ON cloud_outbox(status, next_attempt_at, sequence, created_at);
1839
1922
  CREATE INDEX IF NOT EXISTS idx_cloud_outbox_proposal ON cloud_outbox(proposal_id, sequence, created_at);
1840
1923
  CREATE INDEX IF NOT EXISTS idx_cloud_governance_proposal ON cloud_governance_events(proposal_id, created_at);
1924
+ CREATE INDEX IF NOT EXISTS idx_attention_events_type_time ON attention_events(event_type, occurred_at, event_id);
1925
+ CREATE INDEX IF NOT EXISTS idx_attention_events_proposal ON attention_events(proposal_id, occurred_at, event_id);
1926
+ CREATE INDEX IF NOT EXISTS idx_attention_events_capability ON attention_events(capability, occurred_at, event_id);
1927
+ CREATE INDEX IF NOT EXISTS idx_attention_items_status ON attention_items(status, severity, last_seen_at, attention_id);
1928
+ CREATE INDEX IF NOT EXISTS idx_notification_deliveries_due ON notification_deliveries(status, next_attempt_at, created_at, delivery_id);
1929
+ CREATE INDEX IF NOT EXISTS idx_notification_deliveries_sink ON notification_deliveries(sink_id, status, updated_at, delivery_id);
1841
1930
 
1842
1931
  INSERT OR IGNORE INTO proposal_store_schema(version, applied_at)
1843
1932
  VALUES (1, datetime('now'));
@@ -1869,6 +1958,14 @@ var ProposalStore = class {
1869
1958
  this.ensureColumn("approvals", "signature", "TEXT");
1870
1959
  this.ensureColumn("approvals", "integrity_hash", "TEXT");
1871
1960
  this.ensureColumn("approvals", "freshness_proof_digest", "TEXT");
1961
+ this.ensureColumn("attention_items", "acknowledgement_identity_json", "TEXT");
1962
+ this.ensureColumn("attention_items", "acknowledgement_decision_hash", "TEXT");
1963
+ this.ensureColumn("attention_items", "acknowledgement_signature", "TEXT");
1964
+ this.ensureColumn("attention_items", "acknowledgement_integrity_hash", "TEXT");
1965
+ this.ensureColumn("worker_queue", "execution_mode", "TEXT DEFAULT 'legacy'");
1966
+ this.ensureColumn("worker_queue", "contract_digest", "TEXT");
1967
+ this.ensureColumn("worker_queue", "lease_id", "TEXT");
1968
+ this.ensureColumn("worker_queue", "terminal_outcome", "TEXT");
1872
1969
  }
1873
1970
  ensureSearchIndexes() {
1874
1971
  this.db.exec(`
@@ -1912,6 +2009,7 @@ var ProposalStore = class {
1912
2009
 
1913
2010
  CREATE INDEX IF NOT EXISTS idx_proposal_events_kind_created ON proposal_events(kind, created_at);
1914
2011
  CREATE INDEX IF NOT EXISTS idx_worker_queue_claim ON worker_queue(status, next_attempt_at, lease_expires_at, created_at);
2012
+ CREATE INDEX IF NOT EXISTS idx_worker_queue_supervised_claim ON worker_queue(execution_mode, contract_digest, status, next_attempt_at, lease_expires_at, created_at);
1915
2013
  `);
1916
2014
  }
1917
2015
  ensureColumn(table, column, definition) {
@@ -2291,6 +2389,7 @@ var ProposalStore = class {
2291
2389
  const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
2292
2390
  const window = utcDayWindow(now);
2293
2391
  const trippedLimits = [];
2392
+ const nearLimits = [];
2294
2393
  let quorumDeferred = false;
2295
2394
  this.transaction(() => {
2296
2395
  const proposal = this.requireProposal(proposalId);
@@ -2343,6 +2442,17 @@ var ProposalStore = class {
2343
2442
  window_end: window.end,
2344
2443
  reason: `${scope} daily auto-approval count ${projected2} exceeds ${limit.max}`
2345
2444
  });
2445
+ } else if (projected2 / limit.max >= 0.8) {
2446
+ nearLimits.push({
2447
+ ...limit,
2448
+ scope,
2449
+ observed: rows.length,
2450
+ proposed: 1,
2451
+ projected: projected2,
2452
+ window_start: window.start,
2453
+ window_end: window.end,
2454
+ reason: `${scope} daily auto-approval count ${projected2} is approaching ${limit.max}`
2455
+ });
2346
2456
  }
2347
2457
  continue;
2348
2458
  }
@@ -2377,6 +2487,17 @@ var ProposalStore = class {
2377
2487
  window_end: window.end,
2378
2488
  reason: invalidHistory || !field || typeof proposed !== "number" || !Number.isSafeInteger(proposed) ? `${scope} daily auto-approval total could not be verified safely${field ? ` for ${field}` : ""}` : `${scope} daily auto-approval total ${projected} for ${field} exceeds ${limit.max}`
2379
2489
  });
2490
+ } else if (projected / limit.max >= 0.8) {
2491
+ nearLimits.push({
2492
+ ...limit,
2493
+ scope,
2494
+ observed,
2495
+ proposed: proposedNumber,
2496
+ projected,
2497
+ window_start: window.start,
2498
+ window_end: window.end,
2499
+ reason: `${scope} daily auto-approval total is approaching its reviewed maximum`
2500
+ });
2380
2501
  }
2381
2502
  }
2382
2503
  if (trippedLimits.length > 0) {
@@ -2413,6 +2534,19 @@ var ProposalStore = class {
2413
2534
  aggregate_limits: options.limits ?? [],
2414
2535
  freshness_proof_digest: options.freshness_proof_digest ?? null
2415
2536
  });
2537
+ for (const near of nearLimits) {
2538
+ this.appendEvent(proposalId, "policy_limit_near", actor, {
2539
+ policy: options.policy,
2540
+ kind: near.kind,
2541
+ scope: near.scope,
2542
+ observed: near.observed,
2543
+ proposed: near.proposed,
2544
+ projected: near.projected,
2545
+ max: near.max,
2546
+ window_start: near.window_start,
2547
+ window_end: near.window_end
2548
+ });
2549
+ }
2416
2550
  });
2417
2551
  return {
2418
2552
  proposal: this.requireProposal(proposalId),
@@ -2722,6 +2856,30 @@ var ProposalStore = class {
2722
2856
  decision_hash: input.identity?.decision_hash
2723
2857
  });
2724
2858
  this.recordExecutionReceiptRows(receipt, proposal);
2859
+ const workerItem = this.workerQueueItem(intent.proposal_id);
2860
+ if (workerItem?.status === "reconciliation_required") {
2861
+ const queueStatus = receipt.status === "failed" ? "dead_letter" : "completed";
2862
+ const terminalOutcome = receipt.status === "failed" ? "dead_letter" : receipt.status;
2863
+ this.db.prepare(`
2864
+ UPDATE worker_queue
2865
+ SET status = ?, lease_owner = NULL, lease_id = NULL,
2866
+ lease_expires_at = NULL, last_error_code = ?,
2867
+ terminal_outcome = ?, updated_at = ?
2868
+ WHERE proposal_id = ? AND status = 'reconciliation_required'
2869
+ `).run(
2870
+ queueStatus,
2871
+ receipt.status === "failed" ? "RECONCILED_FAILED" : null,
2872
+ terminalOutcome,
2873
+ receipt.executed_at,
2874
+ intent.proposal_id
2875
+ );
2876
+ this.appendEvent(intent.proposal_id, "writeback_worker_reconciled", input.actor, {
2877
+ intent_id: intent.intent_id,
2878
+ outcome: receipt.status,
2879
+ queue_status: queueStatus,
2880
+ source_database_changed: false
2881
+ });
2882
+ }
2725
2883
  });
2726
2884
  return this.requireWritebackIntent(intent.intent_id);
2727
2885
  }
@@ -3050,6 +3208,78 @@ var ProposalStore = class {
3050
3208
  const rows = this.db.prepare(query.sql).all(...query.params);
3051
3209
  return rows.map(rowToEvent).filter((event) => event !== void 0);
3052
3210
  }
3211
+ enqueueWorkerProposal(options) {
3212
+ const proposal = this.requireProposal(options.proposal_id);
3213
+ if (proposal.state !== "approved" && proposal.state !== "pending_worker") {
3214
+ throw new ProposalStoreError(
3215
+ "WORKER_PROPOSAL_NOT_APPROVED",
3216
+ `proposal ${proposal.proposal_id} is ${proposal.state}, not approved for worker execution`
3217
+ );
3218
+ }
3219
+ const executionMode = options.execution_mode ?? "legacy";
3220
+ const contractDigest = options.contract_digest;
3221
+ if (executionMode === "supervised_worker" && !contractDigest) {
3222
+ throw new ProposalStoreError(
3223
+ "SUPERVISED_WORKER_DIGEST_REQUIRED",
3224
+ `supervised worker queue item ${proposal.proposal_id} requires an exact contract digest`
3225
+ );
3226
+ }
3227
+ if (contractDigest && !/^sha256:[a-f0-9]{64}$/.test(contractDigest)) {
3228
+ throw new ProposalStoreError("WORKER_CONTRACT_DIGEST_INVALID", "worker queue contract digest must be a full lowercase sha256 digest");
3229
+ }
3230
+ const maxAttempts = Math.max(1, Math.min(options.max_attempts ?? 5, 100));
3231
+ const queueLimit = Math.max(1, Math.min(options.queue_limit ?? 1e4, 1e5));
3232
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3233
+ this.transaction(() => {
3234
+ const existing = this.workerQueueItem(proposal.proposal_id);
3235
+ if (existing) {
3236
+ if (existing.execution_mode !== executionMode || existing.contract_digest !== contractDigest) {
3237
+ throw new ProposalStoreError(
3238
+ "WORKER_QUEUE_AUTHORITY_MISMATCH",
3239
+ `worker queue item ${proposal.proposal_id} already exists under different execution authority`
3240
+ );
3241
+ }
3242
+ return;
3243
+ }
3244
+ const active = this.db.prepare(`
3245
+ SELECT COUNT(*) AS count
3246
+ FROM worker_queue q
3247
+ JOIN proposals p ON p.proposal_id = q.proposal_id
3248
+ WHERE q.execution_mode = ?
3249
+ AND (? IS NULL OR q.contract_digest = ?)
3250
+ AND p.action = ?
3251
+ AND q.status IN ('queued', 'leased', 'retry_wait', 'blocked', 'reconciliation_required')
3252
+ `).get(executionMode, contractDigest ?? null, contractDigest ?? null, proposal.action);
3253
+ if (isRecord(active) && Number(active.count ?? 0) >= queueLimit) {
3254
+ throw new ProposalStoreError(
3255
+ "WORKER_QUEUE_LIMIT_EXCEEDED",
3256
+ `worker queue limit ${queueLimit} reached for ${proposal.action}`
3257
+ );
3258
+ }
3259
+ this.db.prepare(`
3260
+ INSERT INTO worker_queue (
3261
+ proposal_id, status, execution_mode, contract_digest, attempts,
3262
+ max_attempts, next_attempt_at, lease_owner, lease_id,
3263
+ lease_expires_at, last_error_code, terminal_outcome, created_at,
3264
+ updated_at
3265
+ ) VALUES (?, 'queued', ?, ?, 0, ?, ?, NULL, NULL, NULL, NULL, NULL, ?, ?)
3266
+ `).run(
3267
+ proposal.proposal_id,
3268
+ executionMode,
3269
+ contractDigest ?? null,
3270
+ maxAttempts,
3271
+ now,
3272
+ now,
3273
+ now
3274
+ );
3275
+ this.appendEvent(proposal.proposal_id, "writeback_worker_queued", "runner", {
3276
+ execution_mode: executionMode,
3277
+ contract_digest: contractDigest ?? null,
3278
+ max_attempts: maxAttempts
3279
+ });
3280
+ });
3281
+ return this.requireWorkerQueueItem(proposal.proposal_id);
3282
+ }
3053
3283
  enqueueApprovedForWorker(options = {}) {
3054
3284
  const maxAttempts = Math.max(1, Math.min(options.maxAttempts ?? 5, 100));
3055
3285
  const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -3061,9 +3291,11 @@ var ProposalStore = class {
3061
3291
  for (const proposal of proposals) {
3062
3292
  this.db.prepare(`
3063
3293
  INSERT OR IGNORE INTO worker_queue (
3064
- proposal_id, status, attempts, max_attempts, next_attempt_at,
3065
- lease_owner, lease_expires_at, last_error_code, created_at, updated_at
3066
- ) VALUES (?, 'queued', 0, ?, ?, NULL, NULL, NULL, ?, ?)
3294
+ proposal_id, status, execution_mode, contract_digest, attempts,
3295
+ max_attempts, next_attempt_at, lease_owner, lease_id,
3296
+ lease_expires_at, last_error_code, terminal_outcome, created_at,
3297
+ updated_at
3298
+ ) VALUES (?, 'queued', 'legacy', NULL, 0, ?, ?, NULL, NULL, NULL, NULL, NULL, ?, ?)
3067
3299
  `).run(proposal.proposal_id, maxAttempts, now, now, now);
3068
3300
  }
3069
3301
  });
@@ -3075,6 +3307,58 @@ var ProposalStore = class {
3075
3307
  const leaseExpiresAt = new Date(Date.parse(now) + leaseSeconds * 1e3).toISOString();
3076
3308
  let claimed;
3077
3309
  this.transaction(() => {
3310
+ if (options.maxConcurrent !== void 0) {
3311
+ const maximum = Math.max(1, Math.min(options.maxConcurrent, 32));
3312
+ const active = this.db.prepare(`
3313
+ SELECT COUNT(*) AS count
3314
+ FROM worker_queue q
3315
+ JOIN proposals p ON p.proposal_id = q.proposal_id
3316
+ WHERE q.status = 'leased'
3317
+ AND q.lease_expires_at > ?
3318
+ AND (? IS NULL OR q.execution_mode = ?)
3319
+ AND (? IS NULL OR p.action = ?)
3320
+ AND (? IS NULL OR p.tenant_id = ?)
3321
+ AND (? IS NULL OR q.contract_digest = ?)
3322
+ `).get(
3323
+ now,
3324
+ options.executionMode ?? null,
3325
+ options.executionMode ?? null,
3326
+ options.capability ?? null,
3327
+ options.capability ?? null,
3328
+ options.tenant ?? null,
3329
+ options.tenant ?? null,
3330
+ options.contractDigest ?? null,
3331
+ options.contractDigest ?? null
3332
+ );
3333
+ if (isRecord(active) && Number(active.count ?? 0) >= maximum) return;
3334
+ }
3335
+ if (options.rateLimit) {
3336
+ const executions = Math.max(1, Math.min(options.rateLimit.executions, 1e5));
3337
+ const windowSeconds = Math.max(1, Math.min(options.rateLimit.windowSeconds, 86400));
3338
+ const windowStart = new Date(Date.parse(now) - windowSeconds * 1e3).toISOString();
3339
+ const recent = this.db.prepare(`
3340
+ SELECT COUNT(*) AS count
3341
+ FROM worker_queue q
3342
+ JOIN proposals p ON p.proposal_id = q.proposal_id
3343
+ WHERE q.status = 'completed'
3344
+ AND q.updated_at >= ?
3345
+ AND (? IS NULL OR q.execution_mode = ?)
3346
+ AND (? IS NULL OR p.action = ?)
3347
+ AND (? IS NULL OR p.tenant_id = ?)
3348
+ AND (? IS NULL OR q.contract_digest = ?)
3349
+ `).get(
3350
+ windowStart,
3351
+ options.executionMode ?? null,
3352
+ options.executionMode ?? null,
3353
+ options.capability ?? null,
3354
+ options.capability ?? null,
3355
+ options.tenant ?? null,
3356
+ options.tenant ?? null,
3357
+ options.contractDigest ?? null,
3358
+ options.contractDigest ?? null
3359
+ );
3360
+ if (isRecord(recent) && Number(recent.count ?? 0) >= executions) return;
3361
+ }
3078
3362
  const raw = this.db.prepare(`
3079
3363
  SELECT q.*
3080
3364
  FROM worker_queue q
@@ -3084,58 +3368,297 @@ var ProposalStore = class {
3084
3368
  OR (q.status = 'leased' AND q.lease_expires_at <= ?)
3085
3369
  )
3086
3370
  AND p.state IN ('approved', 'pending_worker', 'failed')
3371
+ AND (? IS NULL OR q.execution_mode = ?)
3372
+ AND (? IS NULL OR p.action = ?)
3373
+ AND (? IS NULL OR p.tenant_id = ?)
3374
+ AND (? IS NULL OR q.contract_digest = ?)
3087
3375
  ORDER BY q.next_attempt_at ASC, q.created_at ASC
3088
3376
  LIMIT 1
3089
- `).get(now, now);
3377
+ `).get(
3378
+ now,
3379
+ now,
3380
+ options.executionMode ?? null,
3381
+ options.executionMode ?? null,
3382
+ options.capability ?? null,
3383
+ options.capability ?? null,
3384
+ options.tenant ?? null,
3385
+ options.tenant ?? null,
3386
+ options.contractDigest ?? null,
3387
+ options.contractDigest ?? null
3388
+ );
3090
3389
  const item = rowToWorkerQueueItem(raw);
3091
3390
  if (!item) return;
3391
+ const proposal = this.requireProposal(item.proposal_id);
3392
+ if (options.proposalTtlSeconds !== void 0) {
3393
+ const ttlSeconds = Math.max(60, Math.min(options.proposalTtlSeconds, 2592e3));
3394
+ const expiresAt = Date.parse(proposal.created_at) + ttlSeconds * 1e3;
3395
+ if (!Number.isFinite(expiresAt) || expiresAt <= Date.parse(now)) {
3396
+ this.blockQueuedWorkerItem(item, options.workerId, "SUPERVISED_WORKER_PROPOSAL_EXPIRED", now);
3397
+ return;
3398
+ }
3399
+ }
3400
+ if (options.policyExecution) {
3401
+ const trips = this.workerPolicyExecutionLimitTrips({
3402
+ proposal,
3403
+ policy: options.policyExecution.policy,
3404
+ limits: options.policyExecution.limits,
3405
+ now
3406
+ });
3407
+ if (trips.length > 0) {
3408
+ this.blockQueuedWorkerItem(
3409
+ item,
3410
+ options.workerId,
3411
+ "SUPERVISED_WORKER_POLICY_LIMIT_EXCEEDED",
3412
+ now,
3413
+ { policy: options.policyExecution.policy, tripped_limits: trips }
3414
+ );
3415
+ return;
3416
+ }
3417
+ }
3418
+ const leaseId = workerLeaseId(item.proposal_id, options.workerId, item.attempts + 1, now);
3092
3419
  this.db.prepare(`
3093
3420
  UPDATE worker_queue
3094
3421
  SET status = 'leased', attempts = attempts + 1, lease_owner = ?,
3095
- lease_expires_at = ?, updated_at = ?
3422
+ lease_id = ?, lease_expires_at = ?, last_error_code = NULL,
3423
+ terminal_outcome = NULL, updated_at = ?
3096
3424
  WHERE proposal_id = ?
3097
- `).run(options.workerId, leaseExpiresAt, now, item.proposal_id);
3098
- const proposal = this.requireProposal(item.proposal_id);
3425
+ `).run(options.workerId, leaseId, leaseExpiresAt, now, item.proposal_id);
3099
3426
  if (proposal.state === "failed") {
3100
3427
  this.db.prepare("UPDATE proposals SET state = 'pending_worker', updated_at = ? WHERE proposal_id = ?").run(now, item.proposal_id);
3101
3428
  }
3102
3429
  this.appendEvent(item.proposal_id, "writeback_worker_claimed", options.workerId, {
3103
3430
  attempt: item.attempts + 1,
3104
3431
  max_attempts: item.max_attempts,
3432
+ execution_mode: item.execution_mode,
3433
+ contract_digest: item.contract_digest ?? null,
3434
+ lease_id: leaseId,
3105
3435
  lease_expires_at: leaseExpiresAt
3106
3436
  });
3107
3437
  claimed = this.workerQueueItem(item.proposal_id);
3108
3438
  });
3109
3439
  return claimed;
3110
3440
  }
3111
- completeWorkerItem(proposalId, workerId, outcome, now = (/* @__PURE__ */ new Date()).toISOString()) {
3441
+ assertWorkerPolicyExecutionLimits(input) {
3442
+ const proposal = this.requireProposal(input.proposalId);
3443
+ const trips = this.workerPolicyExecutionLimitTrips({
3444
+ proposal,
3445
+ policy: input.policy,
3446
+ limits: input.limits,
3447
+ now: input.now ?? (/* @__PURE__ */ new Date()).toISOString()
3448
+ });
3449
+ if (trips.length > 0) {
3450
+ throw new ProposalStoreError(
3451
+ "SUPERVISED_WORKER_POLICY_LIMIT_EXCEEDED",
3452
+ `execution-time policy limit no longer permits proposal ${proposal.proposal_id}`
3453
+ );
3454
+ }
3455
+ }
3456
+ blockQueuedWorkerItem(item, actor, errorCode, now, payload = {}) {
3457
+ this.db.prepare(`
3458
+ UPDATE worker_queue
3459
+ SET status = 'blocked', lease_owner = NULL, lease_id = NULL,
3460
+ lease_expires_at = NULL, last_error_code = ?,
3461
+ terminal_outcome = NULL, updated_at = ?
3462
+ WHERE proposal_id = ?
3463
+ `).run(errorCode, now, item.proposal_id);
3464
+ this.appendEvent(item.proposal_id, "writeback_worker_blocked", actor, {
3465
+ error_code: errorCode,
3466
+ execution_mode: item.execution_mode,
3467
+ contract_digest: item.contract_digest ?? null,
3468
+ ...payload
3469
+ });
3470
+ }
3471
+ workerPolicyExecutionLimitTrips(input) {
3472
+ if (input.limits.length === 0) return [];
3473
+ const actor = `policy:${input.policy}`;
3474
+ const candidateApproval = this.db.prepare(`
3475
+ SELECT approval_id
3476
+ FROM approvals
3477
+ WHERE proposal_id = ?
3478
+ AND approver = ?
3479
+ AND status = 'approved'
3480
+ AND proposal_hash = ?
3481
+ AND proposal_version = ?
3482
+ LIMIT 1
3483
+ `).get(
3484
+ input.proposal.proposal_id,
3485
+ actor,
3486
+ input.proposal.proposal_hash,
3487
+ input.proposal.proposal_version
3488
+ );
3489
+ if (!isRecord(candidateApproval)) return [];
3490
+ const window = utcDayWindow(input.now);
3491
+ const rows = this.db.prepare(`
3492
+ SELECT DISTINCT
3493
+ p.proposal_id,
3494
+ p.business_object,
3495
+ p.object_id,
3496
+ p.change_set_json
3497
+ FROM worker_queue q
3498
+ JOIN proposals p ON p.proposal_id = q.proposal_id
3499
+ JOIN approvals a ON a.proposal_id = p.proposal_id
3500
+ WHERE a.approver = ?
3501
+ AND a.status = 'approved'
3502
+ AND p.tenant_id = ?
3503
+ AND (
3504
+ (q.status = 'leased' AND q.lease_expires_at > ?)
3505
+ OR (
3506
+ q.status = 'completed'
3507
+ AND q.terminal_outcome IN ('applied', 'already_applied')
3508
+ AND q.updated_at >= ?
3509
+ AND q.updated_at < ?
3510
+ )
3511
+ OR (
3512
+ q.status = 'reconciliation_required'
3513
+ AND q.updated_at >= ?
3514
+ AND q.updated_at < ?
3515
+ )
3516
+ OR (
3517
+ p.state = 'applied'
3518
+ AND p.updated_at >= ?
3519
+ AND p.updated_at < ?
3520
+ )
3521
+ )
3522
+ `).all(
3523
+ actor,
3524
+ input.proposal.tenant_id,
3525
+ input.now,
3526
+ window.start,
3527
+ window.end,
3528
+ window.start,
3529
+ window.end,
3530
+ window.start,
3531
+ window.end
3532
+ );
3533
+ const active = /* @__PURE__ */ new Map();
3534
+ let invalidHistory = false;
3535
+ for (const row of rows) {
3536
+ if (!isRecord(row)) continue;
3537
+ try {
3538
+ const proposalId = String(row.proposal_id);
3539
+ active.set(proposalId, {
3540
+ proposal_id: proposalId,
3541
+ business_object: String(row.business_object),
3542
+ object_id: String(row.object_id),
3543
+ change_set: parseChangeSet(JSON.parse(String(row.change_set_json)))
3544
+ });
3545
+ } catch {
3546
+ invalidHistory = true;
3547
+ active.set(String(row.proposal_id), {
3548
+ proposal_id: String(row.proposal_id),
3549
+ business_object: String(row.business_object),
3550
+ object_id: String(row.object_id),
3551
+ change_set: input.proposal.change_set
3552
+ });
3553
+ }
3554
+ }
3555
+ active.set(input.proposal.proposal_id, {
3556
+ proposal_id: input.proposal.proposal_id,
3557
+ business_object: input.proposal.business_object,
3558
+ object_id: input.proposal.object_id,
3559
+ change_set: input.proposal.change_set
3560
+ });
3561
+ const trips = [];
3562
+ for (const limit of input.limits) {
3563
+ const scope = limit.scope ?? "tenant_policy";
3564
+ const scoped = [...active.values()].filter((proposal) => scope !== "tenant_policy_object" || proposal.business_object === input.proposal.business_object && proposal.object_id === input.proposal.object_id);
3565
+ if (limit.kind === "count") {
3566
+ const projected2 = scoped.length;
3567
+ if (projected2 > limit.max) {
3568
+ trips.push({
3569
+ ...limit,
3570
+ scope,
3571
+ observed: Math.max(0, projected2 - 1),
3572
+ proposed: 1,
3573
+ projected: projected2,
3574
+ window_start: window.start,
3575
+ window_end: window.end,
3576
+ reason: `${scope} execution count ${projected2} exceeds ${limit.max}`
3577
+ });
3578
+ }
3579
+ continue;
3580
+ }
3581
+ const field = limit.field;
3582
+ let projected = 0;
3583
+ let proposed = 0;
3584
+ let invalid = !field || invalidHistory;
3585
+ for (const proposal of scoped) {
3586
+ const value = field ? proposal.change_set.patch[field] : void 0;
3587
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) {
3588
+ invalid = true;
3589
+ continue;
3590
+ }
3591
+ projected += value;
3592
+ if (proposal.proposal_id === input.proposal.proposal_id) proposed = value;
3593
+ }
3594
+ if (invalid || projected > limit.max) {
3595
+ trips.push({
3596
+ ...limit,
3597
+ scope,
3598
+ observed: projected - proposed,
3599
+ proposed,
3600
+ projected,
3601
+ window_start: window.start,
3602
+ window_end: window.end,
3603
+ reason: invalid ? `${scope} execution total could not be verified safely${field ? ` for ${field}` : ""}` : `${scope} execution total ${projected} for ${field} exceeds ${limit.max}`
3604
+ });
3605
+ }
3606
+ }
3607
+ return trips;
3608
+ }
3609
+ assertActiveWorkerLease(options) {
3610
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3611
+ const item = this.assertWorkerLease(options.proposalId, options.workerId, options.leaseId);
3612
+ if (!item.lease_expires_at || Date.parse(item.lease_expires_at) <= Date.parse(now)) {
3613
+ throw new ProposalStoreError("WORKER_LEASE_EXPIRED", `worker lease ${options.leaseId} for ${options.proposalId} has expired`);
3614
+ }
3615
+ return item;
3616
+ }
3617
+ renewWorkerLease(options) {
3618
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3619
+ const leaseSeconds = Math.max(15, Math.min(options.leaseSeconds ?? 60, 3600));
3620
+ const leaseExpiresAt = new Date(Date.parse(now) + leaseSeconds * 1e3).toISOString();
3621
+ this.transaction(() => {
3622
+ this.assertActiveWorkerLease({ ...options, now });
3623
+ this.db.prepare(`
3624
+ UPDATE worker_queue
3625
+ SET lease_expires_at = ?, updated_at = ?
3626
+ WHERE proposal_id = ? AND status = 'leased' AND lease_owner = ? AND lease_id = ?
3627
+ `).run(leaseExpiresAt, now, options.proposalId, options.workerId, options.leaseId);
3628
+ });
3629
+ return this.requireWorkerQueueItem(options.proposalId);
3630
+ }
3631
+ completeWorkerItem(proposalId, workerId, outcome, now = (/* @__PURE__ */ new Date()).toISOString(), leaseId) {
3112
3632
  this.transaction(() => {
3113
- this.assertWorkerLease(proposalId, workerId);
3633
+ this.assertWorkerLease(proposalId, workerId, leaseId);
3114
3634
  this.db.prepare(`
3115
3635
  UPDATE worker_queue
3116
- SET status = 'completed', lease_owner = NULL, lease_expires_at = NULL,
3117
- last_error_code = NULL, updated_at = ?
3636
+ SET status = 'completed', lease_owner = NULL, lease_id = NULL,
3637
+ lease_expires_at = NULL, last_error_code = NULL,
3638
+ terminal_outcome = ?, updated_at = ?
3118
3639
  WHERE proposal_id = ?
3119
- `).run(now, proposalId);
3120
- this.appendEvent(proposalId, "writeback_worker_completed", workerId, { outcome });
3640
+ `).run(outcome, now, proposalId);
3641
+ this.appendEvent(proposalId, "writeback_worker_completed", workerId, { outcome, lease_id: leaseId ?? null });
3121
3642
  });
3122
3643
  return this.requireWorkerQueueItem(proposalId);
3123
3644
  }
3124
3645
  retryWorkerItem(options) {
3125
3646
  const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3126
3647
  this.transaction(() => {
3127
- const item = this.assertWorkerLease(options.proposalId, options.workerId);
3648
+ const item = this.assertWorkerLease(options.proposalId, options.workerId, options.leaseId);
3128
3649
  const deadLetter = item.attempts >= item.max_attempts;
3129
3650
  this.db.prepare(`
3130
3651
  UPDATE worker_queue
3131
- SET status = ?, next_attempt_at = ?, lease_owner = NULL,
3132
- lease_expires_at = NULL, last_error_code = ?, updated_at = ?
3652
+ SET status = ?, next_attempt_at = ?, lease_owner = NULL, lease_id = NULL,
3653
+ lease_expires_at = NULL, last_error_code = ?, terminal_outcome = NULL,
3654
+ updated_at = ?
3133
3655
  WHERE proposal_id = ?
3134
3656
  `).run(deadLetter ? "dead_letter" : "retry_wait", options.retryAt, options.errorCode, now, options.proposalId);
3135
3657
  this.appendEvent(options.proposalId, deadLetter ? "writeback_dead_lettered" : "writeback_retry_scheduled", options.workerId, {
3136
3658
  attempt: item.attempts,
3137
3659
  max_attempts: item.max_attempts,
3138
3660
  error_code: options.errorCode,
3661
+ lease_id: options.leaseId ?? null,
3139
3662
  ...deadLetter ? {} : { retry_at: options.retryAt }
3140
3663
  });
3141
3664
  });
@@ -3144,17 +3667,95 @@ var ProposalStore = class {
3144
3667
  deadLetterWorkerItem(options) {
3145
3668
  const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3146
3669
  this.transaction(() => {
3147
- const item = this.assertWorkerLease(options.proposalId, options.workerId);
3670
+ const item = this.assertWorkerLease(options.proposalId, options.workerId, options.leaseId);
3148
3671
  this.db.prepare(`
3149
3672
  UPDATE worker_queue
3150
- SET status = 'dead_letter', lease_owner = NULL, lease_expires_at = NULL,
3151
- last_error_code = ?, updated_at = ?
3673
+ SET status = 'dead_letter', lease_owner = NULL, lease_id = NULL,
3674
+ lease_expires_at = NULL, last_error_code = ?,
3675
+ terminal_outcome = 'dead_letter', updated_at = ?
3152
3676
  WHERE proposal_id = ?
3153
3677
  `).run(options.errorCode, now, options.proposalId);
3154
3678
  this.appendEvent(options.proposalId, "writeback_dead_lettered", options.workerId, {
3155
3679
  attempt: item.attempts,
3156
3680
  max_attempts: item.max_attempts,
3157
- error_code: options.errorCode
3681
+ error_code: options.errorCode,
3682
+ lease_id: options.leaseId ?? null
3683
+ });
3684
+ });
3685
+ return this.requireWorkerQueueItem(options.proposalId);
3686
+ }
3687
+ blockWorkerItem(options) {
3688
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3689
+ this.transaction(() => {
3690
+ this.assertWorkerLease(options.proposalId, options.workerId, options.leaseId);
3691
+ this.db.prepare(`
3692
+ UPDATE worker_queue
3693
+ SET status = 'blocked', lease_owner = NULL, lease_id = NULL,
3694
+ lease_expires_at = NULL, last_error_code = ?,
3695
+ terminal_outcome = 'blocked', updated_at = ?
3696
+ WHERE proposal_id = ?
3697
+ `).run(options.errorCode, now, options.proposalId);
3698
+ this.appendEvent(options.proposalId, "writeback_worker_blocked", options.workerId, {
3699
+ error_code: options.errorCode,
3700
+ lease_id: options.leaseId
3701
+ });
3702
+ });
3703
+ return this.requireWorkerQueueItem(options.proposalId);
3704
+ }
3705
+ requireWorkerReconciliation(options) {
3706
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3707
+ this.transaction(() => {
3708
+ this.assertWorkerLease(options.proposalId, options.workerId, options.leaseId);
3709
+ this.db.prepare(`
3710
+ UPDATE worker_queue
3711
+ SET status = 'reconciliation_required', lease_owner = NULL,
3712
+ lease_id = NULL, lease_expires_at = NULL, last_error_code = ?,
3713
+ terminal_outcome = 'reconciliation_required', updated_at = ?
3714
+ WHERE proposal_id = ?
3715
+ `).run(options.errorCode, now, options.proposalId);
3716
+ this.appendEvent(options.proposalId, "writeback_reconciliation_required", options.workerId, {
3717
+ safe_error_code: options.errorCode,
3718
+ lease_id: options.leaseId
3719
+ });
3720
+ });
3721
+ return this.requireWorkerQueueItem(options.proposalId);
3722
+ }
3723
+ cancelWorkerItem(options) {
3724
+ const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
3725
+ const proposal = this.requireProposal(options.proposalId);
3726
+ assertOperatorDecision(
3727
+ proposal,
3728
+ "worker_cancel",
3729
+ options.actor,
3730
+ options.identity,
3731
+ options.require_verified_identity === true
3732
+ );
3733
+ this.transaction(() => {
3734
+ const item = this.requireWorkerQueueItem(options.proposalId);
3735
+ if (item.status !== "queued" && item.status !== "retry_wait") {
3736
+ throw new ProposalStoreError(
3737
+ "WORKER_ITEM_NOT_CANCELLABLE",
3738
+ `worker queue item ${options.proposalId} is ${item.status}, not safely cancellable before lease`
3739
+ );
3740
+ }
3741
+ this.db.prepare(`
3742
+ UPDATE worker_queue
3743
+ SET status = 'cancelled', lease_owner = NULL, lease_id = NULL,
3744
+ lease_expires_at = NULL, terminal_outcome = 'cancelled',
3745
+ updated_at = ?
3746
+ WHERE proposal_id = ?
3747
+ `).run(now, options.proposalId);
3748
+ if (proposal.state === "approved" || proposal.state === "pending_worker") {
3749
+ this.db.prepare(`
3750
+ UPDATE proposals
3751
+ SET state = 'canceled', updated_at = ?
3752
+ WHERE proposal_id = ?
3753
+ `).run(now, options.proposalId);
3754
+ }
3755
+ this.appendEvent(options.proposalId, "writeback_canceled", options.actor, {
3756
+ execution_mode: item.execution_mode,
3757
+ contract_digest: item.contract_digest ?? null,
3758
+ identity: options.identity ? publicIdentitySummary(options.identity) : null
3158
3759
  });
3159
3760
  });
3160
3761
  return this.requireWorkerQueueItem(options.proposalId);
@@ -3183,7 +3784,8 @@ var ProposalStore = class {
3183
3784
  this.db.prepare(`
3184
3785
  UPDATE worker_queue
3185
3786
  SET status = 'queued', attempts = 0, max_attempts = ?, next_attempt_at = ?,
3186
- lease_owner = NULL, lease_expires_at = NULL, last_error_code = NULL, updated_at = ?
3787
+ lease_owner = NULL, lease_id = NULL, lease_expires_at = NULL,
3788
+ last_error_code = NULL, terminal_outcome = NULL, updated_at = ?
3187
3789
  WHERE proposal_id = ?
3188
3790
  `).run(retryBudget, now, now, options.proposalId);
3189
3791
  if (proposal.state === "failed") {
@@ -3208,7 +3810,8 @@ var ProposalStore = class {
3208
3810
  }
3209
3811
  this.db.prepare(`
3210
3812
  UPDATE worker_queue
3211
- SET status = 'discarded', lease_owner = NULL, lease_expires_at = NULL, updated_at = ?
3813
+ SET status = 'discarded', lease_owner = NULL, lease_id = NULL,
3814
+ lease_expires_at = NULL, terminal_outcome = 'discarded', updated_at = ?
3212
3815
  WHERE proposal_id = ?
3213
3816
  `).run(now, options.proposalId);
3214
3817
  this.appendEvent(options.proposalId, "writeback_dead_letter_discarded", options.identity.subject, {
@@ -3226,9 +3829,9 @@ var ProposalStore = class {
3226
3829
  if (!item) throw new ProposalStoreError("WORKER_ITEM_NOT_FOUND", `worker queue item not found for ${proposalId}`);
3227
3830
  return item;
3228
3831
  }
3229
- assertWorkerLease(proposalId, workerId) {
3832
+ assertWorkerLease(proposalId, workerId, leaseId) {
3230
3833
  const item = this.requireWorkerQueueItem(proposalId);
3231
- if (item.status !== "leased" || item.lease_owner !== workerId) {
3834
+ if (item.status !== "leased" || item.lease_owner !== workerId || leaseId !== void 0 && item.lease_id !== leaseId) {
3232
3835
  throw new ProposalStoreError("WORKER_LEASE_MISMATCH", `worker ${workerId} does not hold the lease for ${proposalId}`);
3233
3836
  }
3234
3837
  return item;
@@ -3453,43 +4056,679 @@ var ProposalStore = class {
3453
4056
  clauses.push(`${column} = ?`);
3454
4057
  params.push(value);
3455
4058
  }
3456
- const sql = `SELECT * FROM policy_recommendations${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY created_at DESC, recommendation_id DESC`;
3457
- return this.db.prepare(sql).all(...params).map(rowToPolicyRecommendation).filter((item) => item !== void 0);
4059
+ const sql = `SELECT * FROM policy_recommendations${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY created_at DESC, recommendation_id DESC`;
4060
+ return this.db.prepare(sql).all(...params).map(rowToPolicyRecommendation).filter((item) => item !== void 0);
4061
+ }
4062
+ decidePolicyRecommendation(recommendationId, input) {
4063
+ const recommendation = this.requirePolicyRecommendation(recommendationId);
4064
+ if (recommendation.status !== "pending_review") throw new ProposalStoreError("POLICY_RECOMMENDATION_NOT_PENDING", `policy recommendation ${recommendationId} is ${recommendation.status}`);
4065
+ assertPolicyRecommendationIdentity(recommendation, input);
4066
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4067
+ const unsigned = {
4068
+ ...policyRecommendationUnsigned(recommendation),
4069
+ status: input.action === "approve" ? "approved" : "rejected",
4070
+ decision: { actor: input.actor, action: input.action, reason: input.reason, identity: input.identity, decided_at: now },
4071
+ updated_at: now
4072
+ };
4073
+ const updated = { ...unsigned, integrity_hash: canonicalJsonDigest(unsigned) };
4074
+ this.db.prepare("UPDATE policy_recommendations SET status = ?, payload_json = ?, integrity_hash = ?, updated_at = ? WHERE recommendation_id = ?").run(updated.status, JSON.stringify(unsigned), updated.integrity_hash, now, recommendationId);
4075
+ return updated;
4076
+ }
4077
+ markPolicyRecommendationExported(recommendationId, input) {
4078
+ const recommendation = this.requirePolicyRecommendation(recommendationId);
4079
+ if (recommendation.status !== "approved") throw new ProposalStoreError("POLICY_RECOMMENDATION_NOT_APPROVED", `policy recommendation ${recommendationId} is ${recommendation.status}`);
4080
+ if (!/^sha256:[a-f0-9]{64}$/.test(input.artifact_digest)) throw new ProposalStoreError("POLICY_ARTIFACT_DIGEST_INVALID", "policy recommendation export requires a canonical SHA-256 artifact digest");
4081
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4082
+ const unsigned = {
4083
+ ...policyRecommendationUnsigned(recommendation),
4084
+ status: "exported",
4085
+ export: { actor: input.actor, artifact_digest: input.artifact_digest, exported_at: now },
4086
+ updated_at: now
4087
+ };
4088
+ const updated = { ...unsigned, integrity_hash: canonicalJsonDigest(unsigned) };
4089
+ this.db.prepare("UPDATE policy_recommendations SET status = ?, payload_json = ?, integrity_hash = ?, updated_at = ? WHERE recommendation_id = ?").run(updated.status, JSON.stringify(unsigned), updated.integrity_hash, now, recommendationId);
4090
+ return updated;
4091
+ }
4092
+ requirePolicyRecommendation(recommendationId) {
4093
+ const recommendation = this.getPolicyRecommendation(recommendationId);
4094
+ if (!recommendation) throw new ProposalStoreError("POLICY_RECOMMENDATION_NOT_FOUND", `policy recommendation not found: ${recommendationId}`);
4095
+ return recommendation;
4096
+ }
4097
+ recordAttentionEvent(input) {
4098
+ let event;
4099
+ const record = () => {
4100
+ event = this.recordAttentionEventInternal(input);
4101
+ };
4102
+ if (this.db.isTransaction) record();
4103
+ else this.transaction(record);
4104
+ if (!event) throw new ProposalStoreError("ATTENTION_EVENT_CREATE_FAILED", "attention event was not persisted");
4105
+ return event;
4106
+ }
4107
+ listAttentionEvents(filters = {}) {
4108
+ const clauses = [];
4109
+ const params = [];
4110
+ if (filters.event_type) {
4111
+ clauses.push("event_type = ?");
4112
+ params.push(filters.event_type);
4113
+ }
4114
+ if (filters.severity) {
4115
+ clauses.push("severity = ?");
4116
+ params.push(filters.severity);
4117
+ }
4118
+ if (filters.proposal_id) {
4119
+ clauses.push("proposal_id = ?");
4120
+ params.push(filters.proposal_id);
4121
+ }
4122
+ if (filters.capability) {
4123
+ clauses.push("capability = ?");
4124
+ params.push(filters.capability);
4125
+ }
4126
+ if (filters.tenant) {
4127
+ clauses.push("EXISTS (SELECT 1 FROM proposals p WHERE p.proposal_id = attention_events.proposal_id AND p.tenant_id = ?)");
4128
+ params.push(filters.tenant);
4129
+ }
4130
+ if (filters.principal) {
4131
+ clauses.push("EXISTS (SELECT 1 FROM proposals p WHERE p.proposal_id = attention_events.proposal_id AND p.principal = ?)");
4132
+ params.push(filters.principal);
4133
+ }
4134
+ if (filters.from) {
4135
+ clauses.push("occurred_at >= ?");
4136
+ params.push(filters.from);
4137
+ }
4138
+ const limit = Math.max(1, Math.min(filters.limit ?? 200, 1e3));
4139
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
4140
+ return this.db.prepare(`
4141
+ SELECT *
4142
+ FROM attention_events
4143
+ ${where}
4144
+ ORDER BY occurred_at DESC, event_id DESC
4145
+ LIMIT ?
4146
+ `).all(...params, limit).map(rowToAttentionEvent).filter((event) => event !== void 0);
4147
+ }
4148
+ getAttentionEvent(eventId) {
4149
+ return rowToAttentionEvent(
4150
+ this.db.prepare("SELECT * FROM attention_events WHERE event_id = ?").get(eventId)
4151
+ );
4152
+ }
4153
+ listAttentionItems(filters = {}) {
4154
+ const clauses = [];
4155
+ const params = [];
4156
+ if (filters.status) {
4157
+ clauses.push("status = ?");
4158
+ params.push(filters.status);
4159
+ }
4160
+ if (filters.severity) {
4161
+ clauses.push("severity = ?");
4162
+ params.push(filters.severity);
4163
+ }
4164
+ if (filters.capability) {
4165
+ clauses.push("capability = ?");
4166
+ params.push(filters.capability);
4167
+ }
4168
+ if (filters.tenant) {
4169
+ clauses.push(`EXISTS (
4170
+ SELECT 1
4171
+ FROM attention_events ae
4172
+ JOIN proposals p ON p.proposal_id = ae.proposal_id
4173
+ WHERE ae.attention_key = attention_items.attention_key
4174
+ AND p.tenant_id = ?
4175
+ )`);
4176
+ params.push(filters.tenant);
4177
+ }
4178
+ if (filters.principal) {
4179
+ clauses.push(`EXISTS (
4180
+ SELECT 1
4181
+ FROM attention_events ae
4182
+ JOIN proposals p ON p.proposal_id = ae.proposal_id
4183
+ WHERE ae.attention_key = attention_items.attention_key
4184
+ AND p.principal = ?
4185
+ )`);
4186
+ params.push(filters.principal);
4187
+ }
4188
+ const limit = Math.max(1, Math.min(filters.limit ?? 200, 1e3));
4189
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
4190
+ return this.db.prepare(`
4191
+ SELECT *
4192
+ FROM attention_items
4193
+ ${where}
4194
+ ORDER BY
4195
+ CASE severity WHEN 'critical' THEN 0 WHEN 'warning' THEN 1 ELSE 2 END,
4196
+ last_seen_at DESC,
4197
+ attention_id DESC
4198
+ LIMIT ?
4199
+ `).all(...params, limit).map(rowToAttentionItem).filter((item) => item !== void 0);
4200
+ }
4201
+ getAttentionItem(attentionId) {
4202
+ return rowToAttentionItem(this.db.prepare("SELECT * FROM attention_items WHERE attention_id = ?").get(attentionId));
4203
+ }
4204
+ acknowledgeAttention(input) {
4205
+ const actor = boundedSafeLabel(input.actor, "attention acknowledgement actor", 256);
4206
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4207
+ this.transaction(() => {
4208
+ const item = this.requireAttentionItem(input.attention_id);
4209
+ if (item.status !== "open") {
4210
+ throw new ProposalStoreError("ATTENTION_ITEM_NOT_ACKNOWLEDGEABLE", `attention item ${item.attention_id} is ${item.status}`);
4211
+ }
4212
+ assertAttentionOperatorDecision(
4213
+ item,
4214
+ actor,
4215
+ input.identity,
4216
+ input.require_verified_identity === true
4217
+ );
4218
+ this.db.prepare(`
4219
+ UPDATE attention_items
4220
+ SET status = 'acknowledged', acknowledged_by = ?, acknowledged_at = ?,
4221
+ acknowledgement_identity_json = ?,
4222
+ acknowledgement_decision_hash = ?,
4223
+ acknowledgement_signature = ?,
4224
+ acknowledgement_integrity_hash = ?,
4225
+ last_seen_at = MAX(last_seen_at, ?)
4226
+ WHERE attention_id = ?
4227
+ `).run(
4228
+ actor,
4229
+ now,
4230
+ input.identity ? JSON.stringify(input.identity) : null,
4231
+ input.identity?.decision_hash ?? null,
4232
+ input.identity?.signature ?? null,
4233
+ input.identity?.integrity_hash ?? null,
4234
+ now,
4235
+ item.attention_id
4236
+ );
4237
+ });
4238
+ return this.requireAttentionItem(input.attention_id);
4239
+ }
4240
+ resolveAttention(input) {
4241
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4242
+ this.transaction(() => {
4243
+ const item = this.requireAttentionItem(input.attention_id);
4244
+ if (item.status === "expired") {
4245
+ throw new ProposalStoreError("ATTENTION_ITEM_NOT_RESOLVABLE", `attention item ${item.attention_id} is expired`);
4246
+ }
4247
+ this.db.prepare(`
4248
+ UPDATE attention_items
4249
+ SET status = 'resolved', resolved_at = ?, last_seen_at = MAX(last_seen_at, ?)
4250
+ WHERE attention_id = ?
4251
+ `).run(now, now, item.attention_id);
4252
+ });
4253
+ return this.requireAttentionItem(input.attention_id);
4254
+ }
4255
+ enqueueNotificationDelivery(input) {
4256
+ const sinkId = boundedSinkId(input.sink_id);
4257
+ const event = this.requireAttentionEvent(input.event_id);
4258
+ const attention = input.attention_id ? this.requireAttentionItem(input.attention_id) : void 0;
4259
+ if (attention && event.attention_key !== attention.attention_key) {
4260
+ throw new ProposalStoreError(
4261
+ "NOTIFICATION_ATTENTION_MISMATCH",
4262
+ `attention event ${event.event_id} does not belong to attention item ${attention.attention_id}`
4263
+ );
4264
+ }
4265
+ const maxAttempts = Math.max(1, Math.min(input.max_attempts ?? 5, 100));
4266
+ const status = input.status ?? "pending";
4267
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4268
+ if (!Number.isFinite(Date.parse(now))) {
4269
+ throw new ProposalStoreError("NOTIFICATION_TIME_INVALID", "notification delivery time must be an ISO timestamp");
4270
+ }
4271
+ const nextAttemptAt = input.next_attempt_at ?? now;
4272
+ if (!Number.isFinite(Date.parse(nextAttemptAt))) {
4273
+ throw new ProposalStoreError(
4274
+ "NOTIFICATION_TIME_INVALID",
4275
+ "notification delivery next-attempt time must be an ISO timestamp"
4276
+ );
4277
+ }
4278
+ const deliveryId = notificationDeliveryId(sinkId, event.event_id);
4279
+ this.transaction(() => {
4280
+ const existing = this.getNotificationDelivery(deliveryId);
4281
+ if (existing) {
4282
+ if (existing.sink_id !== sinkId || existing.event_id !== event.event_id || existing.attention_id !== attention?.attention_id) {
4283
+ throw new ProposalStoreError(
4284
+ "NOTIFICATION_DELIVERY_IDEMPOTENCY_MISMATCH",
4285
+ `notification delivery ${deliveryId} already exists with different immutable routing`
4286
+ );
4287
+ }
4288
+ return;
4289
+ }
4290
+ this.db.prepare(`
4291
+ INSERT INTO notification_deliveries (
4292
+ delivery_id, sink_id, event_id, attention_id, status, attempts,
4293
+ max_attempts, next_attempt_at, lease_owner, lease_id,
4294
+ lease_expires_at, last_error_code, external_reference, delivered_at,
4295
+ created_at, updated_at
4296
+ ) VALUES (?, ?, ?, ?, ?, 0, ?, ?, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?)
4297
+ `).run(
4298
+ deliveryId,
4299
+ sinkId,
4300
+ event.event_id,
4301
+ attention?.attention_id ?? null,
4302
+ status,
4303
+ maxAttempts,
4304
+ nextAttemptAt,
4305
+ now,
4306
+ now
4307
+ );
4308
+ });
4309
+ return this.requireNotificationDelivery(deliveryId);
4310
+ }
4311
+ includeNotificationDeliveriesInDigest(input) {
4312
+ const sinkId = boundedSinkId(input.sink_id);
4313
+ const deliveryIds = [...new Set(input.delivery_ids)];
4314
+ if (deliveryIds.length === 0 || deliveryIds.length > 1e3) {
4315
+ throw new ProposalStoreError(
4316
+ "NOTIFICATION_DIGEST_MEMBERS_INVALID",
4317
+ "a notification digest must contain from 1 through 1000 delivery ids"
4318
+ );
4319
+ }
4320
+ const digestEvent = this.requireAttentionEvent(input.digest_event_id);
4321
+ if (digestEvent.event_type !== "notification.digest") {
4322
+ throw new ProposalStoreError(
4323
+ "NOTIFICATION_DIGEST_EVENT_INVALID",
4324
+ "notification digest membership requires a notification.digest event"
4325
+ );
4326
+ }
4327
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4328
+ if (!Number.isFinite(Date.parse(now))) {
4329
+ throw new ProposalStoreError("NOTIFICATION_TIME_INVALID", "notification digest time must be an ISO timestamp");
4330
+ }
4331
+ let included = 0;
4332
+ this.transaction(() => {
4333
+ for (const deliveryId of deliveryIds) {
4334
+ const item = this.requireNotificationDelivery(deliveryId);
4335
+ const digestReference = `digest:${digestEvent.event_id}`;
4336
+ if (item.status === "suppressed" && item.sink_id === sinkId && item.external_reference === digestReference) {
4337
+ continue;
4338
+ }
4339
+ if (item.sink_id !== sinkId || item.status !== "batched") {
4340
+ throw new ProposalStoreError(
4341
+ "NOTIFICATION_DIGEST_MEMBER_STATE_INVALID",
4342
+ `notification delivery ${deliveryId} is not a batched member of sink ${sinkId}`
4343
+ );
4344
+ }
4345
+ this.db.prepare(`
4346
+ UPDATE notification_deliveries
4347
+ SET status = 'suppressed', external_reference = ?, updated_at = ?
4348
+ WHERE delivery_id = ?
4349
+ `).run(digestReference, now, deliveryId);
4350
+ included += 1;
4351
+ }
4352
+ });
4353
+ return included;
4354
+ }
4355
+ claimNotificationDeliveries(input) {
4356
+ const owner = boundedSafeLabel(input.owner, "notification dispatcher identity", 128);
4357
+ const sinkId = input.sink_id ? boundedSinkId(input.sink_id) : void 0;
4358
+ const limit = Math.max(1, Math.min(input.limit ?? 20, 100));
4359
+ const leaseSeconds = Math.max(15, Math.min(input.lease_seconds ?? 60, 3600));
4360
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4361
+ if (!Number.isFinite(Date.parse(now))) {
4362
+ throw new ProposalStoreError("NOTIFICATION_TIME_INVALID", "notification claim time must be an ISO timestamp");
4363
+ }
4364
+ const leaseExpiresAt = new Date(Date.parse(now) + leaseSeconds * 1e3).toISOString();
4365
+ const claimed = [];
4366
+ this.transaction(() => {
4367
+ const rows = this.db.prepare(`
4368
+ SELECT *
4369
+ FROM notification_deliveries
4370
+ WHERE (
4371
+ (status IN ('pending', 'retry_wait') AND next_attempt_at <= ?)
4372
+ OR (status = 'leased' AND lease_expires_at <= ?)
4373
+ )
4374
+ AND (? IS NULL OR sink_id = ?)
4375
+ ORDER BY next_attempt_at ASC, created_at ASC, delivery_id ASC
4376
+ LIMIT ?
4377
+ `).all(now, now, sinkId ?? null, sinkId ?? null, limit);
4378
+ for (const row of rows) {
4379
+ const item = rowToNotificationDelivery(row);
4380
+ if (!item) continue;
4381
+ const leaseId = notificationLeaseId(item.delivery_id, owner, item.attempts + 1, now);
4382
+ this.db.prepare(`
4383
+ UPDATE notification_deliveries
4384
+ SET status = 'leased', attempts = attempts + 1, lease_owner = ?,
4385
+ lease_id = ?, lease_expires_at = ?, last_error_code = NULL,
4386
+ updated_at = ?
4387
+ WHERE delivery_id = ?
4388
+ `).run(owner, leaseId, leaseExpiresAt, now, item.delivery_id);
4389
+ claimed.push(this.requireNotificationDelivery(item.delivery_id));
4390
+ }
4391
+ });
4392
+ return claimed;
4393
+ }
4394
+ completeNotificationDelivery(input) {
4395
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4396
+ const externalReference = input.external_reference ? boundedSafeLabel(input.external_reference, "notification external reference", 256) : void 0;
4397
+ this.transaction(() => {
4398
+ this.assertNotificationLease(input.delivery_id, input.owner, input.lease_id, now);
4399
+ this.db.prepare(`
4400
+ UPDATE notification_deliveries
4401
+ SET status = 'delivered', lease_owner = NULL, lease_id = NULL,
4402
+ lease_expires_at = NULL, last_error_code = NULL,
4403
+ external_reference = COALESCE(?, external_reference),
4404
+ delivered_at = ?, updated_at = ?
4405
+ WHERE delivery_id = ?
4406
+ `).run(externalReference ?? null, now, now, input.delivery_id);
4407
+ });
4408
+ return this.requireNotificationDelivery(input.delivery_id);
4409
+ }
4410
+ failNotificationDelivery(input) {
4411
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4412
+ const errorCode = boundedSafeErrorCode(input.error_code);
4413
+ this.transaction(() => {
4414
+ const item = this.assertNotificationLease(input.delivery_id, input.owner, input.lease_id, now);
4415
+ const retryable = input.retryable && item.attempts < item.max_attempts;
4416
+ const retryAt = retryable ? input.retry_at ?? new Date(Date.parse(now) + 1e3).toISOString() : now;
4417
+ if (!Number.isFinite(Date.parse(retryAt))) {
4418
+ throw new ProposalStoreError("NOTIFICATION_RETRY_TIME_INVALID", "notification retry time must be an ISO timestamp");
4419
+ }
4420
+ this.db.prepare(`
4421
+ UPDATE notification_deliveries
4422
+ SET status = ?, next_attempt_at = ?, lease_owner = NULL, lease_id = NULL,
4423
+ lease_expires_at = NULL, last_error_code = ?, updated_at = ?
4424
+ WHERE delivery_id = ?
4425
+ `).run(retryable ? "retry_wait" : "dead_letter", retryAt, errorCode, now, input.delivery_id);
4426
+ });
4427
+ return this.requireNotificationDelivery(input.delivery_id);
4428
+ }
4429
+ listNotificationDeliveries(filters = {}) {
4430
+ const clauses = [];
4431
+ const params = [];
4432
+ for (const [column, value] of [
4433
+ ["status", filters.status],
4434
+ ["sink_id", filters.sink_id],
4435
+ ["event_id", filters.event_id],
4436
+ ["attention_id", filters.attention_id]
4437
+ ]) {
4438
+ if (!value) continue;
4439
+ clauses.push(`${column} = ?`);
4440
+ params.push(value);
4441
+ }
4442
+ const limit = Math.max(1, Math.min(filters.limit ?? 200, 1e3));
4443
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
4444
+ return this.db.prepare(`
4445
+ SELECT *
4446
+ FROM notification_deliveries
4447
+ ${where}
4448
+ ORDER BY created_at DESC, delivery_id DESC
4449
+ LIMIT ?
4450
+ `).all(...params, limit).map(rowToNotificationDelivery).filter((delivery) => delivery !== void 0);
4451
+ }
4452
+ getNotificationDelivery(deliveryId) {
4453
+ return rowToNotificationDelivery(
4454
+ this.db.prepare("SELECT * FROM notification_deliveries WHERE delivery_id = ?").get(deliveryId)
4455
+ );
4456
+ }
4457
+ requeueNotificationDelivery(input) {
4458
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4459
+ const reason = boundedSafeLabel(input.reason, "notification replay reason", 256);
4460
+ this.transaction(() => {
4461
+ const item = this.requireNotificationDelivery(input.delivery_id);
4462
+ if (!["dead_letter", "suppressed", "batched"].includes(item.status)) {
4463
+ throw new ProposalStoreError(
4464
+ "NOTIFICATION_DELIVERY_NOT_REQUEUEABLE",
4465
+ `notification delivery ${item.delivery_id} is ${item.status}`
4466
+ );
4467
+ }
4468
+ assertNotificationReplayOperatorDecision(item, input.identity, reason);
4469
+ const event = this.requireAttentionEvent(item.event_id);
4470
+ this.db.prepare(`
4471
+ UPDATE notification_deliveries
4472
+ SET status = 'pending', attempts = 0, next_attempt_at = ?,
4473
+ lease_owner = NULL, lease_id = NULL, lease_expires_at = NULL,
4474
+ last_error_code = NULL, delivered_at = NULL, updated_at = ?
4475
+ WHERE delivery_id = ?
4476
+ `).run(now, now, item.delivery_id);
4477
+ this.recordAttentionEventInternal({
4478
+ event_type: "notification.replayed",
4479
+ severity: "informational",
4480
+ environment: event.environment,
4481
+ ...event.proposal_id ? { proposal_id: event.proposal_id } : {},
4482
+ ...event.job_id ? { job_id: event.job_id } : {},
4483
+ ...event.operation_id ? { operation_id: event.operation_id } : {},
4484
+ ...event.correlation_id ? { correlation_id: event.correlation_id } : {},
4485
+ ...event.capability ? { capability: event.capability } : {},
4486
+ ...event.contract_digest ? { contract_digest: event.contract_digest } : {},
4487
+ attention_required: false,
4488
+ immediate_default: false,
4489
+ summary: `Notification delivery ${item.delivery_id} requeued by a verified operator`,
4490
+ ...event.workbench_path ? { workbench_path: event.workbench_path } : {},
4491
+ details: {
4492
+ delivery_id: item.delivery_id,
4493
+ sink_id: item.sink_id,
4494
+ replayed_event_id: item.event_id,
4495
+ operator_subject: input.identity.subject,
4496
+ identity_provider: input.identity.provider,
4497
+ operator_decision_hash: input.identity.decision_hash,
4498
+ reason,
4499
+ approval_replayed: false,
4500
+ mutation_replayed: false,
4501
+ source_database_changed: false
4502
+ },
4503
+ source_event_key: `notification-replay:${item.delivery_id}:${input.identity.decision_hash}`,
4504
+ now
4505
+ });
4506
+ });
4507
+ return this.requireNotificationDelivery(input.delivery_id);
4508
+ }
4509
+ recordAttentionEventInternal(input) {
4510
+ assertAttentionEventInput(input);
4511
+ const occurredAt = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4512
+ const environment = boundedSafeLabel(input.environment, "attention environment", 64);
4513
+ const capability = input.capability ? boundedSafeLabel(input.capability, "attention capability", 256) : void 0;
4514
+ const contractDigest = input.contract_digest;
4515
+ const attentionRequired = input.attention_required ?? defaultAttentionEventTypes.has(input.event_type);
4516
+ const immediateDefault = input.immediate_default ?? defaultImmediateAttentionEventTypes.has(input.event_type);
4517
+ const details = Object.fromEntries(
4518
+ Object.entries(input.details ?? {}).sort(([left], [right]) => left.localeCompare(right))
4519
+ );
4520
+ const sourceEventKey = input.source_event_key ? boundedSafeLabel(input.source_event_key, "attention source event key", 512) : canonicalJsonDigest({
4521
+ event_type: input.event_type,
4522
+ occurred_at: occurredAt,
4523
+ proposal_id: input.proposal_id ?? null,
4524
+ job_id: input.job_id ?? null,
4525
+ operation_id: input.operation_id ?? null,
4526
+ capability: capability ?? null,
4527
+ details
4528
+ });
4529
+ const eventId = attentionEventId(sourceEventKey, input.event_type);
4530
+ const attentionKey = attentionRequired ? boundedSafeLabel(
4531
+ input.attention_key ?? defaultAttentionKey({
4532
+ environment,
4533
+ event_type: input.event_type,
4534
+ capability,
4535
+ contract_digest: contractDigest,
4536
+ details
4537
+ }),
4538
+ "attention coalescing key",
4539
+ 512
4540
+ ) : void 0;
4541
+ const unsigned = {
4542
+ schema_version: "synapsor.attention-event.v1",
4543
+ event_id: eventId,
4544
+ event_type: input.event_type,
4545
+ severity: input.severity,
4546
+ occurred_at: occurredAt,
4547
+ environment,
4548
+ ...input.proposal_id ? { proposal_id: boundedSafeLabel(input.proposal_id, "attention proposal id", 256) } : {},
4549
+ ...input.job_id ? { job_id: boundedSafeLabel(input.job_id, "attention job id", 256) } : {},
4550
+ ...input.operation_id ? { operation_id: boundedSafeLabel(input.operation_id, "attention operation id", 256) } : {},
4551
+ ...input.correlation_id ? { correlation_id: boundedSafeLabel(input.correlation_id, "attention correlation id", 256) } : {},
4552
+ ...capability ? { capability } : {},
4553
+ ...contractDigest ? { contract_digest: contractDigest } : {},
4554
+ ...attentionKey ? { attention_key: attentionKey } : {},
4555
+ attention_required: attentionRequired,
4556
+ immediate_default: immediateDefault,
4557
+ summary: boundedSafeLabel(input.summary ?? attentionEventTitle(input.event_type), "attention summary", 512),
4558
+ ...input.approval_source ? { approval_source: input.approval_source } : {},
4559
+ ...input.worker_state ? { worker_state: boundedSafeLabel(input.worker_state, "attention worker state", 128) } : {},
4560
+ ...input.failure_class ? { failure_class: boundedSafeLabel(input.failure_class, "attention failure class", 128) } : {},
4561
+ ...input.expires_at ? { expires_at: input.expires_at } : {},
4562
+ ...input.workbench_path ? { workbench_path: boundedWorkbenchPath(input.workbench_path) } : {},
4563
+ details
4564
+ };
4565
+ assertNoSecretMaterial(unsigned, `attention_event.${eventId}`);
4566
+ const event = {
4567
+ ...unsigned,
4568
+ payload_hash: canonicalJsonDigest(unsigned)
4569
+ };
4570
+ const existing = rowToAttentionEvent(this.db.prepare("SELECT * FROM attention_events WHERE event_id = ?").get(eventId));
4571
+ if (existing) {
4572
+ if (existing.payload_hash !== event.payload_hash) {
4573
+ throw new ProposalStoreError(
4574
+ "ATTENTION_EVENT_IDEMPOTENCY_MISMATCH",
4575
+ `attention event ${eventId} already exists with different immutable content`
4576
+ );
4577
+ }
4578
+ return existing;
4579
+ }
4580
+ this.db.prepare(`
4581
+ INSERT INTO attention_events (
4582
+ event_id, schema_version, event_type, severity, occurred_at, environment,
4583
+ proposal_id, job_id, operation_id, correlation_id, capability,
4584
+ contract_digest, attention_key, attention_required, immediate_default,
4585
+ summary, approval_source, worker_state, failure_class, expires_at,
4586
+ workbench_path, details_json, payload_hash, created_at
4587
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
4588
+ `).run(
4589
+ event.event_id,
4590
+ event.schema_version,
4591
+ event.event_type,
4592
+ event.severity,
4593
+ event.occurred_at,
4594
+ event.environment,
4595
+ event.proposal_id ?? null,
4596
+ event.job_id ?? null,
4597
+ event.operation_id ?? null,
4598
+ event.correlation_id ?? null,
4599
+ event.capability ?? null,
4600
+ event.contract_digest ?? null,
4601
+ event.attention_key ?? null,
4602
+ event.attention_required ? 1 : 0,
4603
+ event.immediate_default ? 1 : 0,
4604
+ event.summary,
4605
+ event.approval_source ?? null,
4606
+ event.worker_state ?? null,
4607
+ event.failure_class ?? null,
4608
+ event.expires_at ?? null,
4609
+ event.workbench_path ?? null,
4610
+ JSON.stringify(event.details),
4611
+ event.payload_hash,
4612
+ event.occurred_at
4613
+ );
4614
+ if (event.attention_required && event.attention_key) this.projectAttentionItem(event);
4615
+ if (event.proposal_id && event.event_type === "proposal.expired") {
4616
+ this.closeProposalExpiryAttention(event, "expired");
4617
+ } else if (event.proposal_id && (event.event_type === "proposal.applied" || event.event_type === "proposal.cancelled" || event.event_type === "proposal.refused")) {
4618
+ this.closeProposalExpiryAttention(event, "resolved");
4619
+ }
4620
+ return event;
4621
+ }
4622
+ closeProposalExpiryAttention(event, status) {
4623
+ if (!event.proposal_id) return;
4624
+ this.db.prepare(`
4625
+ UPDATE attention_items
4626
+ SET status = ?,
4627
+ event_type = ?,
4628
+ title = ?,
4629
+ latest_event_id = ?,
4630
+ resolved_at = CASE WHEN ? = 'resolved' THEN ? ELSE NULL END,
4631
+ last_seen_at = MAX(last_seen_at, ?)
4632
+ WHERE status IN ('open', 'acknowledged')
4633
+ AND attention_key IN (
4634
+ SELECT DISTINCT attention_key
4635
+ FROM attention_events
4636
+ WHERE proposal_id = ?
4637
+ AND event_type = 'proposal.expiring'
4638
+ AND attention_key IS NOT NULL
4639
+ )
4640
+ `).run(
4641
+ status,
4642
+ event.event_type,
4643
+ event.summary,
4644
+ event.event_id,
4645
+ status,
4646
+ event.occurred_at,
4647
+ event.occurred_at,
4648
+ event.proposal_id
4649
+ );
4650
+ }
4651
+ projectAttentionItem(event) {
4652
+ const attentionKey = event.attention_key;
4653
+ if (!attentionKey) return;
4654
+ const existing = rowToAttentionItem(this.db.prepare("SELECT * FROM attention_items WHERE attention_key = ?").get(attentionKey));
4655
+ if (!existing) {
4656
+ this.db.prepare(`
4657
+ INSERT INTO attention_items (
4658
+ attention_id, attention_key, status, severity, environment, event_type,
4659
+ capability, contract_digest, title, occurrence_count, first_event_id,
4660
+ latest_event_id, first_seen_at, last_seen_at, acknowledged_by,
4661
+ acknowledged_at, resolved_at, expires_at
4662
+ ) VALUES (?, ?, 'open', ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, NULL, NULL, NULL, ?)
4663
+ `).run(
4664
+ attentionItemId(attentionKey),
4665
+ attentionKey,
4666
+ event.severity,
4667
+ event.environment,
4668
+ event.event_type,
4669
+ event.capability ?? null,
4670
+ event.contract_digest ?? null,
4671
+ event.summary,
4672
+ event.event_id,
4673
+ event.event_id,
4674
+ event.occurred_at,
4675
+ event.occurred_at,
4676
+ event.expires_at ?? null
4677
+ );
4678
+ return;
4679
+ }
4680
+ const severity = attentionSeverityRank(event.severity) > attentionSeverityRank(existing.severity) ? event.severity : existing.severity;
4681
+ this.db.prepare(`
4682
+ UPDATE attention_items
4683
+ SET status = 'open', severity = ?, event_type = ?, title = ?,
4684
+ occurrence_count = occurrence_count + 1, latest_event_id = ?,
4685
+ last_seen_at = ?, acknowledged_by = NULL, acknowledged_at = NULL,
4686
+ acknowledgement_identity_json = NULL,
4687
+ acknowledgement_decision_hash = NULL,
4688
+ acknowledgement_signature = NULL,
4689
+ acknowledgement_integrity_hash = NULL,
4690
+ resolved_at = NULL,
4691
+ expires_at = COALESCE(?, expires_at)
4692
+ WHERE attention_id = ?
4693
+ `).run(
4694
+ severity,
4695
+ event.event_type,
4696
+ event.summary,
4697
+ event.event_id,
4698
+ event.occurred_at,
4699
+ event.expires_at ?? null,
4700
+ existing.attention_id
4701
+ );
3458
4702
  }
3459
- decidePolicyRecommendation(recommendationId, input) {
3460
- const recommendation = this.requirePolicyRecommendation(recommendationId);
3461
- if (recommendation.status !== "pending_review") throw new ProposalStoreError("POLICY_RECOMMENDATION_NOT_PENDING", `policy recommendation ${recommendationId} is ${recommendation.status}`);
3462
- assertPolicyRecommendationIdentity(recommendation, input);
3463
- const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
3464
- const unsigned = {
3465
- ...policyRecommendationUnsigned(recommendation),
3466
- status: input.action === "approve" ? "approved" : "rejected",
3467
- decision: { actor: input.actor, action: input.action, reason: input.reason, identity: input.identity, decided_at: now },
3468
- updated_at: now
3469
- };
3470
- const updated = { ...unsigned, integrity_hash: canonicalJsonDigest(unsigned) };
3471
- this.db.prepare("UPDATE policy_recommendations SET status = ?, payload_json = ?, integrity_hash = ?, updated_at = ? WHERE recommendation_id = ?").run(updated.status, JSON.stringify(unsigned), updated.integrity_hash, now, recommendationId);
3472
- return updated;
4703
+ requireAttentionItem(attentionId) {
4704
+ const item = this.getAttentionItem(attentionId);
4705
+ if (!item) throw new ProposalStoreError("ATTENTION_ITEM_NOT_FOUND", `attention item not found: ${attentionId}`);
4706
+ return item;
3473
4707
  }
3474
- markPolicyRecommendationExported(recommendationId, input) {
3475
- const recommendation = this.requirePolicyRecommendation(recommendationId);
3476
- if (recommendation.status !== "approved") throw new ProposalStoreError("POLICY_RECOMMENDATION_NOT_APPROVED", `policy recommendation ${recommendationId} is ${recommendation.status}`);
3477
- if (!/^sha256:[a-f0-9]{64}$/.test(input.artifact_digest)) throw new ProposalStoreError("POLICY_ARTIFACT_DIGEST_INVALID", "policy recommendation export requires a canonical SHA-256 artifact digest");
3478
- const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
3479
- const unsigned = {
3480
- ...policyRecommendationUnsigned(recommendation),
3481
- status: "exported",
3482
- export: { actor: input.actor, artifact_digest: input.artifact_digest, exported_at: now },
3483
- updated_at: now
3484
- };
3485
- const updated = { ...unsigned, integrity_hash: canonicalJsonDigest(unsigned) };
3486
- this.db.prepare("UPDATE policy_recommendations SET status = ?, payload_json = ?, integrity_hash = ?, updated_at = ? WHERE recommendation_id = ?").run(updated.status, JSON.stringify(unsigned), updated.integrity_hash, now, recommendationId);
3487
- return updated;
4708
+ requireAttentionEvent(eventId) {
4709
+ const event = rowToAttentionEvent(this.db.prepare("SELECT * FROM attention_events WHERE event_id = ?").get(eventId));
4710
+ if (!event) throw new ProposalStoreError("ATTENTION_EVENT_NOT_FOUND", `attention event not found: ${eventId}`);
4711
+ return event;
3488
4712
  }
3489
- requirePolicyRecommendation(recommendationId) {
3490
- const recommendation = this.getPolicyRecommendation(recommendationId);
3491
- if (!recommendation) throw new ProposalStoreError("POLICY_RECOMMENDATION_NOT_FOUND", `policy recommendation not found: ${recommendationId}`);
3492
- return recommendation;
4713
+ requireNotificationDelivery(deliveryId) {
4714
+ const delivery = this.getNotificationDelivery(deliveryId);
4715
+ if (!delivery) {
4716
+ throw new ProposalStoreError("NOTIFICATION_DELIVERY_NOT_FOUND", `notification delivery not found: ${deliveryId}`);
4717
+ }
4718
+ return delivery;
4719
+ }
4720
+ assertNotificationLease(deliveryId, owner, leaseId, now) {
4721
+ const item = this.requireNotificationDelivery(deliveryId);
4722
+ if (item.status !== "leased" || item.lease_owner !== owner || item.lease_id !== leaseId) {
4723
+ throw new ProposalStoreError(
4724
+ "NOTIFICATION_LEASE_MISMATCH",
4725
+ `dispatcher ${owner} does not hold lease ${leaseId} for ${deliveryId}`
4726
+ );
4727
+ }
4728
+ if (!item.lease_expires_at || Date.parse(item.lease_expires_at) <= Date.parse(now)) {
4729
+ throw new ProposalStoreError("NOTIFICATION_LEASE_EXPIRED", `notification lease ${leaseId} has expired`);
4730
+ }
4731
+ return item;
3493
4732
  }
3494
4733
  enqueueCloudOutbox(input) {
3495
4734
  const eventId = input.event_id.trim();
@@ -3666,6 +4905,92 @@ var ProposalStore = class {
3666
4905
  if (!item) throw new ProposalStoreError("CLOUD_OUTBOX_EVENT_NOT_FOUND", `cloud outbox event not found: ${eventId}`);
3667
4906
  return item;
3668
4907
  }
4908
+ workerControlState() {
4909
+ const raw = this.getRunnerState("supervised_worker_control");
4910
+ if (!raw) return defaultWorkerControlState();
4911
+ return parseWorkerControlState(raw);
4912
+ }
4913
+ updateWorkerControl(input) {
4914
+ const now = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
4915
+ const current = this.workerControlState();
4916
+ assertWorkerControlTarget(input);
4917
+ assertWorkerControlOperatorDecision(
4918
+ current,
4919
+ input,
4920
+ input.actor,
4921
+ input.identity,
4922
+ input.require_verified_identity === true
4923
+ );
4924
+ let mode = current.mode;
4925
+ let controls = [...current.capability_controls];
4926
+ if (input.action === "pause") mode = "paused";
4927
+ else if (input.action === "resume") mode = "active";
4928
+ else if (input.action === "drain") mode = "draining";
4929
+ else {
4930
+ const capability = input.capability;
4931
+ const contractDigest = input.contract_digest;
4932
+ const keyMatches = (entry) => entry.capability === capability && entry.contract_digest === contractDigest;
4933
+ const existing = controls.find(keyMatches);
4934
+ if (existing?.status === "revoked" && input.action === "capability_enable") {
4935
+ throw new ProposalStoreError(
4936
+ "WORKER_DIGEST_REVOKED",
4937
+ `revoked supervised-worker digest ${contractDigest} cannot be re-enabled`
4938
+ );
4939
+ }
4940
+ const status = input.action === "capability_enable" ? "enabled" : input.action === "capability_disable" ? "disabled" : "revoked";
4941
+ controls = [
4942
+ ...controls.filter((entry) => !keyMatches(entry)),
4943
+ {
4944
+ capability,
4945
+ contract_digest: contractDigest,
4946
+ status,
4947
+ updated_at: now,
4948
+ actor: input.actor
4949
+ }
4950
+ ].sort((left, right) => left.capability.localeCompare(right.capability) || left.contract_digest.localeCompare(right.contract_digest));
4951
+ }
4952
+ const unsigned = {
4953
+ schema_version: "synapsor.worker-control.v1",
4954
+ mode,
4955
+ revision: current.revision + 1,
4956
+ capability_controls: controls,
4957
+ ...input.identity ? { last_decision: input.identity } : {},
4958
+ updated_at: now
4959
+ };
4960
+ const updated = {
4961
+ ...unsigned,
4962
+ integrity_hash: canonicalJsonDigest(unsigned)
4963
+ };
4964
+ assertNoSecretMaterial(updated, "runner_state.supervised_worker_control");
4965
+ this.transaction(() => {
4966
+ this.db.prepare(`
4967
+ INSERT INTO runner_state (key, value_json, updated_at)
4968
+ VALUES ('supervised_worker_control', ?, ?)
4969
+ ON CONFLICT(key) DO UPDATE SET value_json = excluded.value_json, updated_at = excluded.updated_at
4970
+ `).run(JSON.stringify(updated), now);
4971
+ const global = input.action === "pause" || input.action === "resume" || input.action === "drain";
4972
+ const eventType = input.action === "resume" || input.action === "capability_enable" ? "capability.activated" : global ? "worker.paused" : "capability.revoked";
4973
+ this.recordAttentionEventInternal({
4974
+ event_type: eventType,
4975
+ severity: "informational",
4976
+ environment: input.environment ? boundedSafeLabel(input.environment, "worker control environment", 64) : "unknown",
4977
+ ...input.capability ? { capability: input.capability } : {},
4978
+ ...input.contract_digest ? { contract_digest: input.contract_digest } : {},
4979
+ attention_required: false,
4980
+ immediate_default: false,
4981
+ summary: workerControlSummary(input),
4982
+ worker_state: mode,
4983
+ details: {
4984
+ control_action: input.action,
4985
+ control_revision: updated.revision,
4986
+ source_database_changed: false
4987
+ },
4988
+ source_event_key: `worker-control:${updated.revision}:${updated.integrity_hash}`,
4989
+ now
4990
+ });
4991
+ });
4992
+ return updated;
4993
+ }
3669
4994
  setRunnerState(key, value) {
3670
4995
  assertNoSecretMaterial(value, `runner_state.${key}`);
3671
4996
  this.db.prepare(`
@@ -3697,6 +5022,9 @@ var ProposalStore = class {
3697
5022
  { table: "shadow_study_cases", kind: "shadow_study_case", key: "case_id", created: "created_at", proposal: "proposal_id", tenant: "tenant_id", capability: "capability" },
3698
5023
  { table: "shadow_outcomes", kind: "shadow_outcome", key: "outcome_id", created: "created_at", proposal: "proposal_id", tenant: "tenant_id" },
3699
5024
  { table: "worker_queue", kind: "worker_queue_item", key: "proposal_id", created: "created_at", proposal: "proposal_id" },
5025
+ { table: "attention_events", kind: "attention_event", key: "event_id", created: "created_at", proposal: "proposal_id", capability: "capability" },
5026
+ { table: "attention_items", kind: "attention_item", key: "attention_id", created: "first_seen_at", capability: "capability" },
5027
+ { table: "notification_deliveries", kind: "notification_delivery", key: "delivery_id", created: "created_at" },
3700
5028
  { table: "runner_state", kind: "runner_state", key: "key", created: "updated_at" },
3701
5029
  { table: "policy_recommendations", kind: "policy_recommendation", key: "recommendation_id", created: "created_at", tenant: "tenant_id", capability: "capability" },
3702
5030
  { table: "cloud_outbox", kind: "cloud_outbox_event", key: "event_id", created: "created_at", proposal: "proposal_id" },
@@ -4269,10 +5597,52 @@ var ProposalStore = class {
4269
5597
  });
4270
5598
  }
4271
5599
  appendEvent(proposalId, kind, actor, payload) {
5600
+ const append = () => {
5601
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
5602
+ const result = this.db.prepare(`
5603
+ INSERT INTO proposal_events (proposal_id, kind, actor, payload_json, created_at)
5604
+ VALUES (?, ?, ?, ?, ?)
5605
+ `).run(proposalId, kind, actor, JSON.stringify(payload), createdAt);
5606
+ const proposal = this.requireProposal(proposalId);
5607
+ for (const event of attentionEventsForProposalEvent({
5608
+ proposal,
5609
+ proposal_event_id: String(result.lastInsertRowid),
5610
+ kind,
5611
+ actor,
5612
+ payload,
5613
+ environment: this.attentionEnvironment(),
5614
+ occurred_at: createdAt
5615
+ })) {
5616
+ this.recordAttentionEventInternal(event);
5617
+ }
5618
+ if (proposal.state !== "pending_review") {
5619
+ this.resolveSatisfiedProposalReviewAttention(proposal, createdAt);
5620
+ }
5621
+ };
5622
+ if (this.db.isTransaction) append();
5623
+ else this.transaction(append);
5624
+ }
5625
+ attentionEnvironment() {
5626
+ const context = this.getRunnerState("attention_context");
5627
+ const environment = context?.environment;
5628
+ return typeof environment === "string" && /^(development|staging|production|unknown)$/.test(environment) ? environment : "unknown";
5629
+ }
5630
+ resolveSatisfiedProposalReviewAttention(proposal, now) {
5631
+ const attentionKey = proposalReviewAttentionKey(proposal, this.attentionEnvironment());
5632
+ const pending = this.db.prepare(`
5633
+ SELECT COUNT(DISTINCT p.proposal_id) AS count
5634
+ FROM attention_events ae
5635
+ JOIN proposals p ON p.proposal_id = ae.proposal_id
5636
+ WHERE ae.attention_key = ?
5637
+ AND p.state = 'pending_review'
5638
+ `).get(attentionKey);
5639
+ if (isRecord(pending) && Number(pending.count ?? 0) > 0) return;
4272
5640
  this.db.prepare(`
4273
- INSERT INTO proposal_events (proposal_id, kind, actor, payload_json, created_at)
4274
- VALUES (?, ?, ?, ?, ?)
4275
- `).run(proposalId, kind, actor, JSON.stringify(payload), (/* @__PURE__ */ new Date()).toISOString());
5641
+ UPDATE attention_items
5642
+ SET status = 'resolved', resolved_at = ?, last_seen_at = MAX(last_seen_at, ?)
5643
+ WHERE attention_key = ?
5644
+ AND status IN ('open', 'acknowledged')
5645
+ `).run(now, now, attentionKey);
4276
5646
  }
4277
5647
  queryAudit(proposalId) {
4278
5648
  const rows = this.db.prepare("SELECT * FROM query_audit WHERE proposal_id = ? ORDER BY audit_id ASC").all(proposalId);
@@ -5012,23 +6382,858 @@ function assertPolicyRecommendationIdentity(recommendation, input) {
5012
6382
  const canonicalCore = JSON.parse(JSON.stringify(core));
5013
6383
  if (proof.integrity_hash !== canonicalJsonDigest(canonicalCore)) throw new ProposalStoreError("POLICY_RECOMMENDATION_IDENTITY_TAMPERED", "verified operator identity proof failed its integrity check");
5014
6384
  }
6385
+ function attentionEventsForProposalEvent(input) {
6386
+ const proposal = input.proposal;
6387
+ const contractDigest = proposal.change_set.contract?.digest;
6388
+ const scopeDigest = canonicalJsonDigest({
6389
+ tenant_id: proposal.tenant_id,
6390
+ principal: proposal.principal ?? null
6391
+ });
6392
+ const sourceKey = (eventType) => `proposal:${proposal.proposal_id}:event:${input.proposal_event_id}:${eventType}`;
6393
+ const base = (eventType, severity) => ({
6394
+ event_type: eventType,
6395
+ severity,
6396
+ environment: input.environment,
6397
+ proposal_id: proposal.proposal_id,
6398
+ capability: proposal.capability ?? proposal.action,
6399
+ ...contractDigest ? { contract_digest: contractDigest } : {},
6400
+ source_event_key: sourceKey(eventType),
6401
+ now: input.occurred_at
6402
+ });
6403
+ const safeCode = safeAttentionPayloadString(input.payload, "safe_error_code") ?? safeAttentionPayloadString(input.payload, "error_code") ?? safeAttentionPayloadString(input.payload, "reason_code");
6404
+ const workerDetails = safeAttentionDetails(input.payload, [
6405
+ "attempt",
6406
+ "max_attempts",
6407
+ "execution_mode",
6408
+ "outcome"
6409
+ ]);
6410
+ const requiredRole = typeof proposal.change_set.approval.required_role === "string" ? proposal.change_set.approval.required_role : "reviewer";
6411
+ const reviewAttention = (summary) => {
6412
+ const attentionKey = proposalReviewAttentionKey(proposal, input.environment);
6413
+ return {
6414
+ ...base("proposal.review_required", "warning"),
6415
+ attention_required: true,
6416
+ immediate_default: true,
6417
+ attention_key: attentionKey,
6418
+ summary: summary ?? `${proposal.capability ?? proposal.action} proposals need ${requiredRole} review`,
6419
+ workbench_path: `/attention/${attentionItemId(attentionKey)}`,
6420
+ details: {
6421
+ required_role: requiredRole,
6422
+ ...safeCode ? { failure_class: safeCode } : {}
6423
+ },
6424
+ ...safeCode ? { failure_class: safeCode } : {}
6425
+ };
6426
+ };
6427
+ const criticalWorkerAttention = (eventType, summary) => {
6428
+ const failureClass = safeCode ?? (eventType === "worker.dead_lettered" ? "RETRY_BUDGET_EXHAUSTED" : eventType === "worker.unknown_outcome" ? "OUTCOME_UNKNOWN" : "RECONCILIATION_REQUIRED");
6429
+ const attentionKey = [
6430
+ input.environment,
6431
+ eventType,
6432
+ proposal.capability ?? proposal.action,
6433
+ contractDigest ?? "no_digest",
6434
+ failureClass,
6435
+ scopeDigest
6436
+ ].join(":");
6437
+ return {
6438
+ ...base(eventType, "critical"),
6439
+ attention_required: true,
6440
+ immediate_default: true,
6441
+ attention_key: attentionKey,
6442
+ summary,
6443
+ workbench_path: `/attention/${attentionItemId(attentionKey)}`,
6444
+ worker_state: eventType.slice("worker.".length),
6445
+ failure_class: failureClass,
6446
+ details: {
6447
+ ...workerDetails,
6448
+ failure_class: failureClass
6449
+ }
6450
+ };
6451
+ };
6452
+ if (input.kind === "proposal_created") {
6453
+ const events = [{
6454
+ ...base("proposal.created", "informational"),
6455
+ attention_required: false,
6456
+ immediate_default: false,
6457
+ details: { source_database_changed: false }
6458
+ }];
6459
+ const approvalMode = proposal.change_set.approval.mode;
6460
+ if (proposal.state === "pending_review" && approvalMode !== "policy") events.push(reviewAttention());
6461
+ return events;
6462
+ }
6463
+ if (input.kind === "proposal_approved") {
6464
+ const policyApproved = input.actor.startsWith("policy:");
6465
+ return [{
6466
+ ...base(policyApproved ? "proposal.auto_approved" : "proposal.approved", "informational"),
6467
+ attention_required: false,
6468
+ immediate_default: false,
6469
+ approval_source: policyApproved ? "policy_auto" : "human",
6470
+ details: safeAttentionDetails(input.payload, ["approvals", "required_approvals", "remaining_approvals"])
6471
+ }];
6472
+ }
6473
+ if (input.kind === "policy_auto_approval_deferred" || input.kind === "proposal_approval_blocked_freshness") {
6474
+ return [reviewAttention(
6475
+ input.kind === "proposal_approval_blocked_freshness" ? `${proposal.capability ?? proposal.action} needs review because its source freshness check failed` : `${proposal.capability ?? proposal.action} exceeded automatic-approval policy and needs human review`
6476
+ )];
6477
+ }
6478
+ if (input.kind === "proposal_rejected" || input.kind === "proposal_failed") {
6479
+ return [{
6480
+ ...base("proposal.refused", "warning"),
6481
+ attention_required: false,
6482
+ immediate_default: false,
6483
+ ...safeCode ? { failure_class: safeCode, details: { failure_class: safeCode } } : {}
6484
+ }];
6485
+ }
6486
+ if (input.kind === "proposal_canceled" || input.kind === "writeback_canceled") {
6487
+ return [{
6488
+ ...base("proposal.cancelled", "informational"),
6489
+ attention_required: false,
6490
+ immediate_default: false
6491
+ }];
6492
+ }
6493
+ if (input.kind === "proposal_conflict" || input.kind === "writeback_conflict" || input.kind === "writeback_intent_conflict") {
6494
+ return [{
6495
+ ...base("proposal.conflict", "warning"),
6496
+ attention_required: false,
6497
+ immediate_default: false,
6498
+ ...safeCode ? { failure_class: safeCode, details: { failure_class: safeCode } } : {}
6499
+ }];
6500
+ }
6501
+ if (input.kind === "proposal_pending_worker" || input.kind === "writeback_worker_queued") {
6502
+ return [{
6503
+ ...base("proposal.queued", "informational"),
6504
+ attention_required: false,
6505
+ immediate_default: false,
6506
+ worker_state: "queued",
6507
+ details: safeAttentionDetails(input.payload, ["execution_mode", "max_attempts"])
6508
+ }];
6509
+ }
6510
+ if (input.kind === "writeback_retry_scheduled") {
6511
+ return [{
6512
+ ...base("worker.retry_scheduled", "warning"),
6513
+ attention_required: false,
6514
+ immediate_default: false,
6515
+ worker_state: "retry_wait",
6516
+ ...safeCode ? { failure_class: safeCode } : {},
6517
+ details: {
6518
+ ...workerDetails,
6519
+ ...safeCode ? { failure_class: safeCode } : {}
6520
+ }
6521
+ }];
6522
+ }
6523
+ if (input.kind === "writeback_dead_lettered") {
6524
+ return [criticalWorkerAttention("worker.dead_lettered")];
6525
+ }
6526
+ if (input.kind === "writeback_worker_blocked") {
6527
+ const failureClass = safeCode ?? "WORKER_POLICY_BLOCKED";
6528
+ if (/PROPOSAL_.*EXPIRED|PROPOSAL_EXPIRED/i.test(failureClass)) {
6529
+ return [{
6530
+ ...base("proposal.expired", "warning"),
6531
+ attention_required: false,
6532
+ immediate_default: false,
6533
+ worker_state: "blocked",
6534
+ failure_class: failureClass,
6535
+ details: {
6536
+ ...workerDetails,
6537
+ failure_class: failureClass
6538
+ }
6539
+ }];
6540
+ }
6541
+ const digestStale = /(?:CONTRACT|DIGEST|GENERATION_LOCK|SCHEMA|POLICY)_.*(?:STALE|MISMATCH|CHANGED)|ACTIVE_CONTRACT_DIGEST/i.test(failureClass);
6542
+ const limitExceeded = /(?:LIMIT|RATE|QUEUE|BUDGET)_.*(?:EXCEEDED|STALE|MISMATCH)|POLICY_LIMIT/i.test(failureClass);
6543
+ const eventType = digestStale ? "contract.digest_stale" : limitExceeded ? "policy.limit_exceeded" : "proposal.refused";
6544
+ const severity = digestStale || limitExceeded ? "critical" : "warning";
6545
+ const attentionRequired = digestStale || limitExceeded;
6546
+ const attentionKey = [
6547
+ input.environment,
6548
+ eventType,
6549
+ proposal.capability ?? proposal.action,
6550
+ contractDigest ?? "no_digest",
6551
+ failureClass,
6552
+ scopeDigest
6553
+ ].join(":");
6554
+ return [{
6555
+ ...base(eventType, severity),
6556
+ attention_required: attentionRequired,
6557
+ immediate_default: attentionRequired,
6558
+ ...attentionRequired ? {
6559
+ attention_key: attentionKey,
6560
+ workbench_path: `/attention/${attentionItemId(attentionKey)}`
6561
+ } : {},
6562
+ worker_state: "blocked",
6563
+ failure_class: failureClass,
6564
+ details: {
6565
+ ...workerDetails,
6566
+ failure_class: failureClass
6567
+ }
6568
+ }];
6569
+ }
6570
+ if (input.kind === "policy_limit_near") {
6571
+ return [{
6572
+ ...base("policy.limit_near", "warning"),
6573
+ attention_required: false,
6574
+ immediate_default: false,
6575
+ summary: `${proposal.capability ?? proposal.action} is approaching a reviewed policy limit`,
6576
+ details: safeAttentionDetails(input.payload, [
6577
+ "kind",
6578
+ "scope",
6579
+ "observed",
6580
+ "proposed",
6581
+ "projected",
6582
+ "max",
6583
+ "window_start",
6584
+ "window_end"
6585
+ ])
6586
+ }];
6587
+ }
6588
+ if (input.kind === "writeback_reconciliation_required" || input.kind === "writeback_intent_reconciliation_required") {
6589
+ return [criticalWorkerAttention("worker.reconciliation_required")];
6590
+ }
6591
+ if ((input.kind === "writeback_failed" || input.kind === "writeback_intent_failed") && (safeCode === "OUTCOME_UNKNOWN" || safeCode === "RECONCILIATION_REQUIRED")) {
6592
+ return [criticalWorkerAttention("worker.unknown_outcome")];
6593
+ }
6594
+ if (input.kind === "writeback_applied" || input.kind === "writeback_already_applied" || input.kind === "writeback_intent_applied" || input.kind === "writeback_intent_already_applied") {
6595
+ return [{
6596
+ ...base("proposal.applied", "informational"),
6597
+ attention_required: false,
6598
+ immediate_default: false,
6599
+ worker_state: "completed",
6600
+ details: safeAttentionDetails(input.payload, ["rows_affected", "source_database_mutated"])
6601
+ }];
6602
+ }
6603
+ if (input.kind === "writeback_worker_completed") {
6604
+ const outcome = safeAttentionPayloadString(input.payload, "outcome");
6605
+ if (outcome === "conflict") {
6606
+ return [{
6607
+ ...base("proposal.conflict", "warning"),
6608
+ attention_required: false,
6609
+ immediate_default: false,
6610
+ worker_state: "completed",
6611
+ details: { outcome }
6612
+ }];
6613
+ }
6614
+ if (outcome === "applied" || outcome === "already_applied") {
6615
+ return [{
6616
+ ...base("proposal.applied", "informational"),
6617
+ attention_required: false,
6618
+ immediate_default: false,
6619
+ worker_state: "completed",
6620
+ details: { outcome }
6621
+ }];
6622
+ }
6623
+ }
6624
+ if (input.kind === "proposal_reconciliation_required") {
6625
+ return [criticalWorkerAttention("worker.reconciliation_required")];
6626
+ }
6627
+ return [];
6628
+ }
6629
+ function proposalReviewAttentionKey(proposal, environment) {
6630
+ const contractDigest = proposal.change_set.contract?.digest;
6631
+ const requiredRole = typeof proposal.change_set.approval.required_role === "string" ? proposal.change_set.approval.required_role : "reviewer";
6632
+ const scopeDigest = canonicalJsonDigest({
6633
+ tenant_id: proposal.tenant_id,
6634
+ principal: proposal.principal ?? null
6635
+ });
6636
+ return [
6637
+ environment,
6638
+ "proposal.review_required",
6639
+ proposal.capability ?? proposal.action,
6640
+ contractDigest ?? "no_digest",
6641
+ requiredRole,
6642
+ scopeDigest
6643
+ ].join(":");
6644
+ }
6645
+ function safeAttentionPayloadString(payload, key) {
6646
+ const value = payload[key];
6647
+ if (typeof value !== "string" || !value.trim()) return void 0;
6648
+ return value.trim().slice(0, 128);
6649
+ }
6650
+ function safeAttentionDetails(payload, keys) {
6651
+ const details = {};
6652
+ for (const key of keys) {
6653
+ const value = payload[key];
6654
+ if (value === null || typeof value === "boolean") details[key] = value;
6655
+ else if (typeof value === "number" && Number.isFinite(value)) details[key] = value;
6656
+ else if (typeof value === "string" && value.trim()) details[key] = value.trim().slice(0, 128);
6657
+ }
6658
+ return details;
6659
+ }
6660
+ var attentionEventTypes = /* @__PURE__ */ new Set([
6661
+ "proposal.created",
6662
+ "proposal.review_required",
6663
+ "proposal.auto_approved",
6664
+ "proposal.approved",
6665
+ "proposal.queued",
6666
+ "proposal.expiring",
6667
+ "proposal.expired",
6668
+ "proposal.cancelled",
6669
+ "proposal.applied",
6670
+ "proposal.conflict",
6671
+ "proposal.refused",
6672
+ "worker.started",
6673
+ "worker.paused",
6674
+ "worker.unhealthy",
6675
+ "worker.recovered",
6676
+ "worker.queue_backlog",
6677
+ "worker.retry_scheduled",
6678
+ "worker.dead_lettered",
6679
+ "worker.unknown_outcome",
6680
+ "worker.reconciliation_required",
6681
+ "capability.review_required",
6682
+ "capability.activated",
6683
+ "capability.revoked",
6684
+ "contract.digest_stale",
6685
+ "schema.drift_detected",
6686
+ "credential.posture_changed",
6687
+ "policy.limit_near",
6688
+ "policy.limit_exceeded",
6689
+ "sensitive_override_activated",
6690
+ "notification.replayed",
6691
+ "notification.digest"
6692
+ ]);
6693
+ var defaultAttentionEventTypes = /* @__PURE__ */ new Set([
6694
+ "proposal.review_required",
6695
+ "proposal.expiring",
6696
+ "proposal.expired",
6697
+ "worker.unhealthy",
6698
+ "worker.queue_backlog",
6699
+ "worker.dead_lettered",
6700
+ "worker.unknown_outcome",
6701
+ "worker.reconciliation_required",
6702
+ "capability.review_required",
6703
+ "contract.digest_stale",
6704
+ "schema.drift_detected",
6705
+ "credential.posture_changed",
6706
+ "policy.limit_exceeded",
6707
+ "sensitive_override_activated"
6708
+ ]);
6709
+ var defaultImmediateAttentionEventTypes = /* @__PURE__ */ new Set([
6710
+ "proposal.review_required",
6711
+ "proposal.expiring",
6712
+ "worker.unhealthy",
6713
+ "worker.queue_backlog",
6714
+ "worker.dead_lettered",
6715
+ "worker.unknown_outcome",
6716
+ "worker.reconciliation_required",
6717
+ "contract.digest_stale",
6718
+ "schema.drift_detected",
6719
+ "credential.posture_changed",
6720
+ "policy.limit_exceeded"
6721
+ ]);
6722
+ function attentionEventTitle(eventType) {
6723
+ const titles = {
6724
+ "proposal.created": "Proposal created",
6725
+ "proposal.review_required": "Proposal needs human review",
6726
+ "proposal.auto_approved": "Proposal approved by reviewed policy",
6727
+ "proposal.approved": "Proposal approved",
6728
+ "proposal.queued": "Proposal queued for trusted execution",
6729
+ "proposal.expiring": "Approved proposal is approaching expiry",
6730
+ "proposal.expired": "Approved proposal expired without execution",
6731
+ "proposal.cancelled": "Proposal cancelled",
6732
+ "proposal.applied": "Proposal applied",
6733
+ "proposal.conflict": "Guarded writeback conflict",
6734
+ "proposal.refused": "Proposal refused",
6735
+ "worker.started": "Trusted worker started",
6736
+ "worker.paused": "Trusted worker paused",
6737
+ "worker.unhealthy": "Trusted worker needs attention",
6738
+ "worker.recovered": "Trusted worker supervision recovered",
6739
+ "worker.queue_backlog": "Trusted worker queue backlog",
6740
+ "worker.retry_scheduled": "Trusted worker retry scheduled",
6741
+ "worker.dead_lettered": "Trusted worker job dead-lettered",
6742
+ "worker.unknown_outcome": "Database transaction outcome is unknown",
6743
+ "worker.reconciliation_required": "Operator reconciliation is required",
6744
+ "capability.review_required": "Capability needs human review",
6745
+ "capability.activated": "Capability activated",
6746
+ "capability.revoked": "Capability revoked",
6747
+ "contract.digest_stale": "Active contract or policy authority is stale",
6748
+ "schema.drift_detected": "Schema drift blocks active authority",
6749
+ "credential.posture_changed": "Credential posture changed",
6750
+ "policy.limit_near": "Policy limit is approaching",
6751
+ "policy.limit_exceeded": "Policy limit exceeded",
6752
+ "sensitive_override_activated": "Sensitive-field override activated",
6753
+ "notification.replayed": "Notification delivery requeued by a verified operator",
6754
+ "notification.digest": "Runner activity digest"
6755
+ };
6756
+ return titles[eventType];
6757
+ }
6758
+ function assertAttentionEventInput(input) {
6759
+ if (!attentionEventTypes.has(input.event_type)) {
6760
+ throw new ProposalStoreError("ATTENTION_EVENT_TYPE_INVALID", `unsupported attention event type: ${String(input.event_type)}`);
6761
+ }
6762
+ if (!["informational", "warning", "critical"].includes(input.severity)) {
6763
+ throw new ProposalStoreError("ATTENTION_EVENT_SEVERITY_INVALID", `unsupported attention severity: ${String(input.severity)}`);
6764
+ }
6765
+ if (input.contract_digest && !/^sha256:[a-f0-9]{64}$/.test(input.contract_digest)) {
6766
+ throw new ProposalStoreError("ATTENTION_EVENT_DIGEST_INVALID", "attention event contract digest must be a lowercase sha256 digest");
6767
+ }
6768
+ for (const [label, value] of [["now", input.now], ["expires_at", input.expires_at]]) {
6769
+ if (value !== void 0 && !Number.isFinite(Date.parse(value))) {
6770
+ throw new ProposalStoreError("ATTENTION_EVENT_TIME_INVALID", `attention event ${label} must be an ISO timestamp`);
6771
+ }
6772
+ }
6773
+ const entries = Object.entries(input.details ?? {});
6774
+ if (entries.length > 32) {
6775
+ throw new ProposalStoreError("ATTENTION_EVENT_DETAILS_TOO_LARGE", "attention event details may contain at most 32 safe fields");
6776
+ }
6777
+ for (const [key, value] of entries) {
6778
+ if (!/^[a-z][a-z0-9_]{0,63}$/.test(key)) {
6779
+ throw new ProposalStoreError("ATTENTION_EVENT_DETAIL_KEY_INVALID", `attention event detail key is invalid: ${key}`);
6780
+ }
6781
+ if (typeof value === "string") boundedSafeLabel(value, `attention detail ${key}`, 256);
6782
+ else if (typeof value === "number" && !Number.isFinite(value)) {
6783
+ throw new ProposalStoreError("ATTENTION_EVENT_DETAIL_VALUE_INVALID", `attention event detail ${key} must be finite`);
6784
+ } else if (value !== null && typeof value !== "number" && typeof value !== "boolean") {
6785
+ throw new ProposalStoreError("ATTENTION_EVENT_DETAIL_VALUE_INVALID", `attention event detail ${key} must be scalar`);
6786
+ }
6787
+ }
6788
+ assertNoSecretMaterial(input.details ?? {}, "attention_event.details");
6789
+ }
6790
+ function boundedSafeLabel(value, label, maximum) {
6791
+ const trimmed = value.trim();
6792
+ if (!trimmed || trimmed.length > maximum || /[\u0000-\u001f\u007f]/.test(trimmed)) {
6793
+ throw new ProposalStoreError("ATTENTION_EVENT_FIELD_INVALID", `${label} must be a non-empty bounded single-line value`);
6794
+ }
6795
+ assertNoSecretMaterial({ value: trimmed }, label);
6796
+ return trimmed;
6797
+ }
6798
+ function boundedWorkbenchPath(value) {
6799
+ const path = boundedSafeLabel(value, "attention Workbench path", 512);
6800
+ if (!path.startsWith("/") || path.includes("?") || path.includes("#") || path.includes("://") || path.includes("..")) {
6801
+ throw new ProposalStoreError(
6802
+ "ATTENTION_WORKBENCH_PATH_INVALID",
6803
+ "attention Workbench path must be a local absolute path without query, fragment, traversal, or authority"
6804
+ );
6805
+ }
6806
+ return path;
6807
+ }
6808
+ function defaultAttentionKey(input) {
6809
+ const failureClass = typeof input.details.failure_class === "string" ? input.details.failure_class : typeof input.details.reason_code === "string" ? input.details.reason_code : "none";
6810
+ return [
6811
+ input.environment,
6812
+ input.event_type,
6813
+ input.capability ?? "global",
6814
+ input.contract_digest ?? "no_digest",
6815
+ failureClass
6816
+ ].join(":");
6817
+ }
6818
+ function attentionEventId(sourceEventKey, eventType) {
6819
+ const digest = canonicalJsonDigest({ source_event_key: sourceEventKey, event_type: eventType });
6820
+ return `aev_${digest.slice("sha256:".length)}`;
6821
+ }
6822
+ function attentionItemId(attentionKey) {
6823
+ const digest = canonicalJsonDigest({ attention_key: attentionKey });
6824
+ return `attn_${digest.slice("sha256:".length, "sha256:".length + 32)}`;
6825
+ }
6826
+ function boundedSinkId(value) {
6827
+ const sinkId = boundedSafeLabel(value, "notification sink id", 128);
6828
+ if (!/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(sinkId)) {
6829
+ throw new ProposalStoreError(
6830
+ "NOTIFICATION_SINK_ID_INVALID",
6831
+ "notification sink id must use letters, numbers, underscore, period, or hyphen"
6832
+ );
6833
+ }
6834
+ return sinkId;
6835
+ }
6836
+ function boundedSafeErrorCode(value) {
6837
+ const errorCode = boundedSafeLabel(value, "notification error code", 128);
6838
+ if (!/^[A-Z][A-Z0-9_]*$/.test(errorCode)) {
6839
+ throw new ProposalStoreError(
6840
+ "NOTIFICATION_ERROR_CODE_INVALID",
6841
+ "notification error code must be an uppercase safe code"
6842
+ );
6843
+ }
6844
+ return errorCode;
6845
+ }
6846
+ function notificationDeliveryId(sinkId, eventId) {
6847
+ const digest = canonicalJsonDigest({ sink_id: sinkId, event_id: eventId });
6848
+ return `ndl_${digest.slice("sha256:".length, "sha256:".length + 32)}`;
6849
+ }
6850
+ function notificationLeaseId(deliveryId, owner, attempt, now) {
6851
+ const digest = canonicalJsonDigest({
6852
+ delivery_id: deliveryId,
6853
+ owner,
6854
+ attempt,
6855
+ now
6856
+ });
6857
+ return `nlease_${digest.slice("sha256:".length, "sha256:".length + 32)}`;
6858
+ }
6859
+ function attentionSeverityRank(severity) {
6860
+ if (severity === "critical") return 3;
6861
+ if (severity === "warning") return 2;
6862
+ return 1;
6863
+ }
6864
+ function workerControlDecisionSubject(state, target) {
6865
+ assertWorkerControlTarget(target);
6866
+ const targetDigest = canonicalJsonDigest({
6867
+ schema_version: "synapsor.worker-control-decision-subject.v1",
6868
+ current_revision: state.revision,
6869
+ current_integrity_hash: state.integrity_hash,
6870
+ action: target.action,
6871
+ capability: target.capability ?? null,
6872
+ contract_digest: target.contract_digest ?? null
6873
+ });
6874
+ return {
6875
+ proposal_id: `worker_control_${targetDigest.slice("sha256:".length, "sha256:".length + 32)}`,
6876
+ proposal_version: state.revision + 1,
6877
+ proposal_hash: targetDigest
6878
+ };
6879
+ }
6880
+ function defaultWorkerControlState() {
6881
+ const unsigned = {
6882
+ schema_version: "synapsor.worker-control.v1",
6883
+ mode: "active",
6884
+ revision: 0,
6885
+ capability_controls: [],
6886
+ updated_at: "1970-01-01T00:00:00.000Z"
6887
+ };
6888
+ return { ...unsigned, integrity_hash: canonicalJsonDigest(unsigned) };
6889
+ }
6890
+ function parseWorkerControlState(value) {
6891
+ if (!isRecord(value) || value.schema_version !== "synapsor.worker-control.v1" || !["active", "paused", "draining"].includes(String(value.mode)) || !Number.isSafeInteger(value.revision) || Number(value.revision) < 0 || !Array.isArray(value.capability_controls) || typeof value.updated_at !== "string" || typeof value.integrity_hash !== "string") {
6892
+ throw new ProposalStoreError("WORKER_CONTROL_STATE_INVALID", "stored supervised-worker control state is invalid");
6893
+ }
6894
+ const controls = value.capability_controls.map((entry) => {
6895
+ if (!isRecord(entry) || typeof entry.capability !== "string" || !/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(entry.capability) || typeof entry.contract_digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(entry.contract_digest) || !["enabled", "disabled", "revoked"].includes(String(entry.status)) || typeof entry.updated_at !== "string" || typeof entry.actor !== "string") {
6896
+ throw new ProposalStoreError("WORKER_CONTROL_STATE_INVALID", "stored supervised-worker capability control is invalid");
6897
+ }
6898
+ return {
6899
+ capability: entry.capability,
6900
+ contract_digest: entry.contract_digest,
6901
+ status: entry.status,
6902
+ updated_at: entry.updated_at,
6903
+ actor: entry.actor
6904
+ };
6905
+ });
6906
+ const lastDecision = isRecord(value.last_decision) ? value.last_decision : void 0;
6907
+ const unsigned = {
6908
+ schema_version: "synapsor.worker-control.v1",
6909
+ mode: value.mode,
6910
+ revision: Number(value.revision),
6911
+ capability_controls: controls,
6912
+ ...lastDecision ? { last_decision: lastDecision } : {},
6913
+ updated_at: value.updated_at
6914
+ };
6915
+ const integrityHash = value.integrity_hash;
6916
+ if (integrityHash !== canonicalJsonDigest(unsigned)) {
6917
+ throw new ProposalStoreError("WORKER_CONTROL_STATE_TAMPERED", "stored supervised-worker control state failed integrity validation");
6918
+ }
6919
+ return { ...unsigned, integrity_hash: integrityHash };
6920
+ }
6921
+ function assertWorkerControlTarget(target) {
6922
+ const capabilityAction = target.action === "capability_enable" || target.action === "capability_disable" || target.action === "digest_revoke";
6923
+ if (capabilityAction) {
6924
+ if (!target.capability || !/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(target.capability)) {
6925
+ throw new ProposalStoreError("WORKER_CONTROL_CAPABILITY_REQUIRED", `${target.action} requires a fixed capability name`);
6926
+ }
6927
+ if (!target.contract_digest || !/^sha256:[a-f0-9]{64}$/.test(target.contract_digest)) {
6928
+ throw new ProposalStoreError("WORKER_CONTROL_DIGEST_REQUIRED", `${target.action} requires an exact sha256 contract digest`);
6929
+ }
6930
+ } else if (target.capability || target.contract_digest) {
6931
+ throw new ProposalStoreError("WORKER_CONTROL_TARGET_INVALID", `${target.action} is a global control and cannot carry capability authority`);
6932
+ }
6933
+ }
6934
+ function workerControlOperatorAction(action) {
6935
+ if (action === "pause") return "worker_pause";
6936
+ if (action === "resume") return "worker_resume";
6937
+ if (action === "drain") return "worker_drain";
6938
+ if (action === "capability_enable") return "worker_capability_enable";
6939
+ if (action === "capability_disable") return "worker_capability_disable";
6940
+ return "worker_digest_revoke";
6941
+ }
6942
+ function assertWorkerControlOperatorDecision(state, target, actor, identity, requireVerified) {
6943
+ if (requireVerified && (!identity || !identity.verified || identity.provider === "dev_env")) {
6944
+ throw new ProposalStoreError(
6945
+ "VERIFIED_OPERATOR_IDENTITY_REQUIRED",
6946
+ `verified operator identity is required to ${target.action} supervised execution`
6947
+ );
6948
+ }
6949
+ if (!identity) return;
6950
+ if (identity.subject !== actor || identity.decision.subject !== actor) {
6951
+ throw new ProposalStoreError("OPERATOR_IDENTITY_MISMATCH", "worker-control identity does not match its actor");
6952
+ }
6953
+ const subject = workerControlDecisionSubject(state, target);
6954
+ if (identity.decision.action !== workerControlOperatorAction(target.action) || identity.decision.proposal_id !== subject.proposal_id || identity.decision.proposal_version !== subject.proposal_version || identity.decision.proposal_hash !== subject.proposal_hash) {
6955
+ throw new ProposalStoreError("OPERATOR_DECISION_MISMATCH", "operator proof is not bound to this exact worker-control revision");
6956
+ }
6957
+ if (identity.decision_hash !== canonicalJsonDigest(identity.decision)) {
6958
+ throw new ProposalStoreError("OPERATOR_IDENTITY_TAMPERED", "worker-control decision hash failed integrity validation");
6959
+ }
6960
+ const { integrity_hash: _integrityHash, ...identityCore } = identity;
6961
+ if (identity.integrity_hash !== canonicalJsonDigest(identityCore)) {
6962
+ throw new ProposalStoreError("OPERATOR_IDENTITY_TAMPERED", "worker-control identity proof failed integrity validation");
6963
+ }
6964
+ }
6965
+ function workerControlSummary(target) {
6966
+ if (target.action === "pause") return "Supervised execution paused by an operator";
6967
+ if (target.action === "resume") return "Supervised execution resumed by an operator";
6968
+ if (target.action === "drain") return "Supervised execution is draining without new leases";
6969
+ if (target.action === "capability_enable") return `${target.capability} supervised execution enabled for the exact reviewed digest`;
6970
+ if (target.action === "capability_disable") return `${target.capability} supervised execution disabled for the exact reviewed digest`;
6971
+ return `${target.capability} supervised execution digest revoked`;
6972
+ }
6973
+ function attentionDecisionSubject(item) {
6974
+ return {
6975
+ proposal_id: item.attention_id,
6976
+ proposal_version: item.occurrence_count,
6977
+ proposal_hash: canonicalJsonDigest({
6978
+ schema_version: "synapsor.attention-decision-subject.v1",
6979
+ attention_id: item.attention_id,
6980
+ attention_key: item.attention_key,
6981
+ status: item.status,
6982
+ severity: item.severity,
6983
+ environment: item.environment,
6984
+ event_type: item.event_type,
6985
+ capability: item.capability ?? null,
6986
+ contract_digest: item.contract_digest ?? null,
6987
+ occurrence_count: item.occurrence_count,
6988
+ latest_event_id: item.latest_event_id,
6989
+ last_seen_at: item.last_seen_at
6990
+ })
6991
+ };
6992
+ }
6993
+ function notificationReplayDecisionSubject(delivery) {
6994
+ return {
6995
+ proposal_id: delivery.delivery_id,
6996
+ proposal_version: Math.max(1, delivery.attempts),
6997
+ proposal_hash: canonicalJsonDigest({
6998
+ schema_version: "synapsor.notification-replay-decision-subject.v1",
6999
+ delivery_id: delivery.delivery_id,
7000
+ sink_id: delivery.sink_id,
7001
+ event_id: delivery.event_id,
7002
+ attention_id: delivery.attention_id ?? null,
7003
+ status: delivery.status,
7004
+ attempts: delivery.attempts,
7005
+ max_attempts: delivery.max_attempts,
7006
+ last_error_code: delivery.last_error_code ?? null,
7007
+ updated_at: delivery.updated_at
7008
+ })
7009
+ };
7010
+ }
7011
+ function assertNotificationReplayOperatorDecision(delivery, identity, reason) {
7012
+ if (!identity.verified || identity.provider === "dev_env") {
7013
+ throw new ProposalStoreError(
7014
+ "VERIFIED_OPERATOR_IDENTITY_REQUIRED",
7015
+ `verified operator identity is required to replay notification delivery ${delivery.delivery_id}`
7016
+ );
7017
+ }
7018
+ if (identity.subject !== identity.decision.subject) {
7019
+ throw new ProposalStoreError(
7020
+ "OPERATOR_IDENTITY_MISMATCH",
7021
+ "notification replay identity does not match its signed decision subject"
7022
+ );
7023
+ }
7024
+ const subject = notificationReplayDecisionSubject(delivery);
7025
+ if (identity.decision.action !== "notification_replay" || identity.decision.proposal_id !== subject.proposal_id || identity.decision.proposal_version !== subject.proposal_version || identity.decision.proposal_hash !== subject.proposal_hash || identity.decision.reason !== reason) {
7026
+ throw new ProposalStoreError(
7027
+ "OPERATOR_DECISION_MISMATCH",
7028
+ "operator proof is not bound to this exact notification delivery revision and replay reason"
7029
+ );
7030
+ }
7031
+ if (identity.decision_hash !== canonicalJsonDigest(identity.decision)) {
7032
+ throw new ProposalStoreError(
7033
+ "OPERATOR_IDENTITY_TAMPERED",
7034
+ "notification replay decision hash failed its integrity check"
7035
+ );
7036
+ }
7037
+ const { integrity_hash: _integrityHash, ...core } = identity;
7038
+ const canonicalCore = JSON.parse(JSON.stringify(core));
7039
+ if (identity.integrity_hash !== canonicalJsonDigest(canonicalCore)) {
7040
+ throw new ProposalStoreError(
7041
+ "OPERATOR_IDENTITY_TAMPERED",
7042
+ "notification replay identity proof failed its integrity check"
7043
+ );
7044
+ }
7045
+ }
7046
+ function assertAttentionOperatorDecision(item, actor, identity, requireVerified) {
7047
+ if (requireVerified && (!identity || !identity.verified || identity.provider === "dev_env")) {
7048
+ throw new ProposalStoreError(
7049
+ "VERIFIED_OPERATOR_IDENTITY_REQUIRED",
7050
+ `verified operator identity is required to acknowledge attention item ${item.attention_id}`
7051
+ );
7052
+ }
7053
+ if (!identity) return;
7054
+ if (identity.subject !== actor || identity.decision.subject !== actor) {
7055
+ throw new ProposalStoreError(
7056
+ "OPERATOR_IDENTITY_MISMATCH",
7057
+ `operator identity ${identity.subject} does not match attention acknowledgement actor ${actor}`
7058
+ );
7059
+ }
7060
+ const subject = attentionDecisionSubject(item);
7061
+ if (identity.decision.action !== "attention_acknowledge" || identity.decision.proposal_id !== subject.proposal_id || identity.decision.proposal_version !== subject.proposal_version || identity.decision.proposal_hash !== subject.proposal_hash) {
7062
+ throw new ProposalStoreError(
7063
+ "OPERATOR_DECISION_MISMATCH",
7064
+ "operator proof is not bound to this exact attention item version"
7065
+ );
7066
+ }
7067
+ if (identity.decision_hash !== canonicalJsonDigest(identity.decision)) {
7068
+ throw new ProposalStoreError(
7069
+ "OPERATOR_IDENTITY_TAMPERED",
7070
+ "attention acknowledgement decision hash failed its integrity check"
7071
+ );
7072
+ }
7073
+ const { integrity_hash: _integrityHash, ...core } = identity;
7074
+ const canonicalCore = JSON.parse(JSON.stringify(core));
7075
+ if (identity.integrity_hash !== canonicalJsonDigest(canonicalCore)) {
7076
+ throw new ProposalStoreError(
7077
+ "OPERATOR_IDENTITY_TAMPERED",
7078
+ "attention acknowledgement identity proof failed its integrity check"
7079
+ );
7080
+ }
7081
+ }
7082
+ function rowToAttentionEvent(row) {
7083
+ if (!isRecord(row)) return void 0;
7084
+ const eventType = String(row.event_type);
7085
+ const severity = String(row.severity);
7086
+ if (!attentionEventTypes.has(eventType) || !["informational", "warning", "critical"].includes(severity)) return void 0;
7087
+ let details;
7088
+ try {
7089
+ const parsed = JSON.parse(String(row.details_json));
7090
+ if (!isRecord(parsed)) return void 0;
7091
+ details = {};
7092
+ for (const [key, value] of Object.entries(parsed)) {
7093
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") return void 0;
7094
+ details[key] = value;
7095
+ }
7096
+ } catch {
7097
+ return void 0;
7098
+ }
7099
+ const event = {
7100
+ schema_version: "synapsor.attention-event.v1",
7101
+ event_id: String(row.event_id),
7102
+ event_type: eventType,
7103
+ severity,
7104
+ occurred_at: String(row.occurred_at),
7105
+ environment: String(row.environment),
7106
+ ...row.proposal_id == null ? {} : { proposal_id: String(row.proposal_id) },
7107
+ ...row.job_id == null ? {} : { job_id: String(row.job_id) },
7108
+ ...row.operation_id == null ? {} : { operation_id: String(row.operation_id) },
7109
+ ...row.correlation_id == null ? {} : { correlation_id: String(row.correlation_id) },
7110
+ ...row.capability == null ? {} : { capability: String(row.capability) },
7111
+ ...row.contract_digest == null ? {} : { contract_digest: String(row.contract_digest) },
7112
+ ...row.attention_key == null ? {} : { attention_key: String(row.attention_key) },
7113
+ attention_required: Number(row.attention_required) === 1,
7114
+ immediate_default: Number(row.immediate_default) === 1,
7115
+ summary: String(row.summary),
7116
+ ...row.approval_source === "human" || row.approval_source === "policy_auto" ? { approval_source: row.approval_source } : {},
7117
+ ...row.worker_state == null ? {} : { worker_state: String(row.worker_state) },
7118
+ ...row.failure_class == null ? {} : { failure_class: String(row.failure_class) },
7119
+ ...row.expires_at == null ? {} : { expires_at: String(row.expires_at) },
7120
+ ...row.workbench_path == null ? {} : { workbench_path: String(row.workbench_path) },
7121
+ details,
7122
+ payload_hash: String(row.payload_hash)
7123
+ };
7124
+ const { payload_hash: _payloadHash, ...unsigned } = event;
7125
+ if (event.payload_hash !== canonicalJsonDigest(unsigned)) {
7126
+ throw new ProposalStoreError("ATTENTION_EVENT_CORRUPT", `attention event ${event.event_id} failed its integrity check`);
7127
+ }
7128
+ return event;
7129
+ }
7130
+ function rowToAttentionItem(row) {
7131
+ if (!isRecord(row)) return void 0;
7132
+ const status = String(row.status);
7133
+ const severity = String(row.severity);
7134
+ const eventType = String(row.event_type);
7135
+ if (!["open", "acknowledged", "resolved", "expired"].includes(status)) return void 0;
7136
+ if (!["informational", "warning", "critical"].includes(severity) || !attentionEventTypes.has(eventType)) return void 0;
7137
+ let acknowledgementIdentity;
7138
+ if (row.acknowledgement_identity_json != null) {
7139
+ try {
7140
+ const parsed = JSON.parse(String(row.acknowledgement_identity_json));
7141
+ if (!isRecord(parsed) || !isRecord(parsed.decision)) return void 0;
7142
+ acknowledgementIdentity = parsed;
7143
+ } catch {
7144
+ return void 0;
7145
+ }
7146
+ }
7147
+ return {
7148
+ attention_id: String(row.attention_id),
7149
+ attention_key: String(row.attention_key),
7150
+ status,
7151
+ severity,
7152
+ environment: String(row.environment),
7153
+ event_type: eventType,
7154
+ capability: row.capability == null ? void 0 : String(row.capability),
7155
+ contract_digest: row.contract_digest == null ? void 0 : String(row.contract_digest),
7156
+ title: String(row.title),
7157
+ occurrence_count: Number(row.occurrence_count),
7158
+ first_event_id: String(row.first_event_id),
7159
+ latest_event_id: String(row.latest_event_id),
7160
+ first_seen_at: String(row.first_seen_at),
7161
+ last_seen_at: String(row.last_seen_at),
7162
+ acknowledged_by: row.acknowledged_by == null ? void 0 : String(row.acknowledged_by),
7163
+ acknowledged_at: row.acknowledged_at == null ? void 0 : String(row.acknowledged_at),
7164
+ acknowledgement_identity: acknowledgementIdentity,
7165
+ resolved_at: row.resolved_at == null ? void 0 : String(row.resolved_at),
7166
+ expires_at: row.expires_at == null ? void 0 : String(row.expires_at)
7167
+ };
7168
+ }
7169
+ function rowToNotificationDelivery(row) {
7170
+ if (!isRecord(row)) return void 0;
7171
+ const status = String(row.status);
7172
+ if (![
7173
+ "pending",
7174
+ "leased",
7175
+ "delivered",
7176
+ "retry_wait",
7177
+ "dead_letter",
7178
+ "suppressed",
7179
+ "batched"
7180
+ ].includes(status)) return void 0;
7181
+ return {
7182
+ delivery_id: String(row.delivery_id),
7183
+ sink_id: String(row.sink_id),
7184
+ event_id: String(row.event_id),
7185
+ ...row.attention_id == null ? {} : { attention_id: String(row.attention_id) },
7186
+ status,
7187
+ attempts: Number(row.attempts),
7188
+ max_attempts: Number(row.max_attempts),
7189
+ next_attempt_at: String(row.next_attempt_at),
7190
+ ...row.lease_owner == null ? {} : { lease_owner: String(row.lease_owner) },
7191
+ ...row.lease_id == null ? {} : { lease_id: String(row.lease_id) },
7192
+ ...row.lease_expires_at == null ? {} : { lease_expires_at: String(row.lease_expires_at) },
7193
+ ...row.last_error_code == null ? {} : { last_error_code: String(row.last_error_code) },
7194
+ ...row.external_reference == null ? {} : { external_reference: String(row.external_reference) },
7195
+ ...row.delivered_at == null ? {} : { delivered_at: String(row.delivered_at) },
7196
+ created_at: String(row.created_at),
7197
+ updated_at: String(row.updated_at)
7198
+ };
7199
+ }
5015
7200
  function rowToWorkerQueueItem(row) {
5016
7201
  if (!isRecord(row)) return void 0;
5017
7202
  const status = String(row.status);
5018
- if (!["queued", "leased", "retry_wait", "completed", "dead_letter", "discarded"].includes(status)) return void 0;
7203
+ if (![
7204
+ "queued",
7205
+ "leased",
7206
+ "retry_wait",
7207
+ "completed",
7208
+ "dead_letter",
7209
+ "discarded",
7210
+ "cancelled",
7211
+ "blocked",
7212
+ "reconciliation_required"
7213
+ ].includes(status)) return void 0;
7214
+ const executionMode = row.execution_mode == null ? "legacy" : String(row.execution_mode);
7215
+ if (executionMode !== "legacy" && executionMode !== "supervised_worker") return void 0;
5019
7216
  return {
5020
7217
  proposal_id: String(row.proposal_id),
5021
7218
  status,
7219
+ execution_mode: executionMode,
7220
+ contract_digest: row.contract_digest == null ? void 0 : String(row.contract_digest),
5022
7221
  attempts: Number(row.attempts),
5023
7222
  max_attempts: Number(row.max_attempts),
5024
7223
  next_attempt_at: String(row.next_attempt_at),
5025
7224
  lease_owner: row.lease_owner == null ? void 0 : String(row.lease_owner),
7225
+ lease_id: row.lease_id == null ? void 0 : String(row.lease_id),
5026
7226
  lease_expires_at: row.lease_expires_at == null ? void 0 : String(row.lease_expires_at),
5027
7227
  last_error_code: row.last_error_code == null ? void 0 : String(row.last_error_code),
7228
+ terminal_outcome: row.terminal_outcome == null ? void 0 : String(row.terminal_outcome),
5028
7229
  created_at: String(row.created_at),
5029
7230
  updated_at: String(row.updated_at)
5030
7231
  };
5031
7232
  }
7233
+ function workerLeaseId(proposalId, workerId, attempt, now) {
7234
+ const prefixLength = "sha256:".length;
7235
+ return `wlease_${canonicalJsonDigest({ proposal_id: proposalId, worker_id: workerId, attempt, now }).slice(prefixLength, prefixLength + 32)}`;
7236
+ }
5032
7237
  function rowToCloudOutboxItem(row) {
5033
7238
  if (!isRecord(row)) return void 0;
5034
7239
  const kind = String(row.kind);
@@ -5176,7 +7381,17 @@ function rowToWritebackIntent(row) {
5176
7381
  };
5177
7382
  }
5178
7383
  function isStoredWritebackOperation(operation) {
5179
- return ["single_row_update", "single_row_insert", "single_row_delete", "set_update", "set_delete", "batch_insert"].includes(operation);
7384
+ return [
7385
+ "single_row_update",
7386
+ "single_row_insert",
7387
+ "single_row_delete",
7388
+ "set_update",
7389
+ "set_delete",
7390
+ "batch_insert",
7391
+ "restore_update",
7392
+ "remove_insert",
7393
+ "restore_insert"
7394
+ ].includes(operation);
5180
7395
  }
5181
7396
  function rowToQueryAudit(row) {
5182
7397
  if (!isRecord(row)) return void 0;
@@ -5287,7 +7502,9 @@ var sharedLedgerJsonColumns = /* @__PURE__ */ new Set([
5287
7502
  "patch_json",
5288
7503
  "selected_capabilities_json",
5289
7504
  "proposed_effect_json",
5290
- "actual_effect_json"
7505
+ "actual_effect_json",
7506
+ "details_json",
7507
+ "acknowledgement_identity_json"
5291
7508
  ]);
5292
7509
  var sharedLedgerKindToTable = {
5293
7510
  proposal: "proposals",
@@ -5306,6 +7523,9 @@ var sharedLedgerKindToTable = {
5306
7523
  shadow_study_case: "shadow_study_cases",
5307
7524
  shadow_outcome: "shadow_outcomes",
5308
7525
  worker_queue_item: "worker_queue",
7526
+ attention_event: "attention_events",
7527
+ attention_item: "attention_items",
7528
+ notification_delivery: "notification_deliveries",
5309
7529
  runner_state: "runner_state",
5310
7530
  policy_recommendation: "policy_recommendations",
5311
7531
  cloud_outbox_event: "cloud_outbox",
@@ -5348,7 +7568,142 @@ var sharedLedgerRestoreSpecs = {
5348
7568
  shadow_studies: restoreSpec("study_id", ["study_id", "name", "description", "selected_capabilities_json", "starts_at", "ends_at", "status", "created_at", "updated_at"], ["study_id", "name", "selected_capabilities_json", "status", "created_at", "updated_at"]),
5349
7569
  shadow_study_cases: restoreSpec("case_id", ["case_id", "study_id", "request_id", "proposal_id", "tenant_id", "principal", "capability", "business_object", "object_id", "evidence_bundle_id", "proposed_effect_json", "agent_result", "decision_reason", "risk_score", "amount_value", "created_at"], ["case_id", "study_id", "request_id", "tenant_id", "capability", "business_object", "object_id", "agent_result", "created_at"]),
5350
7570
  shadow_outcomes: restoreSpec("outcome_id", ["outcome_id", "study_id", "request_id", "proposal_id", "tenant_id", "business_object", "object_id", "actor", "disposition", "actual_effect_json", "occurred_at", "source", "reference", "reason", "created_at"], ["outcome_id", "study_id", "request_id", "tenant_id", "business_object", "object_id", "actor", "disposition", "occurred_at", "source", "created_at"]),
5351
- worker_queue: restoreSpec("proposal_id", ["proposal_id", "status", "attempts", "max_attempts", "next_attempt_at", "lease_owner", "lease_expires_at", "last_error_code", "created_at", "updated_at"], ["proposal_id", "status", "attempts", "max_attempts", "next_attempt_at", "created_at", "updated_at"]),
7571
+ worker_queue: restoreSpec(
7572
+ "proposal_id",
7573
+ [
7574
+ "proposal_id",
7575
+ "status",
7576
+ "execution_mode",
7577
+ "contract_digest",
7578
+ "attempts",
7579
+ "max_attempts",
7580
+ "next_attempt_at",
7581
+ "lease_owner",
7582
+ "lease_id",
7583
+ "lease_expires_at",
7584
+ "last_error_code",
7585
+ "terminal_outcome",
7586
+ "created_at",
7587
+ "updated_at"
7588
+ ],
7589
+ ["proposal_id", "status", "attempts", "max_attempts", "next_attempt_at", "created_at", "updated_at"]
7590
+ ),
7591
+ attention_events: restoreSpec(
7592
+ "event_id",
7593
+ [
7594
+ "event_id",
7595
+ "schema_version",
7596
+ "event_type",
7597
+ "severity",
7598
+ "occurred_at",
7599
+ "environment",
7600
+ "proposal_id",
7601
+ "job_id",
7602
+ "operation_id",
7603
+ "correlation_id",
7604
+ "capability",
7605
+ "contract_digest",
7606
+ "attention_key",
7607
+ "attention_required",
7608
+ "immediate_default",
7609
+ "summary",
7610
+ "approval_source",
7611
+ "worker_state",
7612
+ "failure_class",
7613
+ "expires_at",
7614
+ "workbench_path",
7615
+ "details_json",
7616
+ "payload_hash",
7617
+ "created_at"
7618
+ ],
7619
+ [
7620
+ "event_id",
7621
+ "schema_version",
7622
+ "event_type",
7623
+ "severity",
7624
+ "occurred_at",
7625
+ "environment",
7626
+ "attention_required",
7627
+ "immediate_default",
7628
+ "summary",
7629
+ "details_json",
7630
+ "payload_hash",
7631
+ "created_at"
7632
+ ]
7633
+ ),
7634
+ attention_items: restoreSpec(
7635
+ "attention_id",
7636
+ [
7637
+ "attention_id",
7638
+ "attention_key",
7639
+ "status",
7640
+ "severity",
7641
+ "environment",
7642
+ "event_type",
7643
+ "capability",
7644
+ "contract_digest",
7645
+ "title",
7646
+ "occurrence_count",
7647
+ "first_event_id",
7648
+ "latest_event_id",
7649
+ "first_seen_at",
7650
+ "last_seen_at",
7651
+ "acknowledged_by",
7652
+ "acknowledged_at",
7653
+ "acknowledgement_identity_json",
7654
+ "acknowledgement_decision_hash",
7655
+ "acknowledgement_signature",
7656
+ "acknowledgement_integrity_hash",
7657
+ "resolved_at",
7658
+ "expires_at"
7659
+ ],
7660
+ [
7661
+ "attention_id",
7662
+ "attention_key",
7663
+ "status",
7664
+ "severity",
7665
+ "environment",
7666
+ "event_type",
7667
+ "title",
7668
+ "occurrence_count",
7669
+ "first_event_id",
7670
+ "latest_event_id",
7671
+ "first_seen_at",
7672
+ "last_seen_at"
7673
+ ]
7674
+ ),
7675
+ notification_deliveries: restoreSpec(
7676
+ "delivery_id",
7677
+ [
7678
+ "delivery_id",
7679
+ "sink_id",
7680
+ "event_id",
7681
+ "attention_id",
7682
+ "status",
7683
+ "attempts",
7684
+ "max_attempts",
7685
+ "next_attempt_at",
7686
+ "lease_owner",
7687
+ "lease_id",
7688
+ "lease_expires_at",
7689
+ "last_error_code",
7690
+ "external_reference",
7691
+ "delivered_at",
7692
+ "created_at",
7693
+ "updated_at"
7694
+ ],
7695
+ [
7696
+ "delivery_id",
7697
+ "sink_id",
7698
+ "event_id",
7699
+ "status",
7700
+ "attempts",
7701
+ "max_attempts",
7702
+ "next_attempt_at",
7703
+ "created_at",
7704
+ "updated_at"
7705
+ ]
7706
+ ),
5352
7707
  runner_state: restoreSpec("key", ["key", "value_json", "updated_at"], ["key", "value_json", "updated_at"]),
5353
7708
  policy_recommendations: restoreSpec("recommendation_id", ["recommendation_id", "tenant_id", "capability", "policy", "base_contract_digest", "status", "payload_json", "integrity_hash", "created_at", "updated_at"], ["recommendation_id", "tenant_id", "capability", "policy", "base_contract_digest", "status", "payload_json", "integrity_hash", "created_at", "updated_at"]),
5354
7709
  cloud_outbox: restoreSpec("event_id", ["event_id", "proposal_id", "sequence", "kind", "status", "payload_hash", "payload_json", "attempts", "max_attempts", "next_attempt_at", "lease_owner", "lease_expires_at", "last_error_code", "sent_at", "acknowledged_at", "created_at", "updated_at"], ["event_id", "sequence", "kind", "status", "payload_hash", "payload_json", "attempts", "max_attempts", "next_attempt_at", "created_at", "updated_at"]),
@@ -5397,6 +7752,9 @@ function sharedLedgerRestoreRank(entry) {
5397
7752
  "shadow_human_actions",
5398
7753
  "shadow_outcomes",
5399
7754
  "worker_queue",
7755
+ "attention_events",
7756
+ "attention_items",
7757
+ "notification_deliveries",
5400
7758
  "runner_state",
5401
7759
  "policy_recommendations",
5402
7760
  "cloud_outbox",