@dsh-enhanced/assistant-delivery 0.1.32 → 0.1.34

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
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'node:crypto';
2
2
  import { isAbsolute } from 'node:path';
3
3
  import { acceptanceCanonicalJson } from '@dsh-enhanced/task-acceptance-contract';
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
+ 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
7
  import { parseFeedbackCommand } from './feedback-command.js';
@@ -16,6 +16,24 @@ export class DeliveryStoreError extends Error {
16
16
  this.name = 'DeliveryStoreError';
17
17
  }
18
18
  }
19
+ /**
20
+ * The Goals orchestration primitives that are part of goal bookkeeping rather
21
+ * than owner-facing business/executive tools. A successful native Goal run is
22
+ * reducible to the single model-turn fingerprint Automations can run only when
23
+ * every settled call is one of these read/planning/control primitives; any
24
+ * other tool (Read/Glob/Grep/Bash/Edit/executive MCP tools, ...) means the
25
+ * learned automation would need a real tool plane that paused materialization
26
+ * does not provide, so the goal abstains fail-closed.
27
+ */
28
+ const OWNER_ANCHORED_REDUCIBLE_GOAL_TOOLS = Object.freeze(new Set([
29
+ 'goal_strategy',
30
+ 'goal_schedule',
31
+ 'goal_wait_event',
32
+ 'goal_create',
33
+ 'goal_context',
34
+ 'goal_checkpoint',
35
+ 'goal_control',
36
+ ]));
19
37
  function workflowTraceRevision(row) {
20
38
  let parsed;
21
39
  try {
@@ -3062,6 +3080,46 @@ export class DeliveryStore {
3062
3080
  throw new DeliveryStoreError('conflict', 'foreground acceptance binding is absent or its completion changed');
3063
3081
  }
3064
3082
  }
3083
+ recordForegroundTaskModelSelection(input) {
3084
+ this.assertOpen();
3085
+ if (typeof input.contractId !== 'string' || input.contractId.length === 0 || input.contractId.length > 512
3086
+ || typeof input.provider !== 'string' || typeof input.model !== 'string'
3087
+ || (input.reasoningEffort !== undefined && typeof input.reasoningEffort !== 'string')) {
3088
+ throw new DeliveryStoreError('invalid-binding', 'foreground model selection is invalid');
3089
+ }
3090
+ const provider = modelRoutePart(input.provider, 'provider');
3091
+ const model = modelRoutePart(input.model, 'model');
3092
+ const effort = input.reasoningEffort === undefined ? null : modelRoutePart(input.reasoningEffort, 'effort');
3093
+ this.transaction(() => {
3094
+ const current = this.database.prepare(`SELECT status, model_selection_state, model_provider, model_id, model_reasoning_effort
3095
+ FROM delivery_task_acceptance_executions WHERE contract_id = ?`).get(input.contractId);
3096
+ if (current === undefined)
3097
+ throw new DeliveryStoreError('invalid-binding', 'foreground acceptance is absent');
3098
+ if (current.status !== 'pending')
3099
+ throw new DeliveryStoreError('conflict', 'foreground acceptance model selection is no longer writable');
3100
+ if (current.model_selection_state === 'missing') {
3101
+ const changed = this.database.prepare(`UPDATE delivery_task_acceptance_executions
3102
+ SET model_selection_state = 'frozen', model_provider = ?, model_id = ?, model_reasoning_effort = ?
3103
+ WHERE contract_id = ? AND status = 'pending' AND model_selection_state = 'missing'`).run(provider, model, effort, input.contractId);
3104
+ if (changed.changes !== 1)
3105
+ throw new DeliveryStoreError('conflict', 'foreground acceptance model selection changed');
3106
+ return;
3107
+ }
3108
+ if (current.model_selection_state === 'frozen') {
3109
+ if (current.model_provider === provider && current.model_id === model && current.model_reasoning_effort === effort)
3110
+ return;
3111
+ const changed = this.database.prepare(`UPDATE delivery_task_acceptance_executions
3112
+ SET model_selection_state = 'inconsistent', model_provider = NULL, model_id = NULL, model_reasoning_effort = NULL
3113
+ WHERE contract_id = ? AND status = 'pending' AND model_selection_state = 'frozen'`).run(input.contractId);
3114
+ if (changed.changes !== 1)
3115
+ throw new DeliveryStoreError('conflict', 'foreground acceptance model selection changed');
3116
+ return;
3117
+ }
3118
+ if (current.model_selection_state !== 'inconsistent') {
3119
+ throw new DeliveryStoreError('conflict', 'foreground acceptance model selection state is invalid');
3120
+ }
3121
+ });
3122
+ }
3065
3123
  inspectForegroundAcceptedExecution(contract) {
3066
3124
  const row = this.database.prepare(`SELECT * FROM delivery_task_acceptance_executions
3067
3125
  WHERE contract_id = ?`).get(contract.id);
@@ -3072,7 +3130,8 @@ export class DeliveryStore {
3072
3130
  return null;
3073
3131
  return Object.freeze({ contractId: row.contract_id, contractDigest: row.contract_digest,
3074
3132
  dispatchedAt: row.dispatched_at, status: row.status, quiescent: row.quiescent === 1,
3075
- completedAt: row.completed_at, executionRef: row.execution_ref });
3133
+ completedAt: row.completed_at, executionRef: row.execution_ref, modelSelectionState: row.model_selection_state,
3134
+ ...(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 }) }) } : {}) });
3076
3135
  }
