@dsh-enhanced/assistant-delivery 0.1.24 → 0.1.31

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 (66) hide show
  1. package/README.md +81 -10
  2. package/lib/acceptance.d.ts +46 -0
  3. package/lib/acceptance.d.ts.map +1 -0
  4. package/lib/acceptance.js +2 -0
  5. package/lib/acceptance.js.map +1 -0
  6. package/lib/agent-runtime.d.ts +33 -3
  7. package/lib/agent-runtime.d.ts.map +1 -1
  8. package/lib/agent-runtime.js +835 -62
  9. package/lib/agent-runtime.js.map +1 -1
  10. package/lib/coordinator.d.ts +4 -1
  11. package/lib/coordinator.d.ts.map +1 -1
  12. package/lib/coordinator.js +9 -0
  13. package/lib/coordinator.js.map +1 -1
  14. package/lib/feedback-command.d.ts +21 -1
  15. package/lib/feedback-command.d.ts.map +1 -1
  16. package/lib/feedback-command.js +18 -0
  17. package/lib/feedback-command.js.map +1 -1
  18. package/lib/goal-wake-types.d.ts +93 -0
  19. package/lib/goal-wake-types.d.ts.map +1 -0
  20. package/lib/goal-wake-types.js +70 -0
  21. package/lib/goal-wake-types.js.map +1 -0
  22. package/lib/inbound-images.d.ts.map +1 -1
  23. package/lib/inbound-images.js +11 -9
  24. package/lib/inbound-images.js.map +1 -1
  25. package/lib/index.d.ts +4 -0
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +1 -0
  28. package/lib/index.js.map +1 -1
  29. package/lib/native-web-owner.d.ts +78 -0
  30. package/lib/native-web-owner.d.ts.map +1 -0
  31. package/lib/native-web-owner.js +291 -0
  32. package/lib/native-web-owner.js.map +1 -0
  33. package/lib/operator-snapshot.d.ts +80 -0
  34. package/lib/operator-snapshot.d.ts.map +1 -0
  35. package/lib/operator-snapshot.js +935 -0
  36. package/lib/operator-snapshot.js.map +1 -0
  37. package/lib/operator.d.ts +2 -0
  38. package/lib/operator.d.ts.map +1 -1
  39. package/lib/operator.js +10 -0
  40. package/lib/operator.js.map +1 -1
  41. package/lib/service.d.ts +63 -13
  42. package/lib/service.d.ts.map +1 -1
  43. package/lib/service.js +917 -349
  44. package/lib/service.js.map +1 -1
  45. package/lib/session-lease-runtime.d.ts +69 -0
  46. package/lib/session-lease-runtime.d.ts.map +1 -0
  47. package/lib/session-lease-runtime.js +395 -0
  48. package/lib/session-lease-runtime.js.map +1 -0
  49. package/lib/session-lease-types.d.ts +27 -0
  50. package/lib/session-lease-types.d.ts.map +1 -0
  51. package/lib/session-lease-types.js +2 -0
  52. package/lib/session-lease-types.js.map +1 -0
  53. package/lib/sqlite.d.ts +1 -1
  54. package/lib/sqlite.d.ts.map +1 -1
  55. package/lib/sqlite.js +201 -3
  56. package/lib/sqlite.js.map +1 -1
  57. package/lib/store.d.ts +133 -4
  58. package/lib/store.d.ts.map +1 -1
  59. package/lib/store.js +771 -43
  60. package/lib/store.js.map +1 -1
  61. package/lib/types.d.ts +9 -2
  62. package/lib/types.d.ts.map +1 -1
  63. package/lib/types.js.map +1 -1
  64. package/lib/version.d.ts +1 -1
  65. package/lib/version.js +1 -1
  66. package/package.json +61 -44
package/lib/store.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'node:crypto';
2
2
  import { isAbsolute } from 'node:path';
3
+ import { acceptanceCanonicalJson } from '@dsh-enhanced/task-acceptance-contract';
3
4
  import { ASSISTANT_GROWTH_CONTRACT_VERSION, WORKFLOW_TRACE_SOURCE_ID, growthObjectDigest, validateResolvedWorkflowAutomationTemplate, validateWorkflowAutomationTemplate, validateWorkflowAutomationTemplateContent, validateWorkflowTraceRevision, workflowAutomationTemplateContentDigest, workflowArgumentShapeDigest, workflowScopeKey, workflowTraceRevisionDigest, } from '@dsh-enhanced/assistant-growth-contract';
4
5
  import { bindingMatchesOwnerRoute, canonicalBackgroundSourceId, canonicalConversation, canonicalLocalOperatorId, canonicalOwnerRouteAuthority, canonicalPrincipal, canonicalTarget, externalPrincipalId, ownerRouteAuthorityHash, ownerRouteBindingSnapshot, } from './canonical.js';
5
6
  import { feedbackDispatchRecoveryCode, isFeedbackDeliveryCommand, isFeedbackDispatchRecoveryCode, isExactDeliveryCommand, isLearningDeliveryCommand, isLearningDispatchRecoveryCode, isLearningRetryableFailureCode, isPermissionDeliveryCommand, isWorkflowDeliveryCommand, isWorkflowDispatchRecoveryCode, learningDispatchRecoveryCode, parseDeliveryCommand, permissionDispatchRecoveryCode, permissionDispatchRecoveryFromFailureCode, workflowDispatchRecoveryCode, } from './session-commands.js';
6
7
  import { parseFeedbackCommand } from './feedback-command.js';
8
+ import { validateOwnerGoalOutcomeFeedbackLocator, validateOwnerGoalOutcomeFeedbackProof, } from './goal-wake-types.js';
7
9
  import { openDeliveryDatabase } from './sqlite.js';
8
10
  import { deriveDeterministicallyDeidentifiedWorkflowTemplate, getDeterministicallyDeidentifiedWorkflowTemplate, } from './workflow-auto-producer.js';
