@dsh-enhanced/assistant-delivery 0.1.47 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/store.js CHANGED
@@ -4,10 +4,26 @@ import { acceptanceCanonicalJson } from '@dsh-enhanced/task-acceptance-contract'
4
4
  import { ASSISTANT_GROWTH_CONTRACT_VERSION, WORKFLOW_TRACE_SOURCE_ID, growthObjectDigest, validateResolvedWorkflowAutomationTemplate, validateWorkflowAutomationTemplate, validateWorkflowAutomationTemplateContent, validateWorkflowTraceRevision, workflowAutomationTemplateContentDigest, workflowArgumentShapeDigest, workflowScopeKey, workflowTraceRevisionDigest, WORKFLOW_OWNER_ANCHORED_PLACEHOLDER_SCHEDULE, } from '@dsh-enhanced/assistant-growth-contract';
5
5
  import { bindingMatchesOwnerRoute, canonicalBackgroundSourceId, canonicalConversation, canonicalLocalOperatorId, canonicalOwnerRouteAuthority, canonicalPrincipal, canonicalTarget, externalPrincipalId, ownerRouteAuthorityHash, ownerRouteBindingSnapshot, } from './canonical.js';
6
6
  import { feedbackDispatchRecoveryCode, isFeedbackDeliveryCommand, isFeedbackDispatchRecoveryCode, isExactDeliveryCommand, isLearningDeliveryCommand, isLearningDispatchRecoveryCode, isLearningRetryableFailureCode, isPermissionDeliveryCommand, isWorkflowDeliveryCommand, isWorkflowDispatchRecoveryCode, learningDispatchRecoveryCode, parseDeliveryCommand, permissionDispatchRecoveryCode, permissionDispatchRecoveryFromFailureCode, workflowDispatchRecoveryCode, } from './session-commands.js';
7
- import { parseFeedbackCommand } from './feedback-command.js';
7
+ import { parseFeedbackCommand, parseNaturalObjectiveFeedback } from './feedback-command.js';
8
8
  import { validateOwnerGoalOutcomeFeedbackLocator, validateOwnerGoalOutcomeFeedbackProof, } from './goal-wake-types.js';
9
9
  import { openDeliveryDatabase } from './sqlite.js';
10
10
  import { deriveDeterministicallyDeidentifiedWorkflowTemplate, getDeterministicallyDeidentifiedWorkflowTemplate, } from './workflow-auto-producer.js';