3077
3136
  /**
3078
3137
  * Re-read the immutable acceptance fence for one exact owner reply target.
@@ -3080,7 +3139,7 @@ export class DeliveryStore {
3080
3139
  * cannot manufacture a foreground subject by naming an arbitrary Inbox.
3081
3140
  */
3082
3141
  inspectForegroundAcceptedExecutionForOwner(input) {
3083
- const row = this.database.prepare(`SELECT contract_id, contract_digest, dispatched_at, status, quiescent, completed_at, execution_ref
3142
+ const row = this.database.prepare(`SELECT contract_id, contract_digest, dispatched_at, status, quiescent, completed_at, execution_ref, model_selection_state, model_provider, model_id, model_reasoning_effort
3084
3143
  FROM delivery_task_acceptance_executions
3085
3144
  WHERE inbox_id = ? AND workspace = ? AND preset = ? AND principal_record_id = ?
3086
3145
  AND principal_version = ? AND binding_id = ?`).get(input.inboxId, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.owner.principalVersion, input.bindingId);
@@ -3088,7 +3147,120 @@ export class DeliveryStore {
3088
3147
  return null;
3089
3148
  return Object.freeze({ contractId: row.contract_id, contractDigest: row.contract_digest,
3090
3149
  dispatchedAt: row.dispatched_at, status: row.status, quiescent: row.quiescent === 1,
3091
- completedAt: row.completed_at, executionRef: row.execution_ref });
3150
+ completedAt: row.completed_at, executionRef: row.execution_ref, modelSelectionState: row.model_selection_state,
3151
+ ...(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
+ }
3153
+ /** Immutable pre-prompt receipt for an authenticated owner foreground turn. */
3154
+ bindForegroundTaskExecution(input) {
3155
+ this.assertOpen();
3156
+ if (!Number.isSafeInteger(input.dispatchedAt) || input.dispatchedAt < 0
3157
+ || !Number.isSafeInteger(input.owner.principalVersion) || input.owner.principalVersion < 1
3158
+ || !Number.isSafeInteger(input.binding.version) || input.binding.version < 1
3159
+ || !Number.isSafeInteger(input.binding.generation) || input.binding.generation < 1) {
3160
+ throw new DeliveryStoreError('invalid-binding', 'foreground execution binding is invalid');
3161
+ }
3162
+ const text = [input.inboxId, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.binding.id];
3163
+ if (text.some(value => typeof value !== 'string' || value.trim() === '' || Buffer.byteLength(value, 'utf8') > 4_096)) {
3164
+ throw new DeliveryStoreError('invalid-binding', 'foreground execution binding text is invalid');
3165
+ }
3166
+ this.transaction(() => {
3167
+ if (this.database.prepare('SELECT 1 FROM delivery_foreground_executions WHERE inbox_id = ?').get(input.inboxId) !== undefined) {
3168
+ throw new DeliveryStoreError('idempotency-conflict', 'foreground task has already crossed its dispatch boundary');
3169
+ }
3170
+ const admitted = this.database.prepare(`
3171
+ SELECT inbox.id FROM inbox_messages AS inbox
3172
+ JOIN conversation_bindings AS binding ON binding.id = inbox.binding_id
3173
+ JOIN delivery_principals AS principal ON principal.id = binding.principal_id
3174
+ WHERE inbox.id = ? AND inbox.status = 'claimed' AND inbox.binding_id = ?
3175
+ AND binding.status = 'active' AND binding.version = ? AND binding.generation = ?
3176
+ AND principal.status = 'active' AND principal.role = 'owner'
3177
+ AND principal.id = ? AND principal.version = ?
3178
+ AND binding.workspace = ? AND binding.agent_preset = ?
3179
+ `).get(input.inboxId, input.binding.id, input.binding.version, input.binding.generation, input.owner.principalRecordId, input.owner.principalVersion, input.scope.workspace, input.scope.preset);
3180
+ if (admitted === undefined)
3181
+ throw new DeliveryStoreError('invalid-binding', 'foreground execution is not bound to the claimed owner inbox');
3182
+ this.database.prepare(`INSERT INTO delivery_foreground_executions(
3183
+ inbox_id, workspace, preset, principal_record_id, principal_version, binding_id, binding_version,
3184
+ binding_generation, dispatched_at, status, quiescent
3185
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0)`).run(input.inboxId, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.owner.principalVersion, input.binding.id, input.binding.version, input.binding.generation, input.dispatchedAt);
3186
+ });
3187
+ }
3188
+ finishForegroundTaskExecution(input) {
3189
+ this.assertOpen();
3190
+ if (typeof input.quiescent !== 'boolean'
3191
+ || !['succeeded', 'failed', 'timed-out', 'cancelled', 'unknown'].includes(input.status)
3192
+ || !Number.isSafeInteger(input.completedAt) || input.completedAt < 0) {
3193
+ throw new DeliveryStoreError('invalid-binding', 'foreground execution completion is invalid');
3194
+ }
3195
+ const status = input.quiescent && input.status === 'succeeded' ? 'succeeded' : 'unknown';
3196
+ const quiescent = status === 'succeeded' ? 1 : 0;
3197
+ const changed = this.database.prepare(`UPDATE delivery_foreground_executions
3198
+ SET status = ?, quiescent = ?, completed_at = ?, execution_ref = inbox_id
3199
+ WHERE inbox_id = ? AND status = 'pending' AND dispatched_at <= ?`).run(status, quiescent, input.completedAt, input.inboxId, input.completedAt);
3200
+ if (changed.changes !== 1) {
3201
+ const current = this.database.prepare('SELECT status, quiescent, completed_at FROM delivery_foreground_executions WHERE inbox_id = ?')
3202
+ .get(input.inboxId);
3203
+ if (current?.status === status && current.quiescent === quiescent && current.completed_at === input.completedAt)
3204
+ return;
3205
+ throw new DeliveryStoreError('conflict', 'foreground execution is absent or its completion changed');
3206
+ }
3207
+ }
3208
+ recordForegroundExecutionModelSelection(input) {
3209
+ this.assertOpen();
3210
+ if (typeof input.inboxId !== 'string' || input.inboxId.length === 0 || input.inboxId.length > 512
3211
+ || typeof input.provider !== 'string' || typeof input.model !== 'string'
3212
+ || (input.reasoningEffort !== undefined && typeof input.reasoningEffort !== 'string')) {
3213
+ throw new DeliveryStoreError('invalid-binding', 'foreground execution model selection is invalid');
3214
+ }
3215
+ const provider = modelRoutePart(input.provider, 'provider');
3216
+ const model = modelRoutePart(input.model, 'model');
3217
+ const effort = input.reasoningEffort === undefined ? null : modelRoutePart(input.reasoningEffort, 'effort');
3218
+ this.transaction(() => {
3219
+ const current = this.database.prepare(`SELECT status, model_selection_state, model_provider, model_id, model_reasoning_effort
3220
+ FROM delivery_foreground_executions WHERE inbox_id = ?`).get(input.inboxId);
3221
+ if (current === undefined)
3222
+ throw new DeliveryStoreError('invalid-binding', 'foreground execution is absent');
3223
+ if (current.status !== 'pending')
3224
+ throw new DeliveryStoreError('conflict', 'foreground execution model selection is no longer writable');
3225
+ if (current.model_selection_state === 'missing') {
3226
+ const changed = this.database.prepare(`UPDATE delivery_foreground_executions
3227
+ SET model_selection_state = 'frozen', model_provider = ?, model_id = ?, model_reasoning_effort = ?
3228
+ WHERE inbox_id = ? AND status = 'pending' AND model_selection_state = 'missing'`).run(provider, model, effort, input.inboxId);
3229
+ if (changed.changes !== 1)
3230
+ throw new DeliveryStoreError('conflict', 'foreground execution model selection changed');
3231
+ return;
3232
+ }
3233
+ if (current.model_selection_state === 'frozen') {
3234
+ if (current.model_provider === provider && current.model_id === model && current.model_reasoning_effort === effort)
3235
+ return;
3236
+ const changed = this.database.prepare(`UPDATE delivery_foreground_executions
3237
+ SET model_selection_state = 'inconsistent', model_provider = NULL, model_id = NULL, model_reasoning_effort = NULL
3238
+ WHERE inbox_id = ? AND status = 'pending' AND model_selection_state = 'frozen'`).run(input.inboxId);
3239
+ if (changed.changes !== 1)
3240
+ throw new DeliveryStoreError('conflict', 'foreground execution model selection changed');
3241
+ return;
3242
+ }
3243
+ if (current.model_selection_state !== 'inconsistent')
3244
+ throw new DeliveryStoreError('conflict', 'foreground execution model selection state is invalid');
3245
+ });
3246
+ }
3247
+ inspectForegroundExecutionForOwner(input) {
3248
+ const row = this.database.prepare(`SELECT execution.dispatched_at, execution.status, execution.quiescent, execution.completed_at, execution.execution_ref,
3249
+ execution.model_selection_state, execution.model_provider, execution.model_id, execution.model_reasoning_effort,
3250
+ execution.binding_version AS execution_binding_version,
3251
+ binding.status AS current_binding_status, binding.version AS current_binding_version, binding.generation AS current_binding_generation
3252
+ FROM delivery_foreground_executions AS execution
3253
+ JOIN conversation_bindings AS binding ON binding.id = execution.binding_id
3254
+ WHERE execution.inbox_id = ? AND execution.workspace = ? AND execution.preset = ? AND execution.principal_record_id = ? AND execution.principal_version = ?
3255
+ AND execution.binding_id = ? AND execution.binding_generation = ?`).get(input.inboxId, input.scope.workspace, input.scope.preset, input.owner.principalRecordId, input.owner.principalVersion, input.bindingId, input.bindingGeneration);
3256
+ if (row === undefined || row.current_binding_version !== input.bindingVersion || row.current_binding_generation !== input.bindingGeneration
3257
+ || (row.execution_binding_version !== row.current_binding_version
3258
+ && !(row.current_binding_status === 'revoked' && row.execution_binding_version + 1 === row.current_binding_version))
3259
+ || row.completed_at === null || row.execution_ref !== input.inboxId)
3260
+ return null;
3261
+ return Object.freeze({ dispatchedAt: row.dispatched_at, status: row.status, quiescent: row.quiescent === 1,
3262
+ completedAt: row.completed_at, executionRef: row.execution_ref, modelSelectionState: row.model_selection_state,
3263
+ ...(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 }) }) } : {}) });
3092
3264
  }
3093
3265
  renewInboxClaim(input) {
3094
3266
  this.assertOpen();
@@ -3167,8 +3339,9 @@ export class DeliveryStore {
3167
3339
  : existingPermissionRecovery === undefined
3168
3340
  ? undefined
3169
3341
  : permissionDispatchRecoveryCode(existingPermissionRecovery);
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'
3342
+ const foregroundDispatch = this.database.prepare(`SELECT inbox_id FROM delivery_task_acceptance_executions WHERE inbox_id = ?
3343
+ UNION ALL SELECT inbox_id FROM delivery_foreground_executions WHERE inbox_id = ? LIMIT 1`).get(row.id, row.id) !== undefined;
3344
+ const ambiguous = foregroundDispatch || row.failure_code === 'native-admission' || row.failure_code === 'native-dispatch-started'
3172
3345
  || (row.failure_code === 'dispatch-started'
3173
3346
  && permissionRecovery === undefined && !feedbackRecovery && !learningRecovery && !workflowRecovery);
3174
3347
  const exhausted = row.attempt_count >= input.maxAttempts
@@ -5241,6 +5414,220 @@ export class DeliveryStore {
5241
5414
  throw committed;
5242
5415
  return committed;
5243
5416
  }
5417
+ /**
5418
+ * Record a workflow learned from one Delivery-reverified owner-root successful
5419
+ * Goal run. Unlike the two conversation-fenced routes, there is no live
5420
+ * Inbox/Outbox: the caller (the Host-only Delivery service method) must have
5421
+ * independently re-fetched the verified Goals source and the owner-run-trace
5422
+ * proof, so this method never accepts a caller-supplied prompt, template, or
5423
+ * proof digest — all of them are rebuilt below from the verified source.
5424
+ *
5425
+ * The goal must be honestly reducible to the single built-in
5426
+ * `assistant.agent-turn` step that paused Growth materialization can run
5427
+ * (exactly one step, zero executable/business tools). Otherwise it abstains
5428
+ * with zero writes. The raw objective is stored only in the new table's
5429
+ * content_json and never enters the trace. Re-submitting the same verified
5430
+ * run deterministically replays its prior revision.
5431
+ */
5432
+ commitOwnerAnchoredWorkflowTrace(input) {
5433
+ this.assertOpen();
5434
+ const digest64 = /^[a-f0-9]{64}$/u;
5435
+ if (typeof input !== 'object' || input === null
5436
+ || typeof input.ownerRouteId !== 'string' || input.ownerRouteId === ''
5437
+ || typeof input.scope?.workspace !== 'string' || typeof input.scope?.preset !== 'string'
5438
+ || typeof input.goal?.id !== 'string' || typeof input.goal?.nativeGoalId !== 'string'
5439
+ || typeof input.goal?.sessionId !== 'string'
5440
+ || !Number.isSafeInteger(input.goal?.definition?.version) || input.goal.definition.version < 1
5441
+ || typeof input.goal?.definition?.digest !== 'string' || !digest64.test(input.goal.definition.digest)
5442
+ || typeof input.goal?.definition?.objective !== 'string' || input.goal.definition.objective === ''
5443
+ || Buffer.byteLength(input.goal?.definition?.objective ?? '', 'utf8') > 8192
5444
+ || typeof input.runId !== 'string' || input.runId === ''
5445
+ || !Number.isSafeInteger(input.turn) || input.turn < 0
5446
+ || typeof input.acceptance?.contractId !== 'string' || input.acceptance.contractId === ''
5447
+ || typeof input.acceptance?.receiptDigest !== 'string' || !digest64.test(input.acceptance.receiptDigest)
5448
+ || !Number.isSafeInteger(input.acceptance?.verifiedAt) || input.acceptance.verifiedAt < 0
5449
+ || !Number.isSafeInteger(input.acceptance?.validUntil) || input.acceptance.validUntil <= input.acceptance?.verifiedAt
5450
+ || typeof input.ownerRunTraceDigest !== 'string' || !digest64.test(input.ownerRunTraceDigest)
5451
+ || !Array.isArray(input.steps) || input.steps.length === 0) {
5452
+ throw new DeliveryStoreError('conflict', 'owner-anchored workflow tuple is invalid');
5453
+ }
5454
+ // Fail-closed reduction gate, evaluated before any write. The learned
5455
+ // automation is a single no-tool model turn, so every settled call in the
5456
+ // successful run must be Goals bookkeeping, and even bounded failed read
5457
+ // probes disqualify this slice rather than being silently dropped.
5458
+ const reducible = input.steps.every(step => typeof step?.id === 'string' && step.id !== ''
5459
+ && typeof step?.toolName === 'string'
5460
+ && OWNER_ANCHORED_REDUCIBLE_GOAL_TOOLS.has(step.toolName))
5461
+ && (input.failedObservations === undefined || input.failedObservations.length === 0);
5462
+ if (!reducible) {
5463
+ return Object.freeze({ outcome: 'abstained', reason: 'not-reducible', replayed: false });
5464
+ }
5465
+ return this.transaction(() => {
5466
+ const binding = this.getBinding(input.binding.id);
5467
+ const owner = binding === undefined ? undefined : this.getPrincipal(binding.principal);
5468
+ if (binding === undefined || binding.status !== 'active'
5469
+ || binding.version !== input.binding.version || binding.generation !== input.binding.generation
5470
+ || JSON.stringify(binding.conversation) !== JSON.stringify(input.binding.conversation)
5471
+ || JSON.stringify(binding.principal) !== JSON.stringify(input.binding.principal)
5472
+ || binding.workspace !== input.binding.workspace || binding.agentPreset !== input.binding.agentPreset
5473
+ || owner?.status !== 'active' || owner.role !== 'owner') {
5474
+ throw new DeliveryStoreError('unauthorized-principal', 'owner-anchored workflow owner binding changed');
5475
+ }
5476
+ if (input.scope.workspace !== binding.workspace || input.scope.preset !== binding.agentPreset) {
5477
+ throw new DeliveryStoreError('invalid-binding', 'owner-anchored workflow scope does not match the owner binding');
5478
+ }
5479
+ const anchor = Object.freeze({
5480
+ scope: Object.freeze({ workspace: binding.workspace, preset: binding.agentPreset }),
5481
+ bindingId: binding.id,
5482
+ goalId: input.goal.id,
5483
+ definitionVersion: input.goal.definition.version,
5484
+ definitionDigest: input.goal.definition.digest,
5485
+ sessionId: input.goal.sessionId,
5486
+ nativeGoalId: input.goal.nativeGoalId,
5487
+ runId: input.runId,
5488
+ turn: input.turn,
5489
+ });
5490
+ const subjectRef = growthObjectDigest({
5491
+ contract: 'assistant-delivery-owner-anchored-workflow-subject/v1',
5492
+ ...anchor,
5493
+ });
5494
+ const taskRef = growthObjectDigest({
5495
+ contract: 'assistant-delivery-owner-anchored-workflow-task-ref/v1',
5496
+ ...anchor,
5497
+ acceptanceContractId: input.acceptance.contractId,
5498
+ acceptanceReceiptDigest: input.acceptance.receiptDigest,
5499
+ });
5500
+ const existing = this.database.prepare(`
5501
+ SELECT * FROM workflow_owner_anchored_templates WHERE task_ref = ?
5502
+ `).get(taskRef);
5503
+ if (existing !== undefined) {
5504
+ if (existing.goal_id !== input.goal.id || existing.run_id !== input.runId
5505
+ || existing.turn !== input.turn || existing.session_id !== input.goal.sessionId
5506
+ || existing.native_goal_id !== input.goal.nativeGoalId
5507
+ || existing.owner_route_id !== input.ownerRouteId
5508
+ || existing.acceptance_contract_id !== input.acceptance.contractId
5509
+ || existing.acceptance_receipt_digest !== input.acceptance.receiptDigest
5510
+ || existing.owner_run_trace_digest !== input.ownerRunTraceDigest) {
5511
+ throw new DeliveryStoreError('idempotency-conflict', 'owner-anchored task identity was reused with different evidence');
5512
+ }
5513
+ const currentRow = this.database.prepare(`
5514
+ SELECT revision.* FROM workflow_trace_current AS current
5515
+ JOIN workflow_trace_revisions AS revision
5516
+ ON revision.subject_ref = current.subject_ref AND revision.version = current.version
5517
+ WHERE current.subject_ref = ?
5518
+ `).get(subjectRef);
5519
+ if (currentRow === undefined) {
5520
+ throw new DeliveryStoreError('conflict', 'owner-anchored workflow replay is missing its trace');
5521
+ }
5522
+ const revision = workflowTraceRevision(currentRow);
5523
+ const replayTemplate = revision.evidence?.template;
5524
+ if (revision.disposition !== 'upsert' || replayTemplate === undefined
5525
+ || replayTemplate.templateRef !== existing.template_ref
5526
+ || replayTemplate.templateDigest !== existing.template_digest) {
5527
+ throw new DeliveryStoreError('conflict', 'owner-anchored workflow replay trace is inconsistent');
5528
+ }
5529
+ return Object.freeze({ outcome: 'trace-recorded', revision, template: replayTemplate, replayed: true });
5530
+ }
5531
+ const content = validateWorkflowAutomationTemplateContent({
5532
+ scope: { workspace: binding.workspace, preset: binding.agentPreset },
5533
+ ownerBindingId: binding.id,
5534
+ principalId: externalPrincipalId(binding.principal),
5535
+ name: 'Owner-anchored workflow',
5536
+ prompt: input.goal.definition.objective,
5537
+ schedule: WORKFLOW_OWNER_ANCHORED_PLACEHOLDER_SCHEDULE,
5538
+ timeoutMs: 60_000,
5539
+ toolCatalogIds: ['assistant.agent-turn'],
5540
+ deliveryBindingId: binding.id,
5541
+ });
5542
+ const templateDigest = workflowAutomationTemplateContentDigest(content);
5543
+ const reviewReceipt = Object.freeze({
5544
+ contractVersion: 1,
5545
+ kind: 'owner-anchored-template-review',
5546
+ limitation: 'deidentification-unproven',
5547
+ provenance: 'owner-goal-success',
5548
+ scheduleUnconfirmed: true,
5549
+ templateDigest,
5550
+ bindingId: binding.id,
5551
+ bindingVersion: binding.version,
5552
+ bindingGeneration: binding.generation,
5553
+ principalId: owner.id,
5554
+ ownerRouteId: input.ownerRouteId,
5555
+ goalId: input.goal.id,
5556
+ nativeGoalId: input.goal.nativeGoalId,
5557
+ sessionId: input.goal.sessionId,
5558
+ runId: input.runId,
5559
+ turn: input.turn,
5560
+ acceptanceContractId: input.acceptance.contractId,
5561
+ acceptanceReceiptDigest: input.acceptance.receiptDigest,
5562
+ ownerRunTraceDigest: input.ownerRunTraceDigest,
5563
+ taskRef,
5564
+ });
5565
+ const attestationDigest = growthObjectDigest({
5566
+ contract: 'assistant-delivery-workflow-owner-anchored-template-review/v1',
5567
+ receipt: reviewReceipt,
5568
+ });
5569
+ const templateRef = `workflow-template:${growthObjectDigest({
5570
+ contract: 'assistant-delivery-workflow-template-ref/v3',
5571
+ templateDigest,
5572
+ goalId: input.goal.id,
5573
+ runId: input.runId,
5574
+ })}`;
5575
+ const template = validateWorkflowAutomationTemplate({
5576
+ templateRef,
5577
+ templateDigest,
5578
+ privacyAttestation: {
5579
+ kind: 'owner-anchored',
5580
+ limitation: 'deidentification-unproven',
5581
+ provenance: 'owner-goal-success',
5582
+ attestationId: `workflow-owner-anchored:${attestationDigest}`,
5583
+ attestationDigest,
5584
+ },
5585
+ });
5586
+ const payload = Object.freeze({
5587
+ source: this.workflowTraceSourceAttestation(),
5588
+ scope: Object.freeze({ workspace: binding.workspace, preset: binding.agentPreset }),
5589
+ subjectRef,
5590
+ version: 1,
5591
+ disposition: 'upsert',
5592
+ evidence: Object.freeze({
5593
+ occurredAt: input.acceptance.verifiedAt,
5594
+ signal: 'owner-anchored',
5595
+ objectiveStatus: 'achieved',
5596
+ ownerBindingId: binding.id,
5597
+ taskRef,
5598
+ taskEvidenceDigest: input.ownerRunTraceDigest,
5599
+ template,
5600
+ steps: Object.freeze([Object.freeze({
5601
+ catalogId: 'assistant.agent-turn',
5602
+ argumentSchemaDigest: workflowArgumentShapeDigest({ prompt: content.prompt }),
5603
+ })]),
5604
+ }),
5605
+ });
5606
+ const revision = validateWorkflowTraceRevision({
5607
+ ...payload,
5608
+ digest: workflowTraceRevisionDigest(payload),
5609
+ });
5610
+ const now = this.now();
5611
+ this.database.prepare(`
5612
+ INSERT INTO workflow_owner_anchored_templates(
5613
+ template_ref, template_digest, scope_key, workspace, preset, owner_binding_id,
5614
+ binding_version, binding_generation, principal_id, content_json, privacy_kind,
5615
+ privacy_attestation_id, privacy_attestation_digest, review_receipt_json, status,
5616
+ version, task_ref, owner_route_id, goal_id, native_goal_id, session_id, run_id, turn,
5617
+ acceptance_contract_id, acceptance_receipt_digest, owner_run_trace_digest,
5618
+ created_at, updated_at
5619
+ ) VALUES (
5620
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'owner-anchored', ?, ?, ?, 'active', 1,
5621
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
5622
+ )
5623
+ `).run(template.templateRef, template.templateDigest, workflowScopeKey(content.scope), content.scope.workspace, content.scope.preset, binding.id, binding.version, binding.generation, owner.id, JSON.stringify(content), template.privacyAttestation.attestationId, template.privacyAttestation.attestationDigest, JSON.stringify(reviewReceipt), taskRef, input.ownerRouteId, input.goal.id, input.goal.nativeGoalId, input.goal.sessionId, input.runId, input.turn, input.acceptance.contractId, input.acceptance.receiptDigest, input.ownerRunTraceDigest, now, now);
5624
+ const recorded = this.insertWorkflowTraceRevisionInTransaction(revision);
5625
+ if (recorded.replayed) {
5626
+ throw new DeliveryStoreError('version-conflict', 'new owner-anchored workflow unexpectedly replayed a trace version');
5627
+ }
5628
+ return Object.freeze({ outcome: 'trace-recorded', revision, template, replayed: false });
5629
+ });
5630
+ }
5244
5631
  insertVerifiedWorkflowTaskFeedback(input) {
5245
5632
  if ((input.revision === undefined) !== (input.template === undefined)) {
5246
5633
  throw new DeliveryStoreError('conflict', 'verified workflow trace and template must agree');
@@ -5317,7 +5704,7 @@ export class DeliveryStore {
5317
5704
  SELECT * FROM workflow_template_registry WHERE template_ref = ?
5318
5705
  `).get(template.templateRef);
5319
5706
  if (row === undefined)
5320
- return undefined;
5707
+ return this.getOwnerAnchoredWorkflowTemplate(template);
5321
5708
  let contentValue;
5322
5709
  let reviewValue;
5323
5710
  try {
@@ -5438,6 +5825,7 @@ export class DeliveryStore {
5438
5825
  return Object.freeze({
5439
5826
  resolved,
5440
5827
  review: Object.freeze({
5828
+ kind: 'inbox-anchored',
5441
5829
  bindingId: row.owner_binding_id,
5442
5830
  bindingVersion: review['bindingVersion'],
5443
5831
  bindingGeneration: review['bindingGeneration'],
@@ -5450,6 +5838,131 @@ export class DeliveryStore {
5450
5838
  version: row.version,
5451
5839
  });
5452
5840
  }
5841
+ /**
5842
+ * Rebuild a template learned from a Delivery-reverified owner-root Goal run.
5843
+ * Every column and receipt field is re-derived and cross-checked; a mismatch
5844
+ * fails closed rather than resolving attacker- or corruption-influenced data.
5845
+ */
5846
+ getOwnerAnchoredWorkflowTemplate(template) {
5847
+ const row = this.database.prepare(`
5848
+ SELECT * FROM workflow_owner_anchored_templates WHERE template_ref = ?
5849
+ `).get(template.templateRef);
5850
+ if (row === undefined)
5851
+ return undefined;
5852
+ let contentValue;
5853
+ let reviewValue;
5854
+ try {
5855
+ contentValue = JSON.parse(row.content_json);
5856
+ reviewValue = JSON.parse(row.review_receipt_json);
5857
+ }
5858
+ catch {
5859
+ throw new DeliveryStoreError('conflict', 'owner-anchored workflow payload is corrupt');
5860
+ }
5861
+ if (typeof reviewValue !== 'object' || reviewValue === null || Array.isArray(reviewValue)) {
5862
+ throw new DeliveryStoreError('conflict', 'owner-anchored workflow review receipt is corrupt');
5863
+ }
5864
+ const review = reviewValue;
5865
+ const content = validateWorkflowAutomationTemplateContent(contentValue);
5866
+ const exactReviewKeys = [
5867
+ 'contractVersion', 'kind', 'limitation', 'provenance', 'scheduleUnconfirmed', 'templateDigest',
5868
+ 'bindingId', 'bindingVersion', 'bindingGeneration', 'principalId',
5869
+ 'ownerRouteId', 'goalId', 'nativeGoalId', 'sessionId', 'runId', 'turn',
5870
+ 'acceptanceContractId', 'acceptanceReceiptDigest', 'ownerRunTraceDigest', 'taskRef',
5871
+ ].sort();
5872
+ const bindingVersion = review['bindingVersion'];
5873
+ const bindingGeneration = review['bindingGeneration'];
5874
+ const turn = review['turn'];
5875
+ if (Object.keys(review).sort().some((key, index) => key !== exactReviewKeys[index])
5876
+ || Object.keys(review).length !== exactReviewKeys.length
5877
+ || review['contractVersion'] !== 1
5878
+ || review['kind'] !== 'owner-anchored-template-review'
5879
+ || review['limitation'] !== 'deidentification-unproven'
5880
+ || review['provenance'] !== 'owner-goal-success'
5881
+ || review['scheduleUnconfirmed'] !== true
5882
+ || review['templateDigest'] !== row.template_digest
5883
+ || review['bindingId'] !== row.owner_binding_id
5884
+ || review['principalId'] !== row.principal_id
5885
+ || typeof bindingVersion !== 'number' || !Number.isSafeInteger(bindingVersion) || bindingVersion < 1
5886
+ || typeof bindingGeneration !== 'number' || !Number.isSafeInteger(bindingGeneration) || bindingGeneration < 1
5887
+ || bindingVersion !== row.binding_version || bindingGeneration !== row.binding_generation
5888
+ || review['ownerRouteId'] !== row.owner_route_id
5889
+ || review['goalId'] !== row.goal_id
5890
+ || review['nativeGoalId'] !== row.native_goal_id
5891
+ || review['sessionId'] !== row.session_id
5892
+ || review['runId'] !== row.run_id
5893
+ || review['turn'] !== row.turn
5894
+ || typeof turn !== 'number' || !Number.isSafeInteger(turn) || turn < 0
5895
+ || review['acceptanceContractId'] !== row.acceptance_contract_id
5896
+ || review['acceptanceReceiptDigest'] !== row.acceptance_receipt_digest
5897
+ || review['ownerRunTraceDigest'] !== row.owner_run_trace_digest
5898
+ || review['taskRef'] !== row.task_ref
5899
+ || typeof review['goalId'] !== 'string' || typeof review['nativeGoalId'] !== 'string'
5900
+ || typeof review['sessionId'] !== 'string' || typeof review['runId'] !== 'string'
5901
+ || typeof review['ownerRouteId'] !== 'string'
5902
+ || typeof review['acceptanceContractId'] !== 'string'
5903
+ || typeof review['acceptanceReceiptDigest'] !== 'string'
5904
+ || !/^[a-f0-9]{64}$/u.test(review['acceptanceReceiptDigest'])
5905
+ || !/^[a-f0-9]{64}$/u.test(review['ownerRunTraceDigest'])
5906
+ || !/^[a-f0-9]{64}$/u.test(review['taskRef'])) {
5907
+ throw new DeliveryStoreError('conflict', 'owner-anchored workflow review receipt columns do not match');
5908
+ }
5909
+ const attestationDigest = growthObjectDigest({
5910
+ contract: 'assistant-delivery-workflow-owner-anchored-template-review/v1',
5911
+ receipt: review,
5912
+ });
5913
+ const storedTemplate = validateWorkflowAutomationTemplate({
5914
+ templateRef: row.template_ref,
5915
+ templateDigest: row.template_digest,
5916
+ privacyAttestation: {
5917
+ kind: 'owner-anchored',
5918
+ limitation: 'deidentification-unproven',
5919
+ provenance: 'owner-goal-success',
5920
+ attestationId: row.privacy_attestation_id,
5921
+ attestationDigest: row.privacy_attestation_digest,
5922
+ },
5923
+ });
5924
+ if (JSON.stringify(storedTemplate) !== JSON.stringify(template)
5925
+ || attestationDigest !== row.privacy_attestation_digest
5926
+ || storedTemplate.privacyAttestation.attestationId !== `workflow-owner-anchored:${attestationDigest}`) {
5927
+ throw new DeliveryStoreError('receipt-mismatch', 'owner-anchored workflow attestation is stale');
5928
+ }
5929
+ const contentBinding = this.getBinding(row.owner_binding_id);
5930
+ const resolved = validateResolvedWorkflowAutomationTemplate({
5931
+ contractVersion: 1,
5932
+ template: storedTemplate,
5933
+ ...content,
5934
+ });
5935
+ if (workflowScopeKey(content.scope) !== row.scope_key
5936
+ || content.scope.workspace !== row.workspace || content.scope.preset !== row.preset
5937
+ || content.ownerBindingId !== row.owner_binding_id || content.deliveryBindingId !== row.owner_binding_id
5938
+ || contentBinding === undefined
5939
+ || content.principalId !== externalPrincipalId(contentBinding.principal)
5940
+ || workflowAutomationTemplateContentDigest(content) !== row.template_digest) {
5941
+ throw new DeliveryStoreError('receipt-mismatch', 'owner-anchored workflow content columns do not match');
5942
+ }
5943
+ return Object.freeze({
5944
+ resolved,
5945
+ review: Object.freeze({
5946
+ kind: 'goal-anchored',
5947
+ bindingId: row.owner_binding_id,
5948
+ bindingVersion: row.binding_version,
5949
+ bindingGeneration: row.binding_generation,
5950
+ principalId: row.principal_id,
5951
+ ownerRouteId: row.owner_route_id,
5952
+ goalId: row.goal_id,
5953
+ nativeGoalId: row.native_goal_id,
5954
+ sessionId: row.session_id,
5955
+ runId: row.run_id,
5956
+ turn: row.turn,
5957
+ acceptanceContractId: row.acceptance_contract_id,
5958
+ acceptanceReceiptDigest: row.acceptance_receipt_digest,
5959
+ ownerRunTraceDigest: row.owner_run_trace_digest,
5960
+ taskRef: row.task_ref,
5961
+ }),
5962
+ status: row.status,
5963
+ version: row.version,
5964
+ });
5965
+ }
5453
5966
  requeueCurrentWorkflowTraces() {
5454
5967
  this.assertOpen();
5455
5968
  const now = this.now();