9
11
  export class DeliveryStoreError extends Error {
@@ -366,7 +368,7 @@ function canonicalImageRef(input) {
366
368
  const value = input;
367
369
  const required = ['attachmentId', 'mediaType', 'bytes', 'width', 'height'];
368
370
  if (required.some(key => !Object.hasOwn(value, key))
369
- || Object.keys(value).some(key => ![...required, 'name'].includes(key))
371
+ || Object.keys(value).some(key => ![...required, 'name', 'originalDimensions'].includes(key))
370
372
  || typeof value.attachmentId !== 'string'
371
373
  || value.attachmentId.length < 1 || value.attachmentId.length > 512
372
374
  || /\p{Cc}/u.test(value.attachmentId)
@@ -375,6 +377,15 @@ function canonicalImageRef(input) {
375
377
  || !Number.isSafeInteger(value.bytes) || value.bytes < 1
376
378
  || !Number.isSafeInteger(value.width) || value.width < 1
377
379
  || !Number.isSafeInteger(value.height) || value.height < 1
380
+ || (value.originalDimensions !== undefined
381
+ && (value.originalDimensions === null
382
+ || typeof value.originalDimensions !== 'object'
383
+ || Array.isArray(value.originalDimensions)
384
+ || Object.keys(value.originalDimensions).some(key => key !== 'width' && key !== 'height')
385
+ || !Number.isSafeInteger(value.originalDimensions.width)
386
+ || value.originalDimensions.width < 1
387
+ || !Number.isSafeInteger(value.originalDimensions.height)
388
+ || value.originalDimensions.height < 1))
378
389
  || (value.name !== undefined && (typeof value.name !== 'string' || value.name.length < 1
379
390
  || value.name.length > 255 || value.name === '.' || value.name === '..' || /[\\/\p{Cc}]/u.test(value.name)))) {
380
391
  invalidImageRef();
@@ -386,6 +397,12 @@ function canonicalImageRef(input) {
386
397
  width: value.width,
387
398
  height: value.height,
388
399
  ...(value.name === undefined ? {} : { name: value.name }),
400
+ ...(value.originalDimensions === undefined ? {} : {
401
+ originalDimensions: {
402
+ width: value.originalDimensions.width,
403
+ height: value.originalDimensions.height,
404
+ },
405
+ }),
389
406
  };
390
407
  }
391
408
  function persistedImageRef(row) {
@@ -409,7 +426,9 @@ function sameImageRef(left, right) {
409
426
  && left.bytes === right.bytes
410
427
  && left.width === right.width
411
428
  && left.height === right.height
412
- && left.name === right.name;
429
+ && left.name === right.name
430
+ && left.originalDimensions?.width === right.originalDimensions?.width
431
+ && left.originalDimensions?.height === right.originalDimensions?.height;
413
432
  }
414
433
  function attachmentFromRow(row) {
415
434
  const imageRef = row.status === 'ready' && row.resource_kind === 'image'
@@ -630,6 +649,32 @@ function approvalRouteFromRow(row) {
630
649
  principalVersion: row.principal_version,
631
650
  });
632
651
  }
652
+ function goalOutcomeTargetFromRow(row) {
653
+ let locator;
654
+ let proof;
655
+ try {
656
+ locator = validateOwnerGoalOutcomeFeedbackLocator(JSON.parse(row.locator_json));
657
+ proof = validateOwnerGoalOutcomeFeedbackProof(JSON.parse(row.proof_json));
658
+ }
659
+ catch {
660
+ throw new DeliveryStoreError('invalid-intent', 'stored goal outcome target is invalid');
661
+ }
662
+ if (acceptanceCanonicalJson(proof.locator) !== acceptanceCanonicalJson(locator)
663
+ || proof.proofDigest !== row.proof_digest || locator.ownerRouteId !== row.owner_route_id
664
+ || locator.principalId !== row.principal_id || locator.principalRecordId !== row.principal_record_id
665
+ || locator.principalVersion !== row.principal_version || locator.workspace !== row.workspace
666
+ || locator.preset !== row.preset || locator.bindingId !== row.binding_id
667
+ || locator.bindingVersion !== row.binding_version || locator.bindingGeneration !== row.binding_generation
668
+ || locator.sessionId !== row.session_id || locator.goalId !== row.goal_id
669
+ || locator.assessmentId !== row.assessment_id) {
670
+ throw new DeliveryStoreError('invalid-intent', 'stored goal outcome target columns do not match its proof');
671
+ }
672
+ return Object.freeze({ outboxId: row.outbox_id, locator, proof });
673
+ }
674
+ function sameGoalOutcomeTarget(left, locator, proof) {
675
+ return acceptanceCanonicalJson(left.locator) === acceptanceCanonicalJson(locator)
676
+ && acceptanceCanonicalJson(left.proof) === acceptanceCanonicalJson(proof);
677
+ }
633
678
  function sameApprovalRoute(left, right) {
634
679
  return left.routeVersion === right.routeVersion
635
680
  && left.sourceId === right.sourceId
@@ -1263,6 +1308,28 @@ function getPairingStatement(database) {
1263
1308
  FROM pairing_challenges WHERE id = ?
1264
1309
  `);
1265
1310
  }
1311
+ function sameLeaseBinding(left, right) {
1312
+ return left.id === right.id && left.sessionId === right.sessionId && left.generation === right.generation
1313
+ && left.version === right.version && left.status === 'active' && right.status === 'active'
1314
+ && left.workspace === right.workspace && left.agentPreset === right.agentPreset
1315
+ && JSON.stringify(canonicalConversation(left.conversation)) === JSON.stringify(canonicalConversation(right.conversation))
1316
+ && JSON.stringify(canonicalPrincipal(left.principal)) === JSON.stringify(canonicalPrincipal(right.principal));
1317
+ }
1318
+ function leaseSessionIdentity(target) {
1319
+ const value = target.kind === 'bound' ? target.binding : target;
1320
+ return JSON.stringify({ sessionId: value.sessionId, conversation: canonicalConversation(value.conversation),
1321
+ principal: canonicalPrincipal(value.principal), workspace: value.workspace,
1322
+ agentPreset: value.agentPreset, generation: value.generation });
1323
+ }
1324
+ function leaseTargetJson(target) {
1325
+ if (target?.kind !== 'bound' && target?.kind !== 'construction')
1326
+ throw new DeliveryStoreError('invalid-binding', 'invalid session lease target');
1327
+ if (target.kind === 'bound') {
1328
+ const binding = target.binding;
1329
+ return JSON.stringify({ kind: 'bound', binding: { id: binding.id, conversation: canonicalConversation(binding.conversation), principal: canonicalPrincipal(binding.principal), workspace: binding.workspace, agentPreset: binding.agentPreset, sessionId: binding.sessionId, generation: binding.generation, version: binding.version, status: 'active' } });
1330
+ }
1331
+ return JSON.stringify({ kind: 'construction', sessionId: target.sessionId, conversation: canonicalConversation(target.conversation), principal: canonicalPrincipal(target.principal), workspace: target.workspace, agentPreset: target.agentPreset, generation: target.generation, ...(target.previous === undefined ? {} : { previous: { id: target.previous.id, conversation: canonicalConversation(target.previous.conversation), principal: canonicalPrincipal(target.previous.principal), workspace: target.previous.workspace, agentPreset: target.previous.agentPreset, sessionId: target.previous.sessionId, generation: target.previous.generation, version: target.previous.version, status: 'active' } }) });
1332
+ }
1266
1333
  export class DeliveryStore {
1267
1334
  database;
1268
1335
  databaseInstanceId;
@@ -1564,6 +1631,47 @@ export class DeliveryStore {
1564
1631
  return principalFromRow(result);
1565
1632
  });
1566
1633
  }
1634
+ /**
1635
+ * Bootstrap one empty Delivery database for a fixed local operator. This is
1636
+ * deliberately narrower than handoffOwner(): once any principal history
1637
+ * exists it can only return the exact, sole active owner without changing a
1638
+ * row, its bindings, or any linked authority.
1639
+ */
1640
+ ensureOwner(input) {
1641
+ this.assertOpen();
1642
+ const principal = canonicalPrincipal(input);
1643
+ const json = principalJson(principal);
1644
+ const hash = digest(json);
1645
+ const now = this.now();
1646
+ return this.transaction(() => {
1647
+ const total = this.database.prepare('SELECT COUNT(*) AS count FROM delivery_principals')
1648
+ .get().count;
1649
+ if (total === 0) {
1650
+ const id = `principal_${randomUUID()}`;
1651
+ this.database.prepare(`
1652
+ INSERT INTO delivery_principals (
1653
+ id, key_hash, principal_json, role, status, linked_to_id, created_at, updated_at, version
1654
+ ) VALUES (?, ?, ?, 'owner', 'active', NULL, ?, ?, 1)
1655
+ `).run(id, hash, json, now, now);
1656
+ return principalFromRow(this.database.prepare(`
1657
+ SELECT id, principal_json, role, status, linked_to_id, created_at, updated_at, version
1658
+ FROM delivery_principals WHERE id = ?
1659
+ `).get(id));
1660
+ }
1661
+ const exact = this.database.prepare(`
1662
+ SELECT id, principal_json, role, status, linked_to_id, created_at, updated_at, version
1663
+ FROM delivery_principals WHERE key_hash = ? AND principal_json = ?
1664
+ `).get(hash, json);
1665
+ const activeOwners = this.database.prepare(`
1666
+ SELECT id FROM delivery_principals WHERE role = 'owner' AND status = 'active'
1667
+ `).all();
1668
+ if (exact === undefined || exact.role !== 'owner' || exact.status !== 'active'
1669
+ || exact.linked_to_id !== null || activeOwners.length !== 1 || activeOwners[0]?.id !== exact.id) {
1670
+ throw new DeliveryStoreError('unauthorized-principal', 'local owner initialization cannot alter existing authority');
1671
+ }
1672
+ return principalFromRow(exact);
1673
+ });
1674
+ }
1567
1675
  isAuthorizedPrincipal(input) {
1568
1676
  this.assertOpen();
1569
1677
  const row = this.database.prepare('SELECT status FROM delivery_principals WHERE key_hash = ?')
@@ -1608,6 +1716,17 @@ export class DeliveryStore {
1608
1716
  const now = this.now();
1609
1717
  const id = `binding_${randomUUID()}`;
1610
1718
  return this.transaction(() => {
1719
+ const construction = input.constructionLease === undefined ? undefined : this.sessionLeaseCurrent(input.constructionLease, true);
1720
+ if (input.constructionLease !== undefined) {
1721
+ const candidate = construction === undefined ? undefined : JSON.parse(construction.target_json);
1722
+ const expected = { kind: 'construction', sessionId, conversation: target.conversation,
1723
+ principal: target.principal, workspace, agentPreset, generation: input.expectedGeneration ?? 1 };
1724
+ if (construction === undefined || candidate?.kind !== 'construction' || candidate.previous !== undefined
1725
+ || !['prepared', 'dispatched'].includes(construction.state)
1726
+ || leaseSessionIdentity(candidate) !== leaseSessionIdentity(expected)) {
1727
+ throw new DeliveryStoreError('stale-fence', 'binding construction lease is no longer exact');
1728
+ }
1729
+ }
1611
1730
  const principalRow = this.database.prepare(`
1612
1731
  SELECT id, principal_json, role, status, linked_to_id, created_at, updated_at, version
1613
1732
  FROM delivery_principals
@@ -1622,6 +1741,8 @@ export class DeliveryStore {
1622
1741
  WHERE conversation_hash = ? AND conversation_json = ? AND status = 'active'
1623
1742
  `).get(hash, canonicalConversationJson);
1624
1743
  if (existingRow !== undefined) {
1744
+ if (construction !== undefined)
1745
+ throw new DeliveryStoreError('conflict', 'construction already has a binding');
1625
1746
  const existing = bindingFromRow(existingRow);
1626
1747
  if (principalHash(existing.principal) !== canonicalPrincipalHash) {
1627
1748
  throw new DeliveryStoreError('conflict', 'conversation is already bound to another principal');
@@ -1641,7 +1762,15 @@ export class DeliveryStore {
1641
1762
  session_id, generation, policy_ref, status, created_at, updated_at, version
1642
1763
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, 1)
1643
1764
  `).run(id, hash, canonicalConversationJson, principal.id, canonicalPrincipalJson, workspace, agentPreset, sessionId, generation, policyRef, now, now);
1644
- return this.getBinding(id);
1765
+ const binding = this.getBinding(id);
1766
+ if (construction !== undefined) {
1767
+ const promoted = this.database.prepare(`UPDATE delivery_session_leases SET target_json = ?
1768
+ WHERE session_id = ? AND holder_id = ? AND fencing_token = ? AND state IN ('prepared', 'dispatched')`)
1769
+ .run(leaseTargetJson({ kind: 'bound', binding }), construction.session_id, construction.holder_id, construction.fencing_token);
1770
+ if (Number(promoted.changes) !== 1)
1771
+ throw new DeliveryStoreError('stale-fence', 'construction binding promotion lost its lease');
1772
+ }
1773
+ return binding;
1645
1774
  });
1646
1775
  }
1647
1776
  /**
@@ -2054,6 +2183,123 @@ export class DeliveryStore {
2054
2183
  }
2055
2184
  return { duplicate: false, record: this.getInbox(id) };
2056
2185
  }
2186
+ /**
2187
+ * Admit and claim one exact Host-native input under one SQLite write fence.
2188
+ *
2189
+ * The normal Delivery scheduler can only claim queued work. Publishing a
2190
+ * fresh native input directly as `claimed` therefore prevents it from
2191
+ * observing a moment where the input is in the ordinary queue. Replays are
2192
+ * read-only: a duplicate never renews, reclaims, or otherwise re-executes
2193
+ * its existing Inbox.
2194
+ */
2195
+ claimNativeInbox(input) {
2196
+ this.assertOpen();
2197
+ const envelope = canonicalEnvelope(input.envelope, this.maxTextBytes);
2198
+ const json = JSON.stringify(envelope);
2199
+ const hash = digest(json);
2200
+ const ownerId = validateBindingText(input.ownerId, 'ownerId', 256);
2201
+ if (!Number.isSafeInteger(input.leaseMs) || input.leaseMs < 1) {
2202
+ throw new DeliveryStoreError('conflict', 'invalid inbox lease');
2203
+ }
2204
+ let bindingId;
2205
+ let sessionId;
2206
+ let bindingConversationJson;
2207
+ let bindingPrincipalJson;
2208
+ try {
2209
+ bindingId = validateBindingText(input.binding.id, 'binding.id', 256);
2210
+ sessionId = validateBindingText(input.binding.sessionId, 'binding.sessionId', 512);
2211
+ bindingConversationJson = conversationJson(input.binding.conversation);
2212
+ bindingPrincipalJson = principalJson(input.binding.principal);
2213
+ }
2214
+ catch {
2215
+ throw new DeliveryStoreError('invalid-binding', 'native Inbox binding snapshot is invalid');
2216
+ }
2217
+ if (!Number.isSafeInteger(input.binding.version) || input.binding.version < 1
2218
+ || !Number.isSafeInteger(input.binding.generation) || input.binding.generation < 1) {
2219
+ throw new DeliveryStoreError('invalid-binding', 'native Inbox binding snapshot is invalid');
2220
+ }
2221
+ if (conversationJson(envelope.conversation) !== bindingConversationJson
2222
+ || principalJson(envelope.principal) !== bindingPrincipalJson) {
2223
+ throw new DeliveryStoreError('invalid-binding', 'native Inbox envelope does not belong to its binding');
2224
+ }
2225
+ const now = this.now();
2226
+ const id = `inbox_${randomUUID()}`;
2227
+ return this.transaction(() => {
2228
+ const existing = this.database.prepare(`${inboxSelect} WHERE channel = ? AND account = ? AND event_id = ?`)
2229
+ .get(envelope.channel, envelope.account, envelope.eventId);
2230
+ if (existing !== undefined) {
2231
+ if (existing.envelope_hash !== hash) {
2232
+ throw new DeliveryStoreError('idempotency-conflict', 'provider event id was reused with a different envelope');
2233
+ }
2234
+ return { duplicate: true, record: inboxFromRow(existing) };
2235
+ }
2236
+ const binding = this.database.prepare(`${bindingSelect} WHERE id = ?`).get(bindingId);
2237
+ const bindingPrincipal = this.database.prepare('SELECT principal_id FROM conversation_bindings WHERE id = ?').get(bindingId);
2238
+ const principal = this.database.prepare(`
2239
+ SELECT id, principal_json, role, status, linked_to_id, created_at, updated_at, version
2240
+ FROM delivery_principals WHERE key_hash = ? AND principal_json = ?
2241
+ `).get(principalHash(input.binding.principal), bindingPrincipalJson);
2242
+ if (binding === undefined || binding.status !== 'active'
2243
+ || binding.version !== input.binding.version || binding.generation !== input.binding.generation
2244
+ || binding.session_id !== sessionId || binding.conversation_json !== bindingConversationJson
2245
+ || binding.principal_json !== bindingPrincipalJson
2246
+ || principal === undefined || principal.id !== bindingPrincipal?.principal_id
2247
+ || principal.id !== input.ownerLineage.principalRecordId || principal.version !== input.ownerLineage.principalVersion
2248
+ || principal.status !== 'active' || principal.role !== 'owner') {
2249
+ throw new DeliveryStoreError('invalid-binding', 'native Inbox binding is no longer an active owner binding');
2250
+ }
2251
+ this.database.prepare(`
2252
+ INSERT INTO inbox_messages (
2253
+ id, channel, account, event_id, envelope_hash, envelope_json, status, binding_id,
2254
+ attempt_count, claimed_by, fencing_token, lease_until, received_at, updated_at
2255
+ ) VALUES (?, ?, ?, ?, ?, ?, 'received', ?, 0, NULL, NULL, NULL, ?, ?)
2256
+ `).run(id, envelope.channel, envelope.account, envelope.eventId, hash, json, bindingId, now, now);
2257
+ for (const [ordinal, attachment] of (envelope.attachments ?? []).entries()) {
2258
+ const descriptorHash = digest(JSON.stringify(attachment));
2259
+ this.database.prepare(`
2260
+ INSERT INTO delivery_attachments (
2261
+ id, owner_kind, owner_id, ordinal, media_type, size_bytes, sha256, spool_ref,
2262
+ resource_kind, provider_ref, file_name, status, expires_at, created_at
2263
+ ) VALUES (?, 'inbox', ?, ?, ?, ?, ?, NULL, ?, ?, ?, 'metadata', NULL, ?)
2264
+ `).run(`attachment_${digest(`${id}:${ordinal}:${attachment.providerRef}`).slice(0, 40)}`, id, ordinal, attachment.mediaType ?? '', attachment.sizeBytes ?? 0, descriptorHash, attachment.resourceType, attachment.providerRef, attachment.fileName ?? null, now);
2265
+ }
2266
+ const blocked = this.database.prepare(`
2267
+ SELECT 1 FROM inbox_messages AS earlier
2268
+ LEFT JOIN conversation_bindings AS earlier_binding ON earlier_binding.id = earlier.binding_id
2269
+ JOIN delivery_inbox_admissions AS earlier_admission ON earlier_admission.inbox_id = earlier.id
2270
+ JOIN delivery_inbox_admissions AS admitted ON admitted.inbox_id = ?
2271
+ WHERE earlier.id <> ? AND earlier.status NOT IN ('processed', 'dead_letter')
2272
+ AND (
2273
+ (earlier_binding.workspace = ? AND earlier_binding.agent_preset = ?
2274
+ AND earlier_binding.principal_id = ?)
2275
+ OR (earlier.binding_id IS NULL
2276
+ AND json_extract(earlier.envelope_json, '$.conversation') = json(?)
2277
+ AND json_extract(earlier.envelope_json, '$.principal') = json(?))
2278
+ )
2279
+ AND (earlier_admission.admission_sequence < admitted.admission_sequence
2280
+ OR earlier.status = 'claimed')
2281
+ LIMIT 1
2282
+ `).get(id, id, binding.workspace, binding.agent_preset, principal.id, bindingConversationJson, bindingPrincipalJson);
2283
+ if (blocked !== undefined) {
2284
+ throw new DeliveryStoreError('conflict', 'native Inbox lane has unfinished earlier or active work');
2285
+ }
2286
+ const previousFence = this.database.prepare('SELECT COALESCE(MAX(fencing_token), 0) AS maximum FROM inbox_attempts WHERE inbox_id = ?').get(id);
2287
+ const fencingToken = previousFence.maximum + 1;
2288
+ const claimed = this.database.prepare(`
2289
+ UPDATE inbox_messages SET status = 'claimed', claimed_by = ?, fencing_token = ?, lease_until = ?,
2290
+ attempt_count = 1, failure_code = 'native-admission', updated_at = ?
2291
+ WHERE id = ? AND status = 'received' AND binding_id = ?
2292
+ `).run(ownerId, fencingToken, now + input.leaseMs, now, id, bindingId);
2293
+ if (claimed.changes !== 1) {
2294
+ throw new DeliveryStoreError('conflict', 'native Inbox claim lost its admission fence');
2295
+ }
2296
+ this.database.prepare(`
2297
+ INSERT INTO inbox_attempts (id, inbox_id, attempt_number, owner_id, fencing_token, status, created_at)
2298
+ VALUES (?, ?, 1, ?, ?, 'claimed', ?)
2299
+ `).run(`inbox_attempt_${randomUUID()}`, id, ownerId, fencingToken, now);
2300
+ return { duplicate: false, record: this.getInbox(id), fencingToken };
2301
+ });
2302
+ }
2057
2303
  listAttachments(input) {
2058
2304
  this.assertOpen();
2059
2305
  const rows = this.database.prepare(`
@@ -2175,6 +2421,16 @@ export class DeliveryStore {
2175
2421
  }
2176
2422
  const now = this.now();
2177
2423
  this.transaction(() => {
2424
+ const current = this.getActiveBinding(record.envelope.conversation);
2425
+ const principal = this.getBindingPrincipal(binding.id);
2426
+ if (current?.id !== binding.id || current.version !== binding.version
2427
+ || current.generation !== binding.generation || current.sessionId !== binding.sessionId) {
2428
+ throw new DeliveryStoreError('invalid-binding', 'inbox binding is no longer the exact active generation');
2429
+ }
2430
+ if (principal?.status !== 'active'
2431
+ || JSON.stringify(principal.principal) !== JSON.stringify(binding.principal)) {
2432
+ throw new DeliveryStoreError('unauthorized-principal', 'inbox binding principal is not active');
2433
+ }
2178
2434
  if (record.status === 'received') {
2179
2435
  this.database.prepare(`
2180
2436
  UPDATE inbox_messages SET status = 'authorized', binding_id = ?, updated_at = ?
@@ -2337,6 +2593,27 @@ export class DeliveryStore {
2337
2593
  `).get(boundary.admission_sequence, conversationJson(target.conversation), principalJson(target.principal));
2338
2594
  return row === undefined ? undefined : inboxFromRow(row);
2339
2595
  }
2596
+ /**
2597
+ * List a bounded page of persisted admissions that have not reached the
2598
+ * worker queue yet. The admission cursor, rather than timestamps or rowid,
2599
+ * preserves the global order across restarts and database maintenance.
2600
+ */
2601
+ listRecoverableInboundAdmissions(input = {}) {
2602
+ this.assertOpen();
2603
+ const afterSequence = input.afterSequence ?? 0;
2604
+ if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
2605
+ throw new DeliveryStoreError('conflict', 'invalid inbound admission recovery cursor');
2606
+ }
2607
+ const limit = boundedMaintenanceLimit(input.limit, 100, 'inbound admission recovery');
2608
+ const rows = this.database.prepare(`${inboxSelect}
2609
+ JOIN delivery_inbox_admissions AS pending_admission
2610
+ ON pending_admission.inbox_id = inbox_messages.id
2611
+ WHERE inbox_messages.status IN ('received', 'authorized')
2612
+ AND pending_admission.admission_sequence > ?
2613
+ ORDER BY pending_admission.admission_sequence LIMIT ?
2614
+ `).all(afterSequence, limit);
2615
+ return rows.map(inboxFromRow);
2616
+ }
2340
2617
  listInbox(input = {}) {
2341
2618
  this.assertOpen();
2342
2619
  const limit = Math.max(1, Math.min(100, input.limit ?? 20));
@@ -2569,7 +2846,9 @@ export class DeliveryStore {
2569
2846
  `).all(now, input.maxAttempts, input.limit);
2570
2847
  for (const candidate of candidates) {
2571
2848
  const current = this.getInbox(candidate.id);
2572
- const fencingToken = current.attemptCount + 1;
2849
+ const previousFence = this.database.prepare('SELECT COALESCE(MAX(fencing_token), 0) AS maximum FROM inbox_attempts WHERE inbox_id = ?')
2850
+ .get(current.id);
2851
+ const fencingToken = previousFence.maximum + 1;
2573
2852
  const changed = this.database.prepare(`
2574
2853
  UPDATE inbox_messages SET status = 'claimed', claimed_by = ?, fencing_token = ?, lease_until = ?,
2575
2854
  attempt_count = attempt_count + 1, next_attempt_at = NULL,
@@ -2616,6 +2895,49 @@ export class DeliveryStore {
2616
2895
  });
2617
2896
  return this.getInbox(input.inboxId);
2618
2897
  }
2898
+ /** Return an undispatched Inbox to its queue while another live session holds the lane. */
2899
+ deferInboxForSessionLease(input) {
2900
+ this.assertOpen();
2901
+ let inboxId;
2902
+ let ownerId;
2903
+ try {
2904
+ inboxId = validateBindingText(input.inboxId, 'inboxId', 256);
2905
+ ownerId = validateBindingText(input.ownerId, 'ownerId', 256);
2906
+ }
2907
+ catch {
2908
+ return false;
2909
+ }
2910
+ const now = this.now();
2911
+ if (!Number.isSafeInteger(input.fencingToken) || input.fencingToken < 1
2912
+ || !Number.isSafeInteger(input.retryAt) || input.retryAt < now)
2913
+ return false;
2914
+ return this.transaction(() => {
2915
+ const changed = this.database.prepare(`
2916
+ UPDATE inbox_messages AS inbox
2917
+ SET status = 'retry_wait', next_attempt_at = ?, attempt_count = attempt_count - 1,
2918
+ claimed_by = NULL, lease_until = NULL, failure_code = 'session-lease-busy', updated_at = ?
2919
+ WHERE inbox.id = ? AND inbox.status = 'claimed' AND inbox.claimed_by = ?
2920
+ AND inbox.fencing_token = ? AND inbox.lease_until > ? AND inbox.attempt_count >= 1
2921
+ AND inbox.failure_code IS NULL
2922
+ AND EXISTS (
2923
+ SELECT 1 FROM conversation_bindings AS binding
2924
+ JOIN delivery_session_leases AS lease ON lease.session_id = binding.session_id
2925
+ WHERE binding.id = inbox.binding_id AND lease.state IN ('prepared', 'dispatched')
2926
+ AND lease.lease_until > ?
2927
+ )
2928
+ `).run(input.retryAt, now, inboxId, ownerId, input.fencingToken, now, now);
2929
+ if (Number(changed.changes) !== 1)
2930
+ return false;
2931
+ const settled = this.database.prepare(`
2932
+ UPDATE inbox_attempts SET status = 'retry_wait', failure_code = 'session-lease-busy', finished_at = ?
2933
+ WHERE inbox_id = ? AND owner_id = ? AND fencing_token = ? AND status = 'claimed'
2934
+ `).run(now, inboxId, ownerId, input.fencingToken);
2935
+ if (Number(settled.changes) !== 1) {
2936
+ throw new DeliveryStoreError('conflict', 'session lease defer attempt settlement is missing');
2937
+ }
2938
+ return true;
2939
+ });
2940
+ }
2619
2941
  markInboxDispatching(input) {
2620
2942
  this.assertOpen();
2621
2943
  let bindingId;
@@ -2638,7 +2960,7 @@ export class DeliveryStore {
2638
2960
  const now = this.now();
2639
2961
  return this.transaction(() => {
2640
2962
  const changed = this.database.prepare(`
2641
- UPDATE inbox_messages AS inbox SET failure_code = CASE WHEN failure_code IN (
2963
+ UPDATE inbox_messages AS inbox SET failure_code = CASE WHEN failure_code = 'native-admission' THEN 'native-dispatch-started' WHEN failure_code IN (
2642
2964
  'permission-dispatch-recovery',
2643
2965
  'permission-cancelled-recovery',
2644
2966
  'permission-failure-notice-recovery',
@@ -2675,6 +2997,99 @@ export class DeliveryStore {
2675
2997
  throw new DeliveryStoreError('invalid-binding', 'inbox dispatch binding snapshot is no longer active');
2676
2998
  });
2677
2999
  }
3000
+ /** Immutable pre-prompt binding for a foreground acceptance contract. */
3001
+ bindForegroundTaskAcceptance(input) {
3002
+ this.assertOpen();
3003
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u.test(input.contractId)
3004
+ || !/^[a-f0-9]{64}$/u.test(input.contractDigest)
3005
+ || !Number.isSafeInteger(input.dispatchedAt) || input.dispatchedAt < 0
3006
+ || !Number.isSafeInteger(input.owner.principalVersion) || input.owner.principalVersion < 1
3007
+ || !Number.isSafeInteger(input.binding.version) || input.binding.version < 1
3008
+ || !Number.isSafeInteger(input.binding.generation) || input.binding.generation < 1) {
3009
+ throw new DeliveryStoreError('invalid-binding', 'foreground acceptance binding is invalid');
3010
+ }
3011
+ const text = [input.inboxId, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.binding.id];
3012
+ if (text.some(value => typeof value !== 'string' || value.trim() === '' || Buffer.byteLength(value, 'utf8') > 4_096)) {
3013
+ throw new DeliveryStoreError('invalid-binding', 'foreground acceptance binding text is invalid');
3014
+ }
3015
+ this.transaction(() => {
3016
+ const existing = this.database.prepare('SELECT * FROM delivery_task_acceptance_executions WHERE inbox_id = ?')
3017
+ .get(input.inboxId);
3018
+ if (existing !== undefined) {
3019
+ if (existing.contract_id !== input.contractId || existing.contract_digest !== input.contractDigest
3020
+ || existing.workspace !== input.scope.workspace || existing.preset !== input.scope.preset
3021
+ || existing.principal_record_id !== input.owner.principalRecordId || existing.principal_version !== input.owner.principalVersion
3022
+ || existing.binding_id !== input.binding.id || existing.binding_version !== input.binding.version
3023
+ || existing.binding_generation !== input.binding.generation) {
3024
+ throw new DeliveryStoreError('idempotency-conflict', 'foreground acceptance binding changed');
3025
+ }
3026
+ throw new DeliveryStoreError('idempotency-conflict', 'accepted foreground task has already crossed its dispatch boundary');
3027
+ }
3028
+ const admitted = this.database.prepare(`
3029
+ SELECT inbox.id FROM inbox_messages AS inbox
3030
+ JOIN conversation_bindings AS binding ON binding.id = inbox.binding_id
3031
+ JOIN delivery_principals AS principal ON principal.id = binding.principal_id
3032
+ WHERE inbox.id = ? AND inbox.status = 'claimed' AND inbox.binding_id = ?
3033
+ AND binding.status = 'active' AND binding.version = ? AND binding.generation = ?
3034
+ AND principal.status = 'active' AND principal.role = 'owner'
3035
+ AND principal.id = ? AND principal.version = ?
3036
+ AND binding.workspace = ? AND binding.agent_preset = ?
3037
+ `).get(input.inboxId, input.binding.id, input.binding.version, input.binding.generation, input.owner.principalRecordId, input.owner.principalVersion, input.scope.workspace, input.scope.preset);
3038
+ if (admitted === undefined)
3039
+ throw new DeliveryStoreError('invalid-binding', 'foreground acceptance is not bound to the claimed owner inbox');
3040
+ this.database.prepare(`INSERT INTO delivery_task_acceptance_executions(
3041
+ inbox_id, contract_id, contract_digest, workspace, preset, principal_record_id, principal_version,
3042
+ binding_id, binding_version, binding_generation, dispatched_at, status, quiescent
3043
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0)`).run(input.inboxId, input.contractId, input.contractDigest, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.owner.principalVersion, input.binding.id, input.binding.version, input.binding.generation, input.dispatchedAt);
3044
+ });
3045
+ }
3046
+ finishForegroundTaskAcceptance(input) {
3047
+ this.assertOpen();
3048
+ if (!['succeeded', 'failed', 'timed-out', 'cancelled', 'unknown'].includes(input.status)
3049
+ || !Number.isSafeInteger(input.completedAt) || input.completedAt < 0) {
3050
+ throw new DeliveryStoreError('invalid-binding', 'foreground acceptance completion is invalid');
3051
+ }
3052
+ const status = input.quiescent && input.status === 'succeeded' ? 'succeeded' : 'unknown';
3053
+ const quiescent = status === 'succeeded' ? 1 : 0;
3054
+ const changed = this.database.prepare(`UPDATE delivery_task_acceptance_executions
3055
+ SET status = ?, quiescent = ?, completed_at = ?, execution_ref = inbox_id
3056
+ WHERE contract_id = ? AND status = 'pending' AND dispatched_at <= ?`).run(status, quiescent, input.completedAt, input.contractId, input.completedAt);
3057
+ if (changed.changes !== 1) {
3058
+ const current = this.database.prepare('SELECT status, quiescent, completed_at FROM delivery_task_acceptance_executions WHERE contract_id = ?')
3059
+ .get(input.contractId);
3060
+ if (current?.status === status && current.quiescent === quiescent && current.completed_at === input.completedAt)
3061
+ return;
3062
+ throw new DeliveryStoreError('conflict', 'foreground acceptance binding is absent or its completion changed');
3063
+ }
3064
+ }
3065
+ inspectForegroundAcceptedExecution(contract) {
3066
+ const row = this.database.prepare(`SELECT * FROM delivery_task_acceptance_executions
3067
+ WHERE contract_id = ?`).get(contract.id);
3068
+ if (row === undefined || row.contract_digest !== contract.digest || contract.task.kind !== 'foreground-turn'
3069
+ || contract.task.ref !== row.inbox_id || row.workspace !== contract.scope.workspace || row.preset !== contract.scope.preset
3070
+ || row.principal_record_id !== contract.owner.principalRecordId || row.principal_version !== contract.owner.principalVersion
3071
+ || row.completed_at === null || row.execution_ref === null)
3072
+ return null;
3073
+ return Object.freeze({ contractId: row.contract_id, contractDigest: row.contract_digest,
3074
+ dispatchedAt: row.dispatched_at, status: row.status, quiescent: row.quiescent === 1,
3075
+ completedAt: row.completed_at, executionRef: row.execution_ref });
3076
+ }
3077
+ /**
3078
+ * Re-read the immutable acceptance fence for one exact owner reply target.
3079
+ * This deliberately exposes only the completed execution receipt: callers
3080
+ * cannot manufacture a foreground subject by naming an arbitrary Inbox.
3081
+ */
3082
+ inspectForegroundAcceptedExecutionForOwner(input) {
3083
+ const row = this.database.prepare(`SELECT contract_id, contract_digest, dispatched_at, status, quiescent, completed_at, execution_ref
3084
+ FROM delivery_task_acceptance_executions
3085
+ WHERE inbox_id = ? AND workspace = ? AND preset = ? AND principal_record_id = ?
3086
+ AND principal_version = ? AND binding_id = ?`).get(input.inboxId, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.owner.principalVersion, input.bindingId);
3087
+ if (row === undefined || row.completed_at === null || row.execution_ref !== input.inboxId)
3088
+ return null;
3089
+ return Object.freeze({ contractId: row.contract_id, contractDigest: row.contract_digest,
3090
+ dispatchedAt: row.dispatched_at, status: row.status, quiescent: row.quiescent === 1,
3091
+ completedAt: row.completed_at, executionRef: row.execution_ref });
3092
+ }
2678
3093
  renewInboxClaim(input) {
2679
3094
  this.assertOpen();
2680
3095
  const ownerId = validateBindingText(input.ownerId, 'ownerId', 256);
@@ -2752,8 +3167,10 @@ export class DeliveryStore {
2752
3167
  : existingPermissionRecovery === undefined
2753
3168
  ? undefined
2754
3169
  : permissionDispatchRecoveryCode(existingPermissionRecovery);
2755
- const ambiguous = row.failure_code === 'dispatch-started'
2756
- && permissionRecovery === undefined && !feedbackRecovery && !learningRecovery && !workflowRecovery;
3170
+ const acceptedDispatch = this.database.prepare('SELECT inbox_id FROM delivery_task_acceptance_executions WHERE inbox_id = ?').get(row.id) !== undefined;
3171
+ const ambiguous = acceptedDispatch || row.failure_code === 'native-admission' || row.failure_code === 'native-dispatch-started'
3172
+ || (row.failure_code === 'dispatch-started'
3173
+ && permissionRecovery === undefined && !feedbackRecovery && !learningRecovery && !workflowRecovery);
2757
3174
  const exhausted = row.attempt_count >= input.maxAttempts
2758
3175
  && permissionRecovery === undefined && !feedbackRecovery && !learningRecovery && !workflowRecovery;
2759
3176
  const changed = this.database.prepare(`
@@ -2891,6 +3308,65 @@ export class DeliveryStore {
2891
3308
  return outbox;
2892
3309
  });
2893
3310
  }
3311
+ /** Atomically insert a typed whole-goal result and its immutable authority sidecar. */
3312
+ enqueueGoalOutcomeTarget(input) {
3313
+ this.assertOpen();
3314
+ let locator;
3315
+ let proof;
3316
+ try {
3317
+ locator = validateOwnerGoalOutcomeFeedbackLocator(input.locator);
3318
+ proof = validateOwnerGoalOutcomeFeedbackProof(input.proof);
3319
+ }
3320
+ catch {
3321
+ throw new DeliveryStoreError('invalid-intent', 'goal outcome target is invalid');
3322
+ }
3323
+ if (acceptanceCanonicalJson(locator) !== acceptanceCanonicalJson(proof.locator)) {
3324
+ throw new DeliveryStoreError('invalid-intent', 'goal outcome locator does not match its proof');
3325
+ }
3326
+ return this.transaction(() => {
3327
+ const binding = this.getBinding(locator.bindingId);
3328
+ const owner = binding === undefined ? undefined : this.getBindingPrincipal(binding.id);
3329
+ if (binding === undefined || binding.status !== 'active'
3330
+ || binding.version !== locator.bindingVersion || binding.generation !== locator.bindingGeneration
3331
+ || binding.sessionId !== locator.sessionId || binding.workspace !== locator.workspace
3332
+ || binding.agentPreset !== locator.preset || externalPrincipalId(binding.principal) !== locator.principalId
3333
+ || owner?.status !== 'active' || owner.role !== 'owner' || owner.id !== locator.principalRecordId
3334
+ || owner.version !== locator.principalVersion || externalPrincipalId(owner.principal) !== locator.principalId) {
3335
+ throw new DeliveryStoreError('invalid-binding', 'goal outcome target exact owner fence changed');
3336
+ }
3337
+ const intent = canonicalIntent(input.intent, binding, this.maxTextBytes);
3338
+ const intentHash = digest(JSON.stringify(intent));
3339
+ const winnerRow = this.database.prepare(`${outboxSelect} WHERE idempotency_key = ?`)
3340
+ .get(intent.idempotencyKey);
3341
+ if (winnerRow !== undefined) {
3342
+ const sidecar = this.getGoalOutcomeTarget(winnerRow.id);
3343
+ if (winnerRow.intent_hash !== intentHash || sidecar === undefined
3344
+ || !sameGoalOutcomeTarget(sidecar, locator, proof)) {
3345
+ throw new DeliveryStoreError('idempotency-conflict', 'goal outcome Outbox winner does not match the exact immutable target and intent');
3346
+ }
3347
+ return outboxFromRow(winnerRow);
3348
+ }
3349
+ const outbox = this.enqueueIntent(intent, false);
3350
+ this.database.prepare(`
3351
+ INSERT INTO delivery_goal_outcome_targets(
3352
+ outbox_id, locator_json, proof_json, proof_digest, owner_route_id, principal_id,
3353
+ principal_record_id, principal_version, workspace, preset, binding_id, binding_version,
3354
+ binding_generation, session_id, goal_id, assessment_id
3355
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3356
+ `).run(outbox.id, acceptanceCanonicalJson(locator), acceptanceCanonicalJson(proof), proof.proofDigest, locator.ownerRouteId, locator.principalId, locator.principalRecordId, locator.principalVersion, locator.workspace, locator.preset, locator.bindingId, locator.bindingVersion, locator.bindingGeneration, locator.sessionId, locator.goalId, locator.assessmentId);
3357
+ return outbox;
3358
+ });
3359
+ }
3360
+ getGoalOutcomeTarget(outboxId) {
3361
+ this.assertOpen();
3362
+ const row = this.database.prepare(`
3363
+ SELECT outbox_id, locator_json, proof_json, proof_digest, owner_route_id, principal_id,
3364
+ principal_record_id, principal_version, workspace, preset, binding_id, binding_version,
3365
+ binding_generation, session_id, goal_id, assessment_id
3366
+ FROM delivery_goal_outcome_targets WHERE outbox_id = ?
3367
+ `).get(outboxId);
3368
+ return row === undefined ? undefined : goalOutcomeTargetFromRow(row);
3369
+ }
2894
3370
  /**
2895
3371
  * Resolve and enqueue one Host-owned stable owner route under the same
2896
3372
  * `BEGIN IMMEDIATE` fence as Outbox insertion.
@@ -4456,10 +4932,26 @@ export class DeliveryStore {
4456
4932
  * A normal durable reply alone is intentionally insufficient; the owner
4457
4933
  * must have replied `/feedback achieved` to that exact provider message.
4458
4934
  */
4935
+ verifiedWorkflowObjectiveState(sourceOutboxId, lineage) {
4936
+ const row = this.database.prepare(`SELECT version, objective_status, principal_record_id, principal_version FROM delivery_owner_objective_commands
4937
+ WHERE source_outbox_id = ? AND json_extract(result_json, '$.error') IS NULL ORDER BY version DESC LIMIT 1`).get(sourceOutboxId);
4938
+ if (row !== undefined) {
4939
+ if (lineage !== undefined && (row.principal_record_id !== lineage.principalRecordId || row.principal_version !== lineage.principalVersion))
4940
+ return undefined;
4941
+ return { version: row.version, objectiveStatus: row.objective_status };
4942
+ }
4943
+ const initial = this.database.prepare(`SELECT objective_status, principal_record_id FROM workflow_verified_task_feedback WHERE source_outbox_id = ?`)
4944
+ .get(sourceOutboxId);
4945
+ // Legacy receipts predate principal-version audit. Only an unchanged first
4946
+ // principal incarnation can prove that no authority turnover occurred.
4947
+ if (lineage !== undefined && (lineage.principalVersion !== 1 || initial?.principal_record_id !== lineage.principalRecordId))
4948
+ return undefined;
4949
+ return initial === undefined ? undefined : { version: 1, objectiveStatus: initial.objective_status };
4950
+ }
4459
4951
  commitVerifiedWorkflowTraceFeedback(input) {
4460
4952
  this.assertOpen();
4461
4953
  if (typeof input !== 'object' || input === null
4462
- || !['achieved', 'partial', 'not-achieved'].includes(input.objectiveStatus)
4954
+ || !['achieved', 'partial', 'not-achieved', 'unknown'].includes(input.objectiveStatus)
4463
4955
  || typeof input.feedbackInboxId !== 'string' || typeof input.sourceInboxId !== 'string'
4464
4956
  || typeof input.sourceOutboxId !== 'string') {
4465
4957
  throw new DeliveryStoreError('conflict', 'verified workflow feedback tuple is invalid');
@@ -4467,7 +4959,7 @@ export class DeliveryStore {
4467
4959
  const feedbackInboxId = validateBindingText(input.feedbackInboxId, 'workflow feedback inbox id', 256);
4468
4960
  const sourceInboxId = validateBindingText(input.sourceInboxId, 'workflow source inbox id', 256);
4469
4961
  const sourceOutboxId = validateBindingText(input.sourceOutboxId, 'workflow source outbox id', 256);
4470
- return this.transaction(() => {
4962
+ const committed = this.transaction(() => {
4471
4963
  const binding = this.getBinding(input.binding.id);
4472
4964
  const owner = binding === undefined ? undefined : this.getPrincipal(binding.principal);
4473
4965
  if (binding === undefined || binding.status !== 'active' || binding.conversation.kind !== 'dm'
@@ -4490,7 +4982,9 @@ export class DeliveryStore {
4490
4982
  : parseFeedbackCommand(feedbackCommand.rawInput);
4491
4983
  if (feedbackInbox?.status !== 'claimed' || feedbackInbox.bindingId !== binding.id
4492
4984
  || (feedbackInbox.envelope.attachments?.length ?? 0) !== 0
4493
- || feedback?.kind !== 'objective' || feedback.objectiveStatus !== input.objectiveStatus
4985
+ || (feedback?.kind !== 'objective' && feedback?.kind !== 'objective-revision')
4986
+ || feedback.objectiveStatus !== input.objectiveStatus
4987
+ || JSON.stringify(feedback.kind === 'objective-revision' ? feedback : undefined) !== JSON.stringify(input.command)
4494
4988
  || sourceInbox?.status !== 'processed' || sourceInbox.bindingId !== binding.id
4495
4989
  || sourceInbox.envelope.kind !== 'text' || (sourceInbox.envelope.attachments?.length ?? 0) !== 0
4496
4990
  || sourceOutbox === undefined || sourceOutbox.intent.bindingId !== binding.id
@@ -4512,6 +5006,58 @@ export class DeliveryStore {
4512
5006
  || JSON.stringify(sourceOutbox.intent.target.principal) !== JSON.stringify(binding.principal)) {
4513
5007
  throw new DeliveryStoreError('conflict', 'verified workflow feedback does not target one completed owner turn');
4514
5008
  }
5009
+ const replay = this.database.prepare(`SELECT result_json, principal_record_id, principal_version FROM delivery_owner_objective_commands WHERE inbox_id = ?`)
5010
+ .get(feedbackInbox.id);
5011
+ if (replay !== undefined) {
5012
+ if (replay.principal_record_id !== owner.id || replay.principal_version !== owner.version) {
5013
+ throw new DeliveryStoreError('unauthorized-principal', 'owner command lineage changed');
5014
+ }
5015
+ const receipt = JSON.parse(replay.result_json);
5016
+ if (receipt.error === 'version-conflict' || receipt.error === 'idempotency-conflict')
5017
+ return new DeliveryStoreError(receipt.error, 'feedback version changed; reply /feedback status and retry');
5018
+ return { ...receipt, replayed: true };
5019
+ }
5020
+ const currentState = this.verifiedWorkflowObjectiveState(sourceOutbox.id, { principalRecordId: owner.id, principalVersion: owner.version });
5021
+ const currentOwner = this.database.prepare(`SELECT principal_record_id, principal_version FROM delivery_owner_objective_commands
5022
+ WHERE source_outbox_id = ? AND json_extract(result_json, '$.error') IS NULL ORDER BY version DESC LIMIT 1`).get(sourceOutbox.id);
5023
+ if (currentOwner !== undefined && (currentOwner.principal_record_id !== owner.id || currentOwner.principal_version !== owner.version)) {
5024
+ throw new DeliveryStoreError('unauthorized-principal', 'owner lineage changed');
5025
+ }
5026
+ if (input.command !== undefined && (currentState === undefined
5027
+ || currentState.version !== input.command.expectedVersion
5028
+ || currentState.objectiveStatus !== input.command.previousStatus)) {
5029
+ this.database.prepare(`INSERT INTO delivery_owner_objective_commands VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
5030
+ .run(feedbackInbox.id, sourceOutbox.id, owner.id, owner.version, currentState?.version ?? 0, currentState?.objectiveStatus ?? 'unknown', JSON.stringify(feedback), JSON.stringify({ error: 'version-conflict' }));
5031
+ return new DeliveryStoreError('version-conflict', 'feedback version changed; reply /feedback status and retry');
5032
+ }
5033
+ const nextVersion = currentState === undefined ? 1 : currentState.objectiveStatus === input.objectiveStatus
5034
+ ? currentState.version : currentState.version + 1;
5035
+ const finish = (result) => {
5036
+ const response = { ...result, ownerFeedbackState: { version: nextVersion, objectiveStatus: input.objectiveStatus } };
5037
+ this.database.prepare(`INSERT INTO delivery_owner_objective_commands VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
5038
+ .run(feedbackInbox.id, sourceOutbox.id, owner.id, owner.version, nextVersion, input.objectiveStatus, JSON.stringify(feedback), JSON.stringify(response));
5039
+ return response;
5040
+ };
5041
+ if (currentState !== undefined && (input.command !== undefined || currentState.version >= 1)
5042
+ && (input.command === undefined || currentState.objectiveStatus === input.objectiveStatus)) {
5043
+ if (currentState.objectiveStatus !== input.objectiveStatus) {
5044
+ this.database.prepare(`INSERT INTO delivery_owner_objective_commands VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
5045
+ .run(feedbackInbox.id, sourceOutbox.id, owner.id, owner.version, currentState.version, currentState.objectiveStatus, JSON.stringify(feedback), JSON.stringify({ error: 'idempotency-conflict' }));
5046
+ return new DeliveryStoreError('idempotency-conflict', 'current owner judgement differs; use explicit correction');
5047
+ }
5048
+ const currentReceipt = this.database.prepare(`SELECT result_json FROM delivery_owner_objective_commands
5049
+ WHERE source_outbox_id = ? AND version = ? AND json_extract(result_json, '$.error') IS NULL LIMIT 1`)
5050
+ .get(sourceOutbox.id, currentState.version);
5051
+ if (currentReceipt !== undefined)
5052
+ return finish({ ...JSON.parse(currentReceipt.result_json), replayed: true });
5053
+ }
5054
+ const subjectRef = growthObjectDigest({
5055
+ contract: 'assistant-delivery-verified-workflow-subject/v1',
5056
+ bindingId: binding.id, sourceInboxId: sourceInbox.id, sourceOutboxId: sourceOutbox.id,
5057
+ });
5058
+ const traceHead = this.database.prepare(`SELECT version FROM workflow_trace_current WHERE subject_ref = ?`)
5059
+ .get(subjectRef);
5060
+ const traceVersion = (traceHead?.version ?? 0) + 1;
4515
5061
  const taskRef = growthObjectDigest({
4516
5062
  contract: 'assistant-delivery-verified-workflow-task-ref/v1',
4517
5063
  bindingId: binding.id,
@@ -4546,7 +5092,7 @@ export class DeliveryStore {
4546
5092
  const prior = this.database.prepare(`
4547
5093
  SELECT * FROM workflow_verified_task_feedback WHERE source_outbox_id = ?
4548
5094
  `).get(sourceOutbox.id);
4549
- if (prior !== undefined) {
5095
+ if (prior !== undefined && input.command === undefined) {
4550
5096
  return this.replayVerifiedWorkflowTraceFeedback({
4551
5097
  row: prior,
4552
5098
  binding,
@@ -4563,21 +5109,28 @@ export class DeliveryStore {
4563
5109
  ? deriveDeterministicallyDeidentifiedWorkflowTemplate(sourceInbox.envelope.text)
4564
5110
  : undefined;
4565
5111
  if (descriptor === undefined) {
4566
- this.insertVerifiedWorkflowTaskFeedback({
4567
- sourceOutboxId: sourceOutbox.id,
4568
- sourceInboxId: sourceInbox.id,
4569
- feedbackInboxId: feedbackInbox.id,
4570
- binding,
4571
- ownerId: owner.id,
4572
- objectiveStatus: input.objectiveStatus,
4573
- taskRef,
4574
- taskEvidenceDigest,
4575
- });
4576
- return Object.freeze({
5112
+ if (input.command !== undefined && traceHead !== undefined) {
5113
+ const payload = { source: this.workflowTraceSourceAttestation(),
5114
+ scope: { workspace: binding.workspace, preset: binding.agentPreset },
5115
+ subjectRef, version: traceVersion, disposition: 'retract' };
5116
+ this.insertWorkflowTraceRevisionInTransaction({ ...payload, digest: workflowTraceRevisionDigest(payload) });
5117
+ }
5118
+ if (input.command === undefined)
5119
+ this.insertVerifiedWorkflowTaskFeedback({
5120
+ sourceOutboxId: sourceOutbox.id,
5121
+ sourceInboxId: sourceInbox.id,
5122
+ feedbackInboxId: feedbackInbox.id,
5123
+ binding,
5124
+ ownerId: owner.id,
5125
+ objectiveStatus: input.objectiveStatus,
5126
+ taskRef,
5127
+ taskEvidenceDigest,
5128
+ });
5129
+ return finish(Object.freeze({
4577
5130
  outcome: 'no-trace',
4578
5131
  reason: input.objectiveStatus === 'achieved' ? 'privacy-abstained' : 'objective-not-achieved',
4579
5132
  replayed: false,
4580
- });
5133
+ }));
4581
5134
  }
4582
5135
  const content = validateWorkflowAutomationTemplateContent({
4583
5136
  scope: { workspace: binding.workspace, preset: binding.agentPreset },
@@ -4641,17 +5194,11 @@ export class DeliveryStore {
4641
5194
  'active', ?, ?, ?, ?, ?, 1)
4642
5195
  `).run(template.templateRef, template.templateDigest, workflowScopeKey(content.scope), content.scope.workspace, content.scope.preset, binding.id, JSON.stringify(content), template.privacyAttestation.attestationId, template.privacyAttestation.attestationDigest, JSON.stringify(privacyReceipt), feedbackInbox.id, sourceInbox.id, sourceOutbox.id, now, now);
4643
5196
  }
4644
- const subjectRef = growthObjectDigest({
4645
- contract: 'assistant-delivery-verified-workflow-subject/v1',
4646
- bindingId: binding.id,
4647
- sourceInboxId: sourceInbox.id,
4648
- sourceOutboxId: sourceOutbox.id,
4649
- });
4650
5197
  const payload = Object.freeze({
4651
5198
  source: this.workflowTraceSourceAttestation(),
4652
5199
  scope: Object.freeze({ workspace: binding.workspace, preset: binding.agentPreset }),
4653
5200
  subjectRef,
4654
- version: 1,
5201
+ version: traceVersion,
4655
5202
  disposition: 'upsert',
4656
5203
  evidence: Object.freeze({
4657
5204
  occurredAt: feedbackInbox.receivedAt,
@@ -4675,20 +5222,24 @@ export class DeliveryStore {
4675
5222
  if (recorded.replayed) {
4676
5223
  throw new DeliveryStoreError('version-conflict', 'new verified workflow feedback unexpectedly replayed');
4677
5224
  }
4678
- this.insertVerifiedWorkflowTaskFeedback({
4679
- sourceOutboxId: sourceOutbox.id,
4680
- sourceInboxId: sourceInbox.id,
4681
- feedbackInboxId: feedbackInbox.id,
4682
- binding,
4683
- ownerId: owner.id,
4684
- objectiveStatus: input.objectiveStatus,
4685
- taskRef,
4686
- taskEvidenceDigest,
4687
- revision,
4688
- template,
4689
- });
4690
- return Object.freeze({ outcome: 'trace-recorded', revision, template, replayed: false });
5225
+ if (input.command === undefined)
5226
+ this.insertVerifiedWorkflowTaskFeedback({
5227
+ sourceOutboxId: sourceOutbox.id,
5228
+ sourceInboxId: sourceInbox.id,
5229
+ feedbackInboxId: feedbackInbox.id,
5230
+ binding,
5231
+ ownerId: owner.id,
5232
+ objectiveStatus: 'achieved',
5233
+ taskRef,
5234
+ taskEvidenceDigest,
5235
+ revision,
5236
+ template,
5237
+ });
5238
+ return finish(Object.freeze({ outcome: 'trace-recorded', revision, template, replayed: false }));
4691
5239
  });
5240
+ if (committed instanceof DeliveryStoreError)
5241
+ throw committed;
5242
+ return committed;
4692
5243
  }
4693
5244
  insertVerifiedWorkflowTaskFeedback(input) {
4694
5245
  if ((input.revision === undefined) !== (input.template === undefined)) {
@@ -5508,6 +6059,183 @@ export class DeliveryStore {
5508
6059
  throw new DeliveryStoreError('version-conflict', 'workflow template revocation lost its exact version');
5509
6060
  }
5510
6061
  }
6062
+ /** Ownership survives release, revocation and restart; it is not an active-lease query. */
6063
+ requiresSessionLease(sessionId) {
6064
+ this.assertOpen();
6065
+ sessionId = validateBindingText(sessionId, 'sessionId', 512);
6066
+ const row = this.database.prepare(`
6067
+ SELECT (EXISTS (SELECT 1 FROM conversation_bindings WHERE session_id = ?)
6068
+ OR EXISTS (SELECT 1 FROM delivery_session_leases WHERE session_id = ?)) AS managed
6069
+ `).get(sessionId, sessionId);
6070
+ return row.managed === 1;
6071
+ }
6072
+ claimSessionLease(target, holderId, leaseMs) {
6073
+ this.assertOpen();
6074
+ if (!Number.isSafeInteger(leaseMs) || leaseMs < 1 || leaseMs > 300_000)
6075
+ return { kind: 'denied' };
6076
+ let targetJson;
6077
+ let sessionId;
6078
+ try {
6079
+ targetJson = leaseTargetJson(target);
6080
+ sessionId = validateBindingText(target.kind === 'bound' ? target.binding.sessionId : target.sessionId, 'sessionId', 512);
6081
+ holderId = validateBindingText(holderId, 'holderId', 256);
6082
+ }
6083
+ catch {
6084
+ return { kind: 'denied' };
6085
+ }
6086
+ const now = this.now();
6087
+ const leaseUntil = now + leaseMs;
6088
+ if (!Number.isSafeInteger(now) || now < 0 || !Number.isSafeInteger(leaseUntil))
6089
+ return { kind: 'denied' };
6090
+ return this.transaction(() => {
6091
+ const authority = this.sessionLeaseAuthority(target);
6092
+ if (authority === undefined)
6093
+ return { kind: 'denied' };
6094
+ const row = this.database.prepare('SELECT session_id, holder_id, fencing_token, lease_until, state, target_json, principal_record_id, principal_version FROM delivery_session_leases WHERE session_id = ?').get(sessionId);
6095
+ if (row === undefined) {
6096
+ this.database.prepare("INSERT INTO delivery_session_leases(session_id, holder_id, fencing_token, lease_until, state, target_json, principal_record_id, principal_version) VALUES (?, ?, 1, ?, 'prepared', ?, ?, ?)")
6097
+ .run(sessionId, holderId, leaseUntil, targetJson, authority.id, authority.version);
6098
+ return { kind: 'claimed', lease: Object.freeze({ sessionId, holderId, fencingToken: 1, leaseUntil }) };
6099
+ }
6100
+ // A released construction can outlive a crash before createBinding. Its Session
6101
+ // remains owned by the original identity, even though no binding exists yet.
6102
+ try {
6103
+ if (row.principal_record_id !== authority.id
6104
+ || leaseSessionIdentity(JSON.parse(row.target_json)) !== leaseSessionIdentity(target))
6105
+ return { kind: 'denied' };
6106
+ }
6107
+ catch {
6108
+ return { kind: 'denied' };
6109
+ }
6110
+ if (row.state === 'unknown')
6111
+ return { kind: 'unknown' };
6112
+ if (row.state === 'dispatched') {
6113
+ if (row.lease_until > now)
6114
+ return { kind: 'busy' };
6115
+ this.database.prepare("UPDATE delivery_session_leases SET state = 'unknown' WHERE session_id = ? AND fencing_token = ? AND state = 'dispatched'")
6116
+ .run(sessionId, row.fencing_token);
6117
+ return { kind: 'unknown' };
6118
+ }
6119
+ if (row.state !== 'prepared' && row.state !== 'released')
6120
+ return { kind: 'unknown' };
6121
+ if (row.state === 'prepared' && row.lease_until > now)
6122
+ return { kind: 'busy' };
6123
+ const token = row.fencing_token + 1;
6124
+ if (!Number.isSafeInteger(token))
6125
+ return { kind: 'denied' };
6126
+ this.database.prepare("UPDATE delivery_session_leases SET holder_id = ?, fencing_token = ?, lease_until = ?, state = 'prepared', target_json = ?, principal_record_id = ?, principal_version = ? WHERE session_id = ? AND fencing_token = ?")
6127
+ .run(holderId, token, leaseUntil, targetJson, authority.id, authority.version, sessionId, row.fencing_token);
6128
+ return { kind: 'claimed', lease: Object.freeze({ sessionId, holderId, fencingToken: token, leaseUntil }) };
6129
+ });
6130
+ }
6131
+ markSessionLeaseDispatched(lease) {
6132
+ this.assertOpen();
6133
+ return this.transaction(() => {
6134
+ const row = this.sessionLeaseCurrent(lease, true);
6135
+ if (row === undefined || row.state !== 'prepared')
6136
+ return false;
6137
+ return Number(this.database.prepare("UPDATE delivery_session_leases SET state = 'dispatched' WHERE session_id = ? AND holder_id = ? AND fencing_token = ? AND state = 'prepared'")
6138
+ .run(lease.sessionId, lease.holderId, lease.fencingToken).changes) === 1;
6139
+ });
6140
+ }
6141
+ hasSessionLease(lease) {
6142
+ this.assertOpen();
6143
+ return this.transaction(() => {
6144
+ const row = this.sessionLeaseCurrent(lease, true);
6145
+ return row !== undefined && (row.state === 'prepared' || row.state === 'dispatched');
6146
+ });
6147
+ }
6148
+ renewSessionLease(lease, leaseMs) {
6149
+ this.assertOpen();
6150
+ if (!Number.isSafeInteger(leaseMs) || leaseMs < 1 || leaseMs > 300_000)
6151
+ return false;
6152
+ return this.transaction(() => {
6153
+ const row = this.sessionLeaseCurrent(lease, true);
6154
+ if (row === undefined || (row.state !== 'prepared' && row.state !== 'dispatched'))
6155
+ return false;
6156
+ const until = this.now() + leaseMs;
6157
+ if (!Number.isSafeInteger(until))
6158
+ return false;
6159
+ return Number(this.database.prepare("UPDATE delivery_session_leases SET lease_until = ? WHERE session_id = ? AND holder_id = ? AND fencing_token = ? AND state IN ('prepared', 'dispatched')")
6160
+ .run(until, lease.sessionId, lease.holderId, lease.fencingToken).changes) === 1;
6161
+ });
6162
+ }
6163
+ finishSessionLease(lease, result) {
6164
+ this.assertOpen();
6165
+ if (!lease || typeof result?.quiescent !== 'boolean' || !this.sessionLeaseShape(lease))
6166
+ return false;
6167
+ return Number(this.database.prepare("UPDATE delivery_session_leases SET state = ? WHERE session_id = ? AND holder_id = ? AND fencing_token = ? AND state IN ('prepared', 'dispatched', 'unknown')")
6168
+ .run(result.quiescent ? 'released' : 'unknown', lease.sessionId, lease.holderId, lease.fencingToken).changes) === 1;
6169
+ }
6170
+ sessionLeaseShape(lease) {
6171
+ return lease !== null && typeof lease === 'object'
6172
+ && typeof lease.sessionId === 'string' && typeof lease.holderId === 'string'
6173
+ && Number.isSafeInteger(lease.fencingToken) && lease.fencingToken >= 1
6174
+ && Number.isSafeInteger(lease.leaseUntil) && lease.leaseUntil >= 0;
6175
+ }
6176
+ /** Re-reads target authority; no runtime-supplied binding is trusted after claim. */
6177
+ sessionLeaseAuthority(target) {
6178
+ try {
6179
+ if (target?.kind !== 'bound' && target?.kind !== 'construction')
6180
+ return undefined;
6181
+ if (target.kind === 'bound') {
6182
+ const current = this.getBinding(target.binding.id);
6183
+ if (current === undefined || !sameLeaseBinding(current, target.binding))
6184
+ return undefined;
6185
+ const principal = this.getPrincipal(current.principal);
6186
+ return principal?.status === 'active' ? principal : undefined;
6187
+ }
6188
+ const conversation = canonicalConversation(target.conversation);
6189
+ const principalKey = canonicalPrincipal(target.principal);
6190
+ const sessionId = validateBindingText(target.sessionId, 'sessionId', 512);
6191
+ const workspace = validateBindingText(target.workspace, 'workspace', 4_096);
6192
+ const preset = validateBindingText(target.agentPreset, 'agentPreset', 128);
6193
+ if (!isAbsolute(workspace) || !Number.isSafeInteger(target.generation) || target.generation < 1)
6194
+ return undefined;
6195
+ const principal = this.getPrincipal(principalKey);
6196
+ if (principal?.status !== 'active')
6197
+ return undefined;
6198
+ const existingSession = this.database.prepare('SELECT id FROM conversation_bindings WHERE session_id = ?').get(sessionId);
6199
+ if (existingSession !== undefined)
6200
+ return undefined;
6201
+ const existing = this.getActiveBinding(conversation);
6202
+ if (target.previous !== undefined) {
6203
+ const previous = this.getBinding(target.previous.id);
6204
+ if (previous === undefined || !sameLeaseBinding(previous, target.previous)
6205
+ || previous.generation + 1 !== target.generation
6206
+ || JSON.stringify(canonicalConversation(previous.conversation)) !== JSON.stringify(conversation)
6207
+ || JSON.stringify(canonicalPrincipal(previous.principal)) !== JSON.stringify(principalKey)
6208
+ || previous.workspace !== workspace || previous.agentPreset !== preset
6209
+ || existing === undefined || !sameLeaseBinding(existing, target.previous))
6210
+ return undefined;
6211
+ }
6212
+ else if (existing !== undefined || this.nextBindingGenerationByHash(conversationHash(conversation)) !== target.generation)
6213
+ return undefined;
6214
+ return principal;
6215
+ }
6216
+ catch {
6217
+ return undefined;
6218
+ }
6219
+ }
6220
+ sessionLeaseCurrent(lease, requireLiveAuthority) {
6221
+ if (!this.sessionLeaseShape(lease))
6222
+ return undefined;
6223
+ const row = this.database.prepare('SELECT session_id, holder_id, fencing_token, lease_until, state, target_json, principal_record_id, principal_version FROM delivery_session_leases WHERE session_id = ? AND holder_id = ? AND fencing_token = ?')
6224
+ .get(lease.sessionId, lease.holderId, lease.fencingToken);
6225
+ if (row === undefined || row.lease_until <= this.now())
6226
+ return undefined;
6227
+ if (!requireLiveAuthority)
6228
+ return row;
6229
+ let target;
6230
+ try {
6231
+ target = JSON.parse(row.target_json);
6232
+ }
6233
+ catch {
6234
+ return undefined;
6235
+ }
6236
+ const principal = this.sessionLeaseAuthority(target);
6237
+ return principal !== undefined && principal.id === row.principal_record_id && principal.version === row.principal_version ? row : undefined;
6238
+ }
5511
6239
  close() {
5512
6240
  if (this.closed)
5513
6241
  return;