11
+ function foregroundSourceByteLimit(value) {
12
+ if (value === undefined)
13
+ return 16_384;
14
+ if (!Number.isSafeInteger(value) || value < 1 || value > 16_384) {
15
+ throw new DeliveryStoreError('invalid-binding', 'foreground source byte limit must be between 1 and 16384');
16
+ }
17
+ return value;
18
+ }
19
+ function foregroundSourceText(text, limit) {
20
+ const bytes = Buffer.from(text, 'utf8');
21
+ let end = Math.min(bytes.length, limit);
22
+ while (end > 0 && end < bytes.length && (bytes[end] & 0xc0) === 0x80)
23
+ end--;
24
+ return Object.freeze({ text: bytes.subarray(0, end).toString('utf8'), fullTextDigest: createHash('sha256').update(bytes).digest('hex'),
25
+ returnedBytes: end, fullBytes: bytes.length, truncated: end < bytes.length });
26
+ }
11
27
  export class DeliveryStoreError extends Error {
12
28
  code;
13
29
  constructor(code, message) {
@@ -1355,6 +1371,7 @@ export class DeliveryStore {
1355
1371
  codeGenerator;
1356
1372
  maxTextBytes;
1357
1373
  closed = false;
1374
+ writerTransactionActive = false;
1358
1375
  constructor(options) {
1359
1376
  this.database = openDeliveryDatabase(options.path);
1360
1377
  this.now = options.now ?? Date.now;
@@ -3150,6 +3167,32 @@ export class DeliveryStore {
3150
3167
  completedAt: row.completed_at, executionRef: row.execution_ref, modelSelectionState: row.model_selection_state,
3151
3168
  ...(row.model_selection_state === 'frozen' && row.model_provider && row.model_id ? { modelSelection: Object.freeze({ provider: row.model_provider, model: row.model_id, ...(row.model_reasoning_effort === null ? {} : { reasoningEffort: row.model_reasoning_effort }) }) } : {}) });
3152
3169
  }
3170
+ /** One SQL snapshot binds a pending execution to its still claimed Inbox and current owner. */
3171
+ inspectPendingForegroundToolTask(input) {
3172
+ this.assertOpen();
3173
+ const row = this.database.prepare(`SELECT execution.dispatched_at AS dispatched_at
3174
+ FROM delivery_foreground_executions AS execution
3175
+ JOIN inbox_messages AS inbox ON inbox.id = execution.inbox_id
3176
+ JOIN conversation_bindings AS binding ON binding.id = execution.binding_id
3177
+ JOIN delivery_principals AS principal ON principal.id = binding.principal_id
3178
+ WHERE execution.inbox_id = ? AND execution.status = 'pending' AND execution.completed_at IS NULL
3179
+ AND inbox.status = 'claimed' AND inbox.binding_id = execution.binding_id
3180
+ AND binding.status = 'active' AND binding.session_id = ?
3181
+ AND binding.id = ? AND binding.version = ? AND binding.generation = ?
3182
+ AND binding.workspace = ? AND binding.agent_preset = ?
3183
+ AND principal.status = 'active' AND principal.role = 'owner'
3184
+ AND principal.id = ? AND principal.version = ?
3185
+ AND execution.workspace = binding.workspace AND execution.preset = binding.agent_preset
3186
+ AND execution.principal_record_id = principal.id AND execution.principal_version = principal.version
3187
+ AND execution.binding_version = binding.version AND execution.binding_generation = binding.generation
3188
+ LIMIT 1`).get(input.inboxId, input.sessionId, input.binding.id, input.binding.version, input.binding.generation, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.owner.principalVersion);
3189
+ if (row === undefined)
3190
+ return undefined;
3191
+ return Object.freeze({ protocol: 'assistant-delivery/foreground-task/v1',
3192
+ inboxId: input.inboxId, sessionId: input.sessionId,
3193
+ scope: Object.freeze({ ...input.scope }), owner: Object.freeze({ ...input.owner }),
3194
+ binding: Object.freeze({ ...input.binding }), dispatchedAt: row.dispatched_at });
3195
+ }
3153
3196
  /** Immutable pre-prompt receipt for an authenticated owner foreground turn. */
3154
3197
  bindForegroundTaskExecution(input) {
3155
3198
  this.assertOpen();
@@ -3244,6 +3287,216 @@ export class DeliveryStore {
3244
3287
  throw new DeliveryStoreError('conflict', 'foreground execution model selection state is invalid');
3245
3288
  });
3246
3289
  }
3290
+ foregroundSourceFence(input, authorityInput) {
3291
+ const authority = canonicalOwnerRouteAuthority(authorityInput);
3292
+ const binding = this.getActiveBinding(authority.conversation);
3293
+ const owner = binding === undefined ? undefined : this.getPrincipal(binding.principal);
3294
+ const authorityHash = ownerRouteAuthorityHash(authority);
3295
+ if (input.authorityId !== authority.id || input.principalId !== externalPrincipalId(authority.principal)
3296
+ || input.workspace !== authority.workspace || input.agentPreset !== authority.agentPreset
3297
+ || input.expectedOwner?.authorityHash !== authorityHash
3298
+ || input.expectedOwner.principalRecordId !== owner?.id || input.expectedOwner.principalVersion !== owner?.version
3299
+ || binding?.status !== 'active' || !bindingMatchesOwnerRoute(binding, authority)
3300
+ || owner?.role !== 'owner' || owner.status !== 'active') {
3301
+ throw new DeliveryStoreError('unauthorized-principal', 'foreground source owner authority changed');
3302
+ }
3303
+ // The current binding generation intentionally does not enter this key: /new
3304
+ // retires a Session, not the stable owner's completion cursor.
3305
+ const scopeKey = digest(acceptanceCanonicalJson({ authorityId: authority.id, authorityHash,
3306
+ principalId: input.principalId, principalRecordId: owner.id, principalVersion: owner.version,
3307
+ workspace: authority.workspace, preset: authority.agentPreset }));
3308
+ return { authority, authorityHash, scopeKey };
3309
+ }
3310
+ foregroundSource(row, input, fence) {
3311
+ const inbox = this.getInbox(row.inbox_id);
3312
+ const binding = this.getBinding(row.binding_id);
3313
+ if (!inbox || !binding || inbox.bindingId !== row.binding_id
3314
+ || row.workspace !== input.workspace || row.preset !== input.agentPreset
3315
+ || row.principal_record_id !== input.expectedOwner.principalRecordId
3316
+ || row.principal_version !== input.expectedOwner.principalVersion
3317
+ || !bindingMatchesOwnerRoute(binding, fence.authority)
3318
+ || binding.generation !== row.binding_generation
3319
+ || !(binding.status === 'active' && binding.version === row.binding_version
3320
+ || binding.status === 'revoked' && binding.version === row.binding_version + 1)
3321
+ || inbox.admissionCursor.epoch !== this.databaseInstanceId
3322
+ || row.completed_at === null || row.execution_ref !== inbox.id
3323
+ || !Number.isSafeInteger(row.completion_sequence) || row.completion_sequence < 1)
3324
+ return undefined;
3325
+ try {
3326
+ const envelope = canonicalEnvelope(inbox.envelope, this.maxTextBytes);
3327
+ if (digest(JSON.stringify(envelope)) !== inbox.envelopeHash
3328
+ || envelope.kind !== 'text' || parseDeliveryCommand({ ...envelope, kind: 'command' }) !== undefined
3329
+ || JSON.stringify(envelope.principal) !== JSON.stringify(binding.principal)
3330
+ || JSON.stringify(envelope.conversation) !== JSON.stringify(binding.conversation))
3331
+ return undefined;
3332
+ }
3333
+ catch {
3334
+ return undefined;
3335
+ }
3336
+ const execution = Object.freeze({ dispatchedAt: row.dispatched_at,
3337
+ status: row.status, quiescent: row.quiescent === 1, completedAt: row.completed_at,
3338
+ executionRef: row.execution_ref, modelSelectionState: row.model_selection_state,
3339
+ ...(row.model_selection_state === 'frozen' && row.model_provider && row.model_id ? {
3340
+ modelSelection: Object.freeze({ provider: row.model_provider, model: row.model_id,
3341
+ ...(row.model_reasoning_effort === null ? {} : { reasoningEffort: row.model_reasoning_effort }) }),
3342
+ } : {}) });
3343
+ const content = { protocol: 'assistant-delivery/owner-foreground-source/v1', inboxId: inbox.id,
3344
+ completionSequence: row.completion_sequence, admissionCursor: Object.freeze({ ...inbox.admissionCursor }),
3345
+ authorityId: fence.authority.id, authorityHash: fence.authorityHash, principalId: input.principalId,
3346
+ owner: Object.freeze({ principalRecordId: row.principal_record_id, principalVersion: row.principal_version }),
3347
+ scope: Object.freeze({ workspace: row.workspace, preset: row.preset }),
3348
+ binding: Object.freeze({ id: row.binding_id, version: row.binding_version,
3349
+ generation: row.binding_generation, sessionId: binding.sessionId }), execution };
3350
+ return Object.freeze({ ...content, sourceDigest: digest(acceptanceCanonicalJson({ ...content, envelopeHash: inbox.envelopeHash })) });
3351
+ }
3352
+ /** Host-only durable completion feed. Mutable delivery state never hides a completion. */
3353
+ listOwnerForegroundTaskSources(input, authority) {
3354
+ this.assertOpen();
3355
+ const limit = input.limit ?? 50;
3356
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
3357
+ throw new DeliveryStoreError('invalid-binding', 'foreground source page limit must be between 1 and 100');
3358
+ }
3359
+ return this.transaction(() => {
3360
+ const fence = this.foregroundSourceFence(input, authority);
3361
+ const watermark = this.database.prepare('SELECT current_sequence FROM delivery_foreground_completion_clock WHERE singleton = 1')
3362
+ .get().current_sequence;
3363
+ const after = input.after;
3364
+ if (after !== undefined && (after.protocol !== 'assistant-delivery/owner-foreground-source-cursor/v1'
3365
+ || after.epoch !== this.databaseInstanceId || after.scopeKey !== fence.scopeKey
3366
+ || !Number.isSafeInteger(after.sequence) || after.sequence < 0 || after.sequence > watermark)) {
3367
+ throw new DeliveryStoreError('invalid-binding', 'foreground source cursor is not bound to this owner and watermark');
3368
+ }
3369
+ const rows = this.database.prepare(`SELECT * FROM delivery_foreground_executions
3370
+ WHERE principal_record_id = ? AND principal_version = ? AND workspace = ? AND preset = ?
3371
+ AND status != 'pending' AND completion_sequence > ? AND completion_sequence <= ?
3372
+ ORDER BY completion_sequence LIMIT ?`).all(input.expectedOwner.principalRecordId, input.expectedOwner.principalVersion, input.workspace, input.agentPreset, after?.sequence ?? 0, watermark, limit + 1);
3373
+ const hasMore = rows.length > limit;
3374
+ const scanned = rows.slice(0, limit);
3375
+ const items = scanned.flatMap(row => {
3376
+ const source = this.foregroundSource(row, input, fence);
3377
+ return source === undefined ? [] : [source];
3378
+ });
3379
+ const nextCursor = Object.freeze({ protocol: 'assistant-delivery/owner-foreground-source-cursor/v1',
3380
+ epoch: this.databaseInstanceId, scopeKey: fence.scopeKey,
3381
+ sequence: hasMore ? scanned[scanned.length - 1].completion_sequence : watermark });
3382
+ return Object.freeze({ items: Object.freeze(items), nextCursor, watermark, hasMore });
3383
+ });
3384
+ }
3385
+ /** Host-only exact inbox metadata under the same owner writer fence as the feed. */
3386
+ inspectOwnerForegroundTaskSource(input, authority) {
3387
+ this.assertOpen();
3388
+ if (typeof input.inboxId !== 'string' || input.inboxId === '') {
3389
+ throw new DeliveryStoreError('invalid-binding', 'foreground source inbox ID is invalid');
3390
+ }
3391
+ const inspect = () => {
3392
+ const fence = this.foregroundSourceFence(input, authority);
3393
+ const row = this.database.prepare(`SELECT * FROM delivery_foreground_executions
3394
+ WHERE inbox_id = ? AND status != 'pending'`).get(input.inboxId);
3395
+ return row === undefined ? undefined : this.foregroundSource(row, input, fence);
3396
+ };
3397
+ // A route writer fence may inspect the same source while holding this
3398
+ // connection's transaction. Reuse that atomic snapshot and its write lock.
3399
+ return this.writerTransactionActive ? inspect() : this.transaction(inspect);
3400
+ }
3401
+ /** Content comes from the canonical Inbox/Outbox, with no alternate text ledger. */
3402
+ readOwnerForegroundTaskSource(input, authority) {
3403
+ this.assertOpen();
3404
+ const inputLimit = foregroundSourceByteLimit(input.maxInputBytes);
3405
+ const replyLimit = foregroundSourceByteLimit(input.maxReplyBytes);
3406
+ const read = () => {
3407
+ const fence = this.foregroundSourceFence(input, authority);
3408
+ return this.foregroundSourceContent(input, fence, inputLimit, replyLimit);
3409
+ };
3410
+ // A source fence already holds this connection's writer transaction. Reuse
3411
+ // its owner check and snapshot without starting a nested BEGIN.
3412
+ return this.writerTransactionActive ? read() : this.transaction(read);
3413
+ }
3414
+ /** Lock order: Delivery, then downstream Evaluation/Memory writer fences. */
3415
+ withOwnerForegroundTaskSourceFence(input, authority, callback) {
3416
+ if (typeof callback !== 'function')
3417
+ throw new DeliveryStoreError('invalid-binding', 'foreground source callback is invalid');
3418
+ return this.withForegroundSourceRead(input, authority, source => {
3419
+ if (source === undefined)
3420
+ throw new DeliveryStoreError('conflict', 'foreground source is unavailable or changed');
3421
+ const result = callback(source);
3422
+ this.assertForegroundSourceSyncResult(result);
3423
+ return result;
3424
+ });
3425
+ }
3426
+ /** One owner check and one Delivery writer transaction for an ordered set of exact sources. */
3427
+ withOwnerForegroundTaskSourcesFence(input, authority, callback) {
3428
+ this.assertOpen();
3429
+ if (typeof callback !== 'function')
3430
+ throw new DeliveryStoreError('invalid-binding', 'foreground source callback is invalid');
3431
+ if (!Array.isArray(input.sources) || input.sources.length > 1_000) {
3432
+ throw new DeliveryStoreError('invalid-binding', 'foreground source batch must contain at most 1000 sources');
3433
+ }
3434
+ const inboxIds = new Set();
3435
+ for (const source of input.sources) {
3436
+ if (source === null || typeof source !== 'object' || typeof source.inboxId !== 'string'
3437
+ || source.inboxId === '' || typeof source.expectedSourceDigest !== 'string'
3438
+ || inboxIds.has(source.inboxId)) {
3439
+ throw new DeliveryStoreError('invalid-binding', 'foreground source batch requires unique inbox IDs and digests');
3440
+ }
3441
+ inboxIds.add(source.inboxId);
3442
+ }
3443
+ return this.transaction(() => {
3444
+ const fence = this.foregroundSourceFence(input, authority);
3445
+ const sources = Object.freeze(input.sources.map(source => this.foregroundSourceContent({ ...input, inboxId: source.inboxId, expectedSourceDigest: source.expectedSourceDigest }, fence, 16_384, 16_384)));
3446
+ const result = callback(sources);
3447
+ this.assertForegroundSourceSyncResult(result);
3448
+ return result;
3449
+ });
3450
+ }
3451
+ assertForegroundSourceSyncResult(result) {
3452
+ if (result !== null && (typeof result === 'object' || typeof result === 'function') && 'then' in result) {
3453
+ // The native method consumes a real rejected Promise without invoking an
3454
+ // arbitrary thenable's getter or callback. The transaction then rolls back.
3455
+ try {
3456
+ Promise.prototype.then.call(result, undefined, () => undefined);
3457
+ }
3458
+ catch { /* Not a native Promise. */ }
3459
+ throw new DeliveryStoreError('invalid-binding', 'foreground source callback must be synchronous');
3460
+ }
3461
+ }
3462
+ withForegroundSourceRead(input, authority, callback) {
3463
+ this.assertOpen();
3464
+ const inputLimit = foregroundSourceByteLimit(input.maxInputBytes);
3465
+ const replyLimit = foregroundSourceByteLimit(input.maxReplyBytes);
3466
+ return this.transaction(() => {
3467
+ const fence = this.foregroundSourceFence(input, authority);
3468
+ return callback(this.foregroundSourceContent(input, fence, inputLimit, replyLimit));
3469
+ });
3470
+ }
3471
+ foregroundSourceContent(input, fence, inputLimit, replyLimit) {
3472
+ const row = this.database.prepare(`SELECT * FROM delivery_foreground_executions
3473
+ WHERE inbox_id = ? AND status != 'pending'`).get(input.inboxId);
3474
+ const source = row === undefined ? undefined : this.foregroundSource(row, input, fence);
3475
+ if (!source || source.sourceDigest !== input.expectedSourceDigest || source.execution.status !== 'succeeded'
3476
+ || !source.execution.quiescent || source.execution.modelSelectionState !== 'frozen'
3477
+ || source.execution.modelSelection === undefined)
3478
+ return undefined;
3479
+ const inbox = this.getInbox(source.inboxId);
3480
+ const binding = this.getBinding(source.binding.id);
3481
+ const outbox = this.getOutboxByIdempotencyKey(`inbound:${inbox.id}:reply`);
3482
+ if (!outbox || outbox.intentHash !== digest(JSON.stringify(outbox.intent)))
3483
+ return undefined;
3484
+ const intent = outbox.intent;
3485
+ if (intent.bindingId !== binding.id || intent.replyToEventId !== inbox.envelope.eventId
3486
+ || intent.idempotencyKey !== `inbound:${inbox.id}:reply`
3487
+ || JSON.stringify(intent.target.principal) !== JSON.stringify(binding.principal)
3488
+ || JSON.stringify(intent.target.conversation) !== JSON.stringify(binding.conversation)
3489
+ || ![undefined, 'plain', 'markdown'].includes(intent.format)
3490
+ || intent.approval !== undefined || intent.modelPicker !== undefined || intent.permissionPicker !== undefined
3491
+ || Object.keys(intent.metadata ?? {}).length !== 0)
3492
+ return undefined;
3493
+ const inputText = foregroundSourceText(inbox.envelope.text, inputLimit);
3494
+ const reply = Object.freeze({ ...foregroundSourceText(intent.text, replyLimit), outboxId: outbox.id, intentDigest: outbox.intentHash });
3495
+ const contentDigest = digest(acceptanceCanonicalJson({ protocol: 'assistant-delivery/owner-foreground-source-content/v1',
3496
+ sourceDigest: source.sourceDigest, inputDigest: inputText.fullTextDigest,
3497
+ replyDigest: reply.fullTextDigest, outboxId: reply.outboxId, intentDigest: reply.intentDigest }));
3498
+ return Object.freeze({ source, sourceDigest: source.sourceDigest, contentDigest, input: inputText, reply });
3499
+ }
3247
3500
  inspectForegroundExecutionForOwner(input) {
3248
3501
  const row = this.database.prepare(`SELECT execution.dispatched_at, execution.status, execution.quiescent, execution.completed_at, execution.execution_ref,
3249
3502
  execution.model_selection_state, execution.model_provider, execution.model_id, execution.model_reasoning_effort,
@@ -5095,16 +5348,220 @@ export class DeliveryStore {
5095
5348
  return Object.freeze({ revision, ...(template === undefined ? {} : { template }), replayed: false });
5096
5349
  });
5097
5350
  }
5098
- /**
5099
- * Atomically record an authenticated owner objective judgement about one
5100
- * ordinary completed Agent turn and, only when a closed deterministic
5101
- * deidentification catalog recognizes the source, emit its verified trace.
5102
- *
5103
- * The caller may not supply a prompt, task reference, template, or proof:
5104
- * all of them are rebuilt below from the exact durable Inbox/Outbox fence.
5105
- * A normal durable reply alone is intentionally insufficient; the owner
5106
- * must have replied `/feedback achieved` to that exact provider message.
5107
- */
5351
+ getNaturalObjectiveIntent(inboxId) {
5352
+ this.assertOpen();
5353
+ const row = this.database.prepare('SELECT * FROM delivery_natural_objective_intents WHERE inbox_id = ?')
5354
+ .get(inboxId);
5355
+ if (row === undefined)
5356
+ return undefined;
5357
+ return Object.freeze({
5358
+ inboxId: String(row['inbox_id']), envelopeHash: String(row['envelope_hash']),
5359
+ sourceInboxId: String(row['source_inbox_id']), sourceOutboxId: String(row['source_outbox_id']),
5360
+ bindingId: String(row['binding_id']), bindingVersion: Number(row['binding_version']),
5361
+ bindingGeneration: Number(row['binding_generation']), principalRecordId: String(row['principal_record_id']),
5362
+ principalVersion: Number(row['principal_version']), admissionSequence: Number(row['admission_sequence']),
5363
+ admissionEpoch: String(row['admission_epoch']),
5364
+ occurredAt: Number(row['occurred_at']), selection: row['selection'],
5365
+ status: row['intent_status'],
5366
+ ...(row['command_json'] === null ? {} : { command: JSON.parse(String(row['command_json'])) }),
5367
+ ...(row['canonical_json'] === null ? {} : { canonical: JSON.parse(String(row['canonical_json'])) }),
5368
+ });
5369
+ }
5370
+ listPendingNaturalObjectiveIntents(limit = 100, afterAdmissionSequence = 0) {
5371
+ this.assertOpen();
5372
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
5373
+ throw new DeliveryStoreError('conflict', 'invalid natural feedback limit');
5374
+ if (!Number.isSafeInteger(afterAdmissionSequence) || afterAdmissionSequence < 0)
5375
+ throw new DeliveryStoreError('conflict', 'invalid natural feedback cursor');
5376
+ const ids = this.database.prepare(`SELECT inbox_id FROM delivery_natural_objective_intents
5377
+ WHERE intent_status IN ('awaiting-evaluation', 'frozen') AND admission_sequence > ?
5378
+ ORDER BY admission_sequence LIMIT ?`)
5379
+ .all(afterAdmissionSequence, limit);
5380
+ return ids.map(row => this.getNaturalObjectiveIntent(row.inbox_id));
5381
+ }
5382
+ hasEarlierPendingNaturalObjectiveIntent(sourceOutboxId, admissionSequence) {
5383
+ this.assertOpen();
5384
+ return this.database.prepare(`SELECT 1 FROM delivery_natural_objective_intents
5385
+ WHERE source_outbox_id = ? AND admission_sequence < ?
5386
+ AND intent_status IN ('awaiting-evaluation', 'frozen') LIMIT 1`)
5387
+ .get(sourceOutboxId, admissionSequence) !== undefined;
5388
+ }
5389
+ /** Same source proof required by the local workflow commit, before Evaluation append. */
5390
+ isVerifiedForegroundObjectiveTarget(binding, sourceInboxId, sourceOutboxId) {
5391
+ this.assertOpen();
5392
+ const source = this.getInbox(sourceInboxId);
5393
+ const outbox = this.getOutbox(sourceOutboxId);
5394
+ return source?.status === 'processed' && source.bindingId === binding.id
5395
+ && source.envelope.kind === 'text' && (source.envelope.attachments?.length ?? 0) === 0
5396
+ && outbox !== undefined && outbox.intent.bindingId === binding.id
5397
+ && outbox.intent.idempotencyKey === `inbound:${source.id}:reply`
5398
+ && outbox.intent.replyToEventId === source.eventId
5399
+ && ['accepted', 'delivered', 'read'].includes(outbox.status)
5400
+ && outbox.providerMessageId !== undefined
5401
+ && (outbox.intent.format === 'markdown' || outbox.intent.format === 'plain')
5402
+ && outbox.intent.metadata === undefined && outbox.intent.modelPicker === undefined
5403
+ && outbox.intent.permissionPicker === undefined && outbox.intent.approval === undefined
5404
+ && this.getGoalOutcomeTarget(outbox.id) === undefined
5405
+ && JSON.stringify(source.envelope.conversation) === JSON.stringify(binding.conversation)
5406
+ && JSON.stringify(source.envelope.principal) === JSON.stringify(binding.principal)
5407
+ && JSON.stringify(outbox.intent.target.conversation) === JSON.stringify(binding.conversation)
5408
+ && JSON.stringify(outbox.intent.target.principal) === JSON.stringify(binding.principal);
5409
+ }
5410
+ /** Freeze the immutable owner wording and exact completed result before any Evaluation I/O. */
5411
+ prepareNaturalObjectiveIntent(input) {
5412
+ this.assertOpen();
5413
+ return this.transaction(() => {
5414
+ const binding = this.getBinding(input.binding.id);
5415
+ const owner = binding === undefined ? undefined : this.getPrincipal(binding.principal);
5416
+ const inbox = this.getInbox(input.feedbackInboxId);
5417
+ const source = this.getInbox(input.sourceInboxId);
5418
+ const outbox = this.getOutbox(input.sourceOutboxId);
5419
+ const existing = this.getNaturalObjectiveIntent(input.feedbackInboxId);
5420
+ if (binding?.status !== 'active' || binding.version !== input.binding.version
5421
+ || binding.generation !== input.binding.generation || binding.sessionId !== input.binding.sessionId
5422
+ || binding.conversation.kind !== 'dm' || owner?.status !== 'active' || owner.role !== 'owner'
5423
+ || inbox?.status !== 'claimed' || inbox.bindingId !== binding.id || inbox.envelope.kind !== 'text'
5424
+ || (inbox.envelope.attachments?.length ?? 0) !== 0
5425
+ || parseNaturalObjectiveFeedback(inbox.envelope.text) !== input.selection
5426
+ || !this.isVerifiedForegroundObjectiveTarget(binding, input.sourceInboxId, input.sourceOutboxId)
5427
+ || source === undefined || outbox === undefined
5428
+ || inbox.envelope.metadata?.replyToProviderMessageId !== outbox.providerMessageId
5429
+ || JSON.stringify(inbox.envelope.conversation) !== JSON.stringify(binding.conversation)
5430
+ || JSON.stringify(inbox.envelope.principal) !== JSON.stringify(binding.principal)) {
5431
+ throw new DeliveryStoreError('conflict', 'natural feedback does not target one completed owner turn');
5432
+ }
5433
+ if (existing !== undefined) {
5434
+ if (existing.envelopeHash !== inbox.envelopeHash || existing.sourceInboxId !== source.id
5435
+ || existing.sourceOutboxId !== outbox.id || existing.selection !== input.selection
5436
+ || existing.principalRecordId !== owner.id || existing.principalVersion !== owner.version
5437
+ || existing.bindingVersion !== binding.version || existing.bindingGeneration !== binding.generation
5438
+ || existing.admissionEpoch !== inbox.admissionCursor.epoch
5439
+ || existing.admissionSequence !== inbox.admissionCursor.sequence) {
5440
+ throw new DeliveryStoreError('idempotency-conflict', 'natural feedback identity changed');
5441
+ }
5442
+ return existing;
5443
+ }
5444
+ const now = this.now();
5445
+ this.database.prepare(`INSERT INTO delivery_natural_objective_intents(
5446
+ inbox_id, envelope_hash, source_inbox_id, source_outbox_id, binding_id, binding_version,
5447
+ binding_generation, principal_record_id, principal_version, admission_epoch, admission_sequence,
5448
+ occurred_at, parser_version, intent_status, selection, command_json, canonical_json, created_at, updated_at
5449
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 'awaiting-evaluation', ?, NULL, NULL, ?, ?)`).run(inbox.id, inbox.envelopeHash, source.id, outbox.id, binding.id, binding.version, binding.generation, owner.id, owner.version, inbox.admissionCursor.epoch, inbox.admissionCursor.sequence, inbox.envelope.occurredAt, input.selection, now, now);
5450
+ return this.getNaturalObjectiveIntent(inbox.id);
5451
+ });
5452
+ }
5453
+ /** A CAS command is fixed once; retries may never inspect a newer version and rebase it. */
5454
+ freezeNaturalObjectiveIntent(inboxId, current) {
5455
+ this.assertOpen();
5456
+ return this.transaction(() => {
5457
+ const intent = this.getNaturalObjectiveIntent(inboxId);
5458
+ if (intent === undefined)
5459
+ return undefined;
5460
+ if (intent.status !== 'awaiting-evaluation')
5461
+ return intent;
5462
+ const earlier = this.database.prepare(`SELECT 1 FROM delivery_natural_objective_intents
5463
+ WHERE source_outbox_id = ? AND admission_sequence < ?
5464
+ AND intent_status IN ('awaiting-evaluation', 'frozen') LIMIT 1`)
5465
+ .get(intent.sourceOutboxId, intent.admissionSequence);
5466
+ if (earlier !== undefined)
5467
+ return undefined;
5468
+ const later = this.database.prepare(`SELECT 1 FROM delivery_natural_objective_intents
5469
+ WHERE source_outbox_id = ? AND admission_sequence > ?
5470
+ AND intent_status = 'projected' LIMIT 1`)
5471
+ .get(intent.sourceOutboxId, intent.admissionSequence);
5472
+ if (later !== undefined) {
5473
+ this.database.prepare(`UPDATE delivery_natural_objective_intents SET intent_status = 'conflict', updated_at = ? WHERE inbox_id = ?`)
5474
+ .run(this.now(), inboxId);
5475
+ return this.getNaturalObjectiveIntent(inboxId);
5476
+ }
5477
+ const latest = this.database.prepare(`SELECT inbox.envelope_json, admission.admission_sequence
5478
+ FROM delivery_owner_objective_commands AS command
5479
+ JOIN inbox_messages AS inbox ON inbox.id = command.inbox_id
5480
+ JOIN delivery_inbox_admissions AS admission ON admission.inbox_id = inbox.id
5481
+ WHERE command.source_outbox_id = ? AND json_extract(command.result_json, '$.error') IS NULL
5482
+ ORDER BY command.version DESC, admission.admission_sequence DESC LIMIT 1`)
5483
+ .get(intent.sourceOutboxId);
5484
+ if (latest !== undefined) {
5485
+ const previous = JSON.parse(latest.envelope_json);
5486
+ if (latest.admission_sequence >= intent.admissionSequence
5487
+ || previous.occurredAt >= intent.occurredAt) {
5488
+ this.database.prepare(`UPDATE delivery_natural_objective_intents SET intent_status = 'conflict', updated_at = ? WHERE inbox_id = ?`)
5489
+ .run(this.now(), inboxId);
5490
+ return this.getNaturalObjectiveIntent(inboxId);
5491
+ }
5492
+ }
5493
+ const projected = this.database.prepare(`SELECT MAX(occurred_at) AS occurred_at
5494
+ FROM delivery_natural_objective_intents WHERE source_outbox_id = ?
5495
+ AND admission_sequence < ? AND intent_status = 'projected'`)
5496
+ .get(intent.sourceOutboxId, intent.admissionSequence);
5497
+ if (projected.occurred_at !== null && projected.occurred_at >= intent.occurredAt) {
5498
+ this.database.prepare(`UPDATE delivery_natural_objective_intents SET intent_status = 'conflict', updated_at = ? WHERE inbox_id = ?`)
5499
+ .run(this.now(), inboxId);
5500
+ return this.getNaturalObjectiveIntent(inboxId);
5501
+ }
5502
+ const local = this.verifiedWorkflowObjectiveState(intent.sourceOutboxId, { principalRecordId: intent.principalRecordId, principalVersion: intent.principalVersion });
5503
+ if (local !== undefined && (current === undefined || local.version !== current.version
5504
+ || local.objectiveStatus !== current.objectiveStatus))
5505
+ return undefined;
5506
+ if (current !== undefined && local === undefined)
5507
+ return undefined;
5508
+ if (intent.selection === 'withdraw' && (current === undefined || current.objectiveStatus === 'unknown')) {
5509
+ this.database.prepare(`UPDATE delivery_natural_objective_intents SET intent_status = 'conflict', updated_at = ? WHERE inbox_id = ?`)
5510
+ .run(this.now(), inboxId);
5511
+ return this.getNaturalObjectiveIntent(inboxId);
5512
+ }
5513
+ if (current !== undefined && current.objectiveStatus === intent.selection) {
5514
+ this.database.prepare(`UPDATE delivery_natural_objective_intents SET intent_status = 'projected', updated_at = ? WHERE inbox_id = ?`)
5515
+ .run(this.now(), inboxId);
5516
+ return this.getNaturalObjectiveIntent(inboxId);
5517
+ }
5518
+ const command = current === undefined
5519
+ ? { kind: 'objective', objectiveStatus: intent.selection }
5520
+ : { kind: 'objective-revision', action: intent.selection === 'withdraw' ? 'withdraw' : 'correct',
5521
+ expectedVersion: current.version, previousStatus: current.objectiveStatus,
5522
+ objectiveStatus: intent.selection === 'withdraw' ? 'unknown' : intent.selection };
5523
+ this.database.prepare(`UPDATE delivery_natural_objective_intents
5524
+ SET intent_status = 'frozen', command_json = ?, updated_at = ? WHERE inbox_id = ?`)
5525
+ .run(JSON.stringify(command), this.now(), inboxId);
5526
+ return this.getNaturalObjectiveIntent(inboxId);
5527
+ });
5528
+ }
5529
+ settleNaturalObjectiveIntent(inboxId, status, canonical) {
5530
+ this.assertOpen();
5531
+ this.transaction(() => {
5532
+ const intent = this.getNaturalObjectiveIntent(inboxId);
5533
+ if (intent?.status !== 'frozen' && intent?.status !== status
5534
+ && !(intent?.status === 'awaiting-evaluation' && status === 'conflict')) {
5535
+ throw new DeliveryStoreError('conflict', 'natural intent was not frozen');
5536
+ }
5537
+ if (intent.status === 'projected' && status !== 'projected')
5538
+ throw new DeliveryStoreError('conflict', 'projected natural feedback cannot regress');
5539
+ this.database.prepare(`UPDATE delivery_natural_objective_intents
5540
+ SET intent_status = ?, canonical_json = COALESCE(canonical_json, ?), updated_at = ? WHERE inbox_id = ?`)
5541
+ .run(status, canonical === undefined ? null : JSON.stringify(canonical), this.now(), inboxId);
5542
+ });
5543
+ }
5544
+ findNaturalObjectiveFeedbackByOperation(sourceOutboxId, operationId, lineage) {
5545
+ this.assertOpen();
5546
+ const row = this.database.prepare(`SELECT intent.inbox_id, intent.envelope_hash, intent.principal_record_id,
5547
+ intent.principal_version, inbox.envelope_json, inbox.envelope_hash AS current_hash
5548
+ FROM delivery_natural_objective_intents AS intent JOIN inbox_messages AS inbox ON inbox.id = intent.inbox_id
5549
+ WHERE intent.source_outbox_id = ? AND intent.principal_record_id = ? AND intent.principal_version = ?
5550
+ AND intent.command_json IS NOT NULL
5551
+ AND intent.intent_status IN ('frozen', 'projected')`).all(sourceOutboxId, lineage.principalRecordId, lineage.principalVersion);
5552
+ for (const candidate of row) {
5553
+ if (candidate.envelope_hash !== candidate.current_hash)
5554
+ continue;
5555
+ const derived = `owner-feedback:${createHash('sha256').update(JSON.stringify([candidate.inbox_id, candidate.envelope_hash])).digest('hex')}`;
5556
+ if (derived !== operationId)
5557
+ continue;
5558
+ const envelope = JSON.parse(candidate.envelope_json);
5559
+ if (envelope.kind !== 'text' || parseNaturalObjectiveFeedback(envelope.text) === undefined)
5560
+ continue;
5561
+ return Object.freeze({ inboxId: candidate.inbox_id, text: envelope.text.slice(0, 4096), truncated: envelope.text.length > 4096 });
5562
+ }
5563
+ return undefined;
5564
+ }
5108
5565
  verifiedWorkflowObjectiveState(sourceOutboxId, lineage) {
5109
5566
  const row = this.database.prepare(`SELECT version, objective_status, principal_record_id, principal_version FROM delivery_owner_objective_commands
5110
5567
  WHERE source_outbox_id = ? AND json_extract(result_json, '$.error') IS NULL ORDER BY version DESC LIMIT 1`).get(sourceOutboxId);
@@ -5121,6 +5578,7 @@ export class DeliveryStore {
5121
5578
  return undefined;
5122
5579
  return initial === undefined ? undefined : { version: 1, objectiveStatus: initial.objective_status };
5123
5580
  }
5581
+ /** Commit exact owner feedback (typed command or frozen natural intent) and closed-set workflow evidence. */
5124
5582
  commitVerifiedWorkflowTraceFeedback(input) {
5125
5583
  this.assertOpen();
5126
5584
  if (typeof input !== 'object' || input === null
@@ -5147,13 +5605,28 @@ export class DeliveryStore {
5147
5605
  const feedbackInbox = this.getInbox(feedbackInboxId);
5148
5606
  const sourceInbox = this.getInbox(sourceInboxId);
5149
5607
  const sourceOutbox = this.getOutbox(sourceOutboxId);
5608
+ const naturalIntent = this.getNaturalObjectiveIntent(feedbackInboxId);
5150
5609
  const feedbackCommand = feedbackInbox === undefined || feedbackInbox.envelope.kind !== 'command'
5151
5610
  ? undefined
5152
5611
  : parseDeliveryCommand(feedbackInbox.envelope);
5153
- const feedback = feedbackCommand?.name !== 'feedback'
5612
+ const typedFeedback = feedbackCommand?.name !== 'feedback'
5154
5613
  ? undefined
5155
5614
  : parseFeedbackCommand(feedbackCommand.rawInput);
5156
- if (feedbackInbox?.status !== 'claimed' || feedbackInbox.bindingId !== binding.id
5615
+ const feedback = naturalIntent?.command ?? typedFeedback;
5616
+ const naturalMatches = naturalIntent === undefined || (feedbackInbox !== undefined
5617
+ && feedbackInbox.envelope.kind === 'text'
5618
+ && naturalIntent.status !== 'awaiting-evaluation' && naturalIntent.status !== 'conflict'
5619
+ && naturalIntent.envelopeHash === feedbackInbox.envelopeHash
5620
+ && naturalIntent.sourceInboxId === sourceInboxId && naturalIntent.sourceOutboxId === sourceOutboxId
5621
+ && naturalIntent.bindingId === binding.id
5622
+ && naturalIntent.bindingVersion === binding.version && naturalIntent.bindingGeneration === binding.generation
5623
+ && naturalIntent.principalRecordId === owner.id && naturalIntent.principalVersion === owner.version
5624
+ && naturalIntent.admissionEpoch === feedbackInbox.admissionCursor.epoch
5625
+ && naturalIntent.admissionSequence === feedbackInbox.admissionCursor.sequence
5626
+ && parseNaturalObjectiveFeedback(feedbackInbox.envelope.text) === naturalIntent.selection);
5627
+ if ((feedbackInbox?.status !== 'claimed' && !(naturalIntent !== undefined
5628
+ && (feedbackInbox?.status === 'processed' || feedbackInbox?.status === 'dead_letter')))
5629
+ || feedbackInbox?.bindingId !== binding.id || !naturalMatches
5157
5630
  || (feedbackInbox.envelope.attachments?.length ?? 0) !== 0
5158
5631
  || (feedback?.kind !== 'objective' && feedback?.kind !== 'objective-revision')
5159
5632
  || feedback.objectiveStatus !== input.objectiveStatus
@@ -6757,6 +7230,7 @@ export class DeliveryStore {
6757
7230
  }
6758
7231
  transaction(operation) {
6759
7232
  this.database.exec('BEGIN IMMEDIATE');
7233
+ this.writerTransactionActive = true;
6760
7234
  try {
6761
7235
  const result = operation();
6762
7236
  this.database.exec('COMMIT');
@@ -6766,6 +7240,9 @@ export class DeliveryStore {
6766
7240
  this.database.exec('ROLLBACK');
6767
7241
  throw error;
6768
7242
  }
7243
+ finally {
7244
+ this.writerTransactionActive = false;
7245
+ }
6769
7246
  }
6770
7247
  advanceModelCommandEpoch(conversation) {
6771
7248
  const hash = conversationHash(conversation);