@dsh-enhanced/assistant-evaluation 0.1.66 → 0.1.68

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 (83) hide show
  1. package/README.md +38 -3
  2. package/lib/attention-owner-questions.d.ts +21 -2
  3. package/lib/attention-owner-questions.d.ts.map +1 -1
  4. package/lib/attention-owner-questions.js +57 -20
  5. package/lib/attention-owner-questions.js.map +1 -1
  6. package/lib/attention-tuning.d.ts +66 -5
  7. package/lib/attention-tuning.d.ts.map +1 -1
  8. package/lib/attention-tuning.js +165 -13
  9. package/lib/attention-tuning.js.map +1 -1
  10. package/lib/benchmark/cli.d.ts.map +1 -1
  11. package/lib/benchmark/cli.js +9 -12
  12. package/lib/benchmark/cli.js.map +1 -1
  13. package/lib/benchmark/native-goal-services.d.ts +4 -2
  14. package/lib/benchmark/native-goal-services.d.ts.map +1 -1
  15. package/lib/benchmark/native-goal-services.js +3 -3
  16. package/lib/benchmark/native-goal-services.js.map +1 -1
  17. package/lib/benchmark/native-skills-runtime.d.ts +4 -2
  18. package/lib/benchmark/native-skills-runtime.d.ts.map +1 -1
  19. package/lib/benchmark/native-skills-runtime.js +4 -3
  20. package/lib/benchmark/native-skills-runtime.js.map +1 -1
  21. package/lib/benchmark/native-skills.d.ts +4 -2
  22. package/lib/benchmark/native-skills.d.ts.map +1 -1
  23. package/lib/benchmark/native-skills.js +6 -6
  24. package/lib/benchmark/native-skills.js.map +1 -1
  25. package/lib/benchmark/strategy-capabilities.d.ts.map +1 -1
  26. package/lib/benchmark/strategy-capabilities.js +0 -5
  27. package/lib/benchmark/strategy-capabilities.js.map +1 -1
  28. package/lib/benchmark/strategy-config.d.ts +4 -2
  29. package/lib/benchmark/strategy-config.d.ts.map +1 -1
  30. package/lib/benchmark/strategy-config.js +8 -3
  31. package/lib/benchmark/strategy-config.js.map +1 -1
  32. package/lib/benchmark/strategy-corpus-v2.js +1 -1
  33. package/lib/benchmark/strategy-evidence.d.ts +2 -2
  34. package/lib/benchmark/strategy-evidence.d.ts.map +1 -1
  35. package/lib/benchmark/strategy-evidence.js +1 -1
  36. package/lib/benchmark/strategy-evidence.js.map +1 -1
  37. package/lib/benchmark/strategy-executor.d.ts +4 -2
  38. package/lib/benchmark/strategy-executor.d.ts.map +1 -1
  39. package/lib/benchmark/strategy-executor.js +3 -3
  40. package/lib/benchmark/strategy-executor.js.map +1 -1
  41. package/lib/benchmark/strategy-goal-runtime.d.ts +4 -2
  42. package/lib/benchmark/strategy-goal-runtime.d.ts.map +1 -1
  43. package/lib/benchmark/strategy-goal-runtime.js +4 -3
  44. package/lib/benchmark/strategy-goal-runtime.js.map +1 -1
  45. package/lib/index.d.ts +7 -5
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +3 -2
  48. package/lib/index.js.map +1 -1
  49. package/lib/intake-attention-agreement-store.d.ts +33 -0
  50. package/lib/intake-attention-agreement-store.d.ts.map +1 -0
  51. package/lib/intake-attention-agreement-store.js +162 -0
  52. package/lib/intake-attention-agreement-store.js.map +1 -0
  53. package/lib/intake-attention-agreement.d.ts +63 -0
  54. package/lib/intake-attention-agreement.d.ts.map +1 -1
  55. package/lib/intake-attention-agreement.js +63 -0
  56. package/lib/intake-attention-agreement.js.map +1 -1
  57. package/lib/notification-outcomes.d.ts +7 -0
  58. package/lib/notification-outcomes.d.ts.map +1 -1
  59. package/lib/notification-outcomes.js +52 -1
  60. package/lib/notification-outcomes.js.map +1 -1
  61. package/lib/service.d.ts +117 -5
  62. package/lib/service.d.ts.map +1 -1
  63. package/lib/service.js +242 -6
  64. package/lib/service.js.map +1 -1
  65. package/lib/sqlite.d.ts +1 -1
  66. package/lib/sqlite.d.ts.map +1 -1
  67. package/lib/sqlite.js +92 -1
  68. package/lib/sqlite.js.map +1 -1
  69. package/lib/store.d.ts +14 -0
  70. package/lib/store.d.ts.map +1 -1
  71. package/lib/store.js +42 -1
  72. package/lib/store.js.map +1 -1
  73. package/lib/types.d.ts +20 -1
  74. package/lib/types.d.ts.map +1 -1
  75. package/lib/types.js +8 -0
  76. package/lib/types.js.map +1 -1
  77. package/lib/version.d.ts +1 -1
  78. package/lib/version.js +1 -1
  79. package/package.json +18 -23
  80. package/lib/benchmark/super-relay.d.ts +0 -26
  81. package/lib/benchmark/super-relay.d.ts.map +0 -1
  82. package/lib/benchmark/super-relay.js +0 -173
  83. package/lib/benchmark/super-relay.js.map +0 -1
package/lib/service.js CHANGED
@@ -4,10 +4,11 @@ import { acceptanceDigest, goalDefinitionSituation, validateTaskAcceptanceContra
4
4
  import { EvaluationStore, EvaluationStoreError, canonicalEvaluationScope, evaluationLearningProjectionDigest, } from './store.js';
5
5
  import { registerEvaluationTools } from './tools.js';
6
6
  import { normalizeAttentionCohort, } from './attention-tuning.js';
7
- import { ATTENTION_OWNER_QUESTION_SOURCE_ID, attentionOwnerQuestionIdempotencyKey, attentionOwnerQuestionText, } from './attention-owner-questions.js';
7
+ import { ATTENTION_OWNER_QUESTION_SOURCE_ID, attentionOwnerQuestionIdempotencyKey, attentionOwnerQuestionText, parseAttentionOwnerQuestionChoice, ATTENTION_OWNER_QUESTION_REPLY_PROTOCOL, } from './attention-owner-questions.js';
8
8
  import { validateWorkItemOwnerFeedbackProof } from './work-item-owner-proof.js';
9
- import { computeIntakeAttentionAgreement, INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT, isIntakeProvenanceSource } from './intake-attention-agreement.js';
9
+ import { computeIntakeAttentionAgreement, createIntakeAttentionAgreementSnapshot, INTAKE_ATTENTION_AGREEMENT_HISTORY_PROTOCOL, INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT, isIntakeProvenanceSource, } from './intake-attention-agreement.js';
10
10
  import { executionStatuses, objectiveStatuses, outcomeSourceKinds, TRUSTED_EVALUATION_PRODUCER_PROTOCOL, } from './types.js';
11
+ const PERSIST_DISABLED = Object.freeze({ recorded: false, replayed: false, reason: 'disabled' });
11
12
  const OWNER_NOTIFICATION_WITHDRAWAL_PROTOCOL = 'assistant-delivery/owner-notification-withdrawal/v1';
12
13
  function isOwnerQuestionDeliveryPort(value) {
13
14
  const port = value;
@@ -15,6 +16,10 @@ function isOwnerQuestionDeliveryPort(value) {
15
16
  && typeof port.validateOwnerRoute === 'function' && typeof port.resolveOwnerRoute === 'function'
16
17
  && typeof port.inspectOwnerNotification === 'function' && typeof port.registerOwnerNotificationWithdrawal === 'function';
17
18
  }
19
+ function isOwnerQuestionReplyPort(value) {
20
+ const port = value;
21
+ return typeof port === 'object' && port !== null && typeof port.registerOwnerNotificationReply === 'function';
22
+ }
18
23
  export const ASSISTANT_EVALUATION_SKILL = `# Personal assistant self-evaluation
19
24
 
20
25
  Use this workflow only to assess whether an already-finished task met its objective. A self-assessment is diagnostic evidence, never ground truth and never permission to change production behavior.
@@ -52,6 +57,7 @@ const configSchema = Schema.object({
52
57
  notificationSyncIntervalMs: Schema.number().step(1).min(0).max(86_400_000).default(60_000),
53
58
  attentionTuningAutoAdopt: Schema.boolean().default(false),
54
59
  intakeAttentionAgreementEnabled: Schema.boolean().default(false),
60
+ intakeAttentionAgreementPersistIntervalMs: Schema.number().step(1).min(60_000).max(604_800_000),
55
61
  attentionTuningCohorts: Schema.array(Schema.object({
56
62
  lane: Schema.string().required(),
57
63
  sourceId: Schema.string().required(),
@@ -248,8 +254,13 @@ export class AssistantEvaluationService extends Service {
248
254
  projectionTimer;
249
255
  notificationFacts;
250
256
  lastNotificationSyncAt = Number.NEGATIVE_INFINITY;
257
+ lastIntakeAgreementPersistAt = Number.NEGATIVE_INFINITY;
258
+ /** Rotates across scopes when one database serves several (bounded one row per tick). */
259
+ intakeAgreementPersistCursor;
251
260
  ownerQuestionWithdrawal;
252
261
  activeOwnerQuestionWithdrawals = new WeakSet();
262
+ ownerQuestionReply;
263
+ activeOwnerQuestionReplies = new WeakSet();
253
264
  attentionCohorts;
254
265
  active = true;
255
266
  constructor(ctx, input, options = {}) {
@@ -272,6 +283,15 @@ export class AssistantEvaluationService extends Service {
272
283
  if (this.config.intakeAttentionAgreementEnabled && !this.config.notificationOutcomesEnabled) {
273
284
  throw new Error('assistant-evaluation: intakeAttentionAgreementEnabled requires notificationOutcomesEnabled');
274
285
  }
286
+ if (this.config.intakeAttentionAgreementPersistIntervalMs !== undefined) {
287
+ const value = this.config.intakeAttentionAgreementPersistIntervalMs;
288
+ if (!Number.isSafeInteger(value) || value < 60_000 || value > 604_800_000) {
289
+ throw new Error('assistant-evaluation: intakeAttentionAgreementPersistIntervalMs must be between 60000 and 604800000');
290
+ }
291
+ if (!this.config.intakeAttentionAgreementEnabled || !this.config.notificationOutcomesEnabled) {
292
+ throw new Error('assistant-evaluation: intakeAttentionAgreementPersistIntervalMs requires intakeAttentionAgreementEnabled and notificationOutcomesEnabled');
293
+ }
294
+ }
275
295
  if (this.config.attentionTuningAutoAdopt && !this.config.notificationOutcomesEnabled) {
276
296
  throw new Error('assistant-evaluation: attentionTuningAutoAdopt requires notificationOutcomesEnabled');
277
297
  }
@@ -401,6 +421,8 @@ export class AssistantEvaluationService extends Service {
401
421
  if (this.config.attentionOwnerQuestionDeliveryEnabled) {
402
422
  this.bindOwnerQuestionWithdrawal(currentDelivery);
403
423
  ctx.inject(['assistantDelivery'], deliveryCtx => this.bindOwnerQuestionWithdrawal(deliveryCtx.get('assistantDelivery')));
424
+ this.bindOwnerQuestionReply(currentDelivery);
425
+ ctx.inject(['assistantDelivery'], deliveryCtx => this.bindOwnerQuestionReply(deliveryCtx.get('assistantDelivery')));
404
426
  }
405
427
  const currentVerifier = ctx.get('assistantVerifier');
406
428
  if (isTrustedVerifierEvaluationProducer(currentVerifier))
@@ -437,6 +459,14 @@ export class AssistantEvaluationService extends Service {
437
459
  this.reconcileAttentionOwnerQuestions();
438
460
  }
439
461
  catch { /* retried on a later sync */ }
462
+ const agreementInterval = this.config.intakeAttentionAgreementPersistIntervalMs;
463
+ if (agreementInterval !== undefined && now - this.lastIntakeAgreementPersistAt >= agreementInterval) {
464
+ this.lastIntakeAgreementPersistAt = now;
465
+ try {
466
+ this.reconcileIntakeAttentionAgreementPersistence();
467
+ }
468
+ catch { /* retried on a later sync */ }
469
+ }
440
470
  }
441
471
  void this.reconcileProjections().catch(() => { });
442
472
  }, this.config.projectionIntervalMs);
@@ -449,6 +479,7 @@ export class AssistantEvaluationService extends Service {
449
479
  this.projectionTimer = undefined;
450
480
  this.notificationFacts = undefined;
451
481
  this.ownerQuestionWithdrawal?.dispose();
482
+ this.ownerQuestionReply?.dispose();
452
483
  disposeCurrentProjector?.();
453
484
  this.automationBinding?.dispose();
454
485
  this.deliveryBinding?.dispose();
@@ -630,6 +661,14 @@ export class AssistantEvaluationService extends Service {
630
661
  this.assertActive();
631
662
  return this.store.getForegroundLearningProjection(exactEvaluationHostScope(input.scope), hostIdentifier(input.inboxId, 'inboxId', 1_000));
632
663
  }
664
+ /**
665
+ * Host-only resource metrics of one trusted outcome. Kept out of the
666
+ * learning-projection receipt so historical digests stay unchanged.
667
+ */
668
+ inspectTrustedOutcomeMetrics(input) {
669
+ this.assertActive();
670
+ return this.store.inspectTrustedOutcomeMetrics(exactEvaluationHostScope(input.scope), hostIdentifier(input.outcomeId, 'outcomeId', 200));
671
+ }
633
672
  /** Host-only exact current owner result for one durable work item. */
634
673
  getTrustedWorkItemLearningProjection(input) {
635
674
  this.assertActive();
@@ -928,11 +967,82 @@ export class AssistantEvaluationService extends Service {
928
967
  throw new AssistantEvaluationError('invalid-input', 'intake attention agreement window is invalid');
929
968
  }
930
969
  const { scope } = canonicalEvaluationScope(input.scope);
970
+ return this.#intakeAttentionAgreement(scope, from, to);
971
+ }
972
+ /** Shared read model for the ad hoc inspect and the E1 persistence snapshot. */
973
+ #intakeAttentionAgreement(scope, from, to) {
931
974
  const heads = this.store.notifications.workItemHeads(scope, from, to, INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT);
932
975
  const workItems = this.ctx.get('assistantWorkItems');
933
976
  return computeIntakeAttentionAgreement({ scope, fromNotifiedAt: from, toNotifiedAt: to, records: heads.records,
934
977
  truncated: heads.truncated, source: isIntakeProvenanceSource(workItems) ? workItems : undefined });
935
978
  }
979
+ /**
980
+ * WP22 E1, default off: persist at most one agreement snapshot per call,
981
+ * using one aligned interval bucket. The window is
982
+ * `[bucketEnd - defaultSummaryWindowMs, bucketEnd)`, so a retry inside the
983
+ * same bucket computes the same window and the store replays the existing
984
+ * digest; a late outcome revision changes the digest and appends one
985
+ * immutable revision row for the same window. When the database serves
986
+ * several scopes the method rotates deterministically; one call writes at
987
+ * most one row and one credential-free scan.
988
+ *
989
+ * It never sends, adopts, authorizes intake/auto or changes the read model.
990
+ * Without a WorkItems peer it writes nothing and says so; with a peer that
991
+ * throws per item (including WorkItems' intake ledger off) the item is
992
+ * counted as `unresolved` inside a normal snapshot.
993
+ */
994
+ reconcileIntakeAttentionAgreementPersistence() {
995
+ this.assertActive();
996
+ const interval = this.config.intakeAttentionAgreementPersistIntervalMs;
997
+ if (interval === undefined)
998
+ return PERSIST_DISABLED;
999
+ if (!this.config.notificationOutcomesEnabled || !this.config.intakeAttentionAgreementEnabled)
1000
+ return PERSIST_DISABLED;
1001
+ const workItems = this.ctx.get('assistantWorkItems');
1002
+ if (!isIntakeProvenanceSource(workItems))
1003
+ return Object.freeze({ recorded: false, replayed: false, reason: 'work-items-unavailable' });
1004
+ const scopes = this.store.notifications.workItemScopes(100);
1005
+ if (scopes.length === 0)
1006
+ return Object.freeze({ recorded: false, replayed: false, reason: 'no-work-item-notifications' });
1007
+ const scopeKeys = scopes.map(scope => canonicalEvaluationScope(scope).scopeKey);
1008
+ const cursor = this.intakeAgreementPersistCursor;
1009
+ const next = cursor === undefined ? 0 : scopeKeys.findIndex(key => key > cursor);
1010
+ const index = next === -1 ? 0 : next;
1011
+ const scope = scopes[index];
1012
+ const scopeKey = scopeKeys[index];
1013
+ const now = this.now();
1014
+ const to = Math.floor(now / interval) * interval;
1015
+ const from = Math.max(0, to - this.config.defaultSummaryWindowMs);
1016
+ if (to <= from)
1017
+ return Object.freeze({ recorded: false, replayed: false, reason: 'outside-window' });
1018
+ this.intakeAgreementPersistCursor = scopeKey;
1019
+ const agreement = this.#intakeAttentionAgreement(scope, from, to);
1020
+ if (agreement.scanned === 0)
1021
+ return Object.freeze({ recorded: false, replayed: false, reason: 'no-work-item-notifications', scope });
1022
+ const snapshot = createIntakeAttentionAgreementSnapshot({ agreement, computedAt: now,
1023
+ windowMs: this.config.defaultSummaryWindowMs, scanLimit: INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT });
1024
+ const result = this.store.intakeAttentionAgreement.record(scope, snapshot);
1025
+ return Object.freeze({ recorded: !result.replayed, replayed: result.replayed, scope, snapshot: result.record });
1026
+ }
1027
+ /**
1028
+ * WP22 E1 read-only history, newest first. Undefined when
1029
+ * `intakeAttentionAgreementEnabled` is off; persisted rows are returned only
1030
+ * for the exact owner scope. No snapshot is a causal claim and none may
1031
+ * authorize intake/auto.
1032
+ */
1033
+ inspectIntakeAttentionAgreementHistory(input) {
1034
+ this.assertActive();
1035
+ if (!this.config.intakeAttentionAgreementEnabled)
1036
+ return undefined;
1037
+ const limit = input.limit ?? 20;
1038
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
1039
+ throw new AssistantEvaluationError('invalid-input', 'intake attention agreement history limit is invalid');
1040
+ }
1041
+ const { scope } = canonicalEvaluationScope(input.scope);
1042
+ const snapshots = this.store.intakeAttentionAgreement.list(scope, limit);
1043
+ return Object.freeze({ protocol: INTAKE_ATTENTION_AGREEMENT_HISTORY_PROTOCOL, causal: false,
1044
+ scope: Object.freeze({ workspace: scope.workspace, preset: scope.preset }), snapshots: Object.freeze(snapshots) });
1045
+ }
936
1046
  #attentionPolicy() {
937
1047
  return Object.freeze({
938
1048
  minSample: this.config.attentionTuningMinSample,
@@ -942,7 +1052,14 @@ export class AssistantEvaluationService extends Service {
942
1052
  });
943
1053
  }
944
1054
  #looseningOptions() {
945
- return this.config.attentionLooseningEnabled ? Object.freeze({ replayWindowMs: this.config.attentionLooseningReplayWindowMs }) : undefined;
1055
+ if (!this.config.attentionLooseningEnabled)
1056
+ return undefined;
1057
+ return Object.freeze({
1058
+ replayWindowMs: this.config.attentionLooseningReplayWindowMs,
1059
+ ...(this.config.attentionOwnerQuestionDeliveryEnabled
1060
+ ? { ownerQuestionReplyLoop: Object.freeze({ noticeTtlMs: this.config.attentionOwnerQuestionNoticeTtlMs }) }
1061
+ : {}),
1062
+ });
946
1063
  }
947
1064
  #attentionCohort(input) {
948
1065
  let cohort;
@@ -1095,6 +1212,95 @@ export class AssistantEvaluationService extends Service {
1095
1212
  this.ownerQuestionWithdrawal = binding;
1096
1213
  return binding.dispose;
1097
1214
  }
1215
+ /**
1216
+ * Register (once per Delivery instance) the slice-5 reply consumer. A
1217
+ * Delivery without the reply port is treated as absent: slice-4 notice
1218
+ * delivery keeps working, only replies are not read. A registration error
1219
+ * only disables replies; it never blocks notice delivery.
1220
+ */
1221
+ bindOwnerQuestionReply(source) {
1222
+ if (!this.active || !isOwnerQuestionReplyPort(source))
1223
+ return undefined;
1224
+ if (this.ownerQuestionReply?.source === source)
1225
+ return undefined;
1226
+ const registration = Object.freeze({
1227
+ protocol: ATTENTION_OWNER_QUESTION_REPLY_PROTOCOL,
1228
+ authority: 'assistantEvaluation',
1229
+ sourceId: ATTENTION_OWNER_QUESTION_SOURCE_ID,
1230
+ idempotencyKeyPrefix: attentionOwnerQuestionIdempotencyKey(''),
1231
+ consume: (input) => this.#consumeOwnerQuestionReply(input),
1232
+ });
1233
+ this.activeOwnerQuestionReplies.add(registration);
1234
+ let unregister;
1235
+ try {
1236
+ unregister = source.registerOwnerNotificationReply(registration)?.unregister;
1237
+ }
1238
+ catch (error) {
1239
+ this.activeOwnerQuestionReplies.delete(registration);
1240
+ this.ctx.logger.warn(`assistant-evaluation: owner-question reply registration failed; replies are not read: ${String(error)}`);
1241
+ return undefined;
1242
+ }
1243
+ if (typeof unregister !== 'function') {
1244
+ this.activeOwnerQuestionReplies.delete(registration);
1245
+ this.ctx.logger.warn('assistant-evaluation: owner-question reply registration returned no unregister; replies are not read');
1246
+ return undefined;
1247
+ }
1248
+ const release = unregister;
1249
+ this.ownerQuestionReply?.dispose();
1250
+ let live = true;
1251
+ const binding = Object.freeze({ source, registration, dispose: () => {
1252
+ if (!live)
1253
+ return;
1254
+ live = false;
1255
+ this.activeOwnerQuestionReplies.delete(registration);
1256
+ try {
1257
+ release();
1258
+ }
1259
+ catch { /* Delivery already gone */ }
1260
+ if (this.ownerQuestionReply === binding)
1261
+ this.ownerQuestionReply = undefined;
1262
+ } });
1263
+ this.ownerQuestionReply = binding;
1264
+ return binding.dispose;
1265
+ }
1266
+ /**
1267
+ * Synchronous Delivery callback for one authenticated reply to an exact
1268
+ * owner-question notice. Only exact whole-phrase choices are accepted; a
1269
+ * recognized choice is consumed even when its question is no longer open,
1270
+ * so a stale "authorize-once" never reaches the Agent. Storage errors are
1271
+ * `transient` and leave Delivery's Inbox pending for a bounded retry.
1272
+ */
1273
+ #consumeOwnerQuestionReply(input) {
1274
+ if (!this.active || !this.config.attentionOwnerQuestionDeliveryEnabled)
1275
+ return 'ignored';
1276
+ if (typeof input?.idempotencyKey !== 'string' || typeof input.text !== 'string')
1277
+ return 'ignored';
1278
+ const prefix = attentionOwnerQuestionIdempotencyKey('');
1279
+ if (!input.idempotencyKey.startsWith(prefix))
1280
+ return 'ignored';
1281
+ const questionId = input.idempotencyKey.slice(prefix.length);
1282
+ const choice = parseAttentionOwnerQuestionChoice(input.text);
1283
+ if (choice === undefined || questionId.length === 0)
1284
+ return 'ignored';
1285
+ try {
1286
+ for (const cohort of this.attentionCohorts) {
1287
+ for (const scope of this.store.attention.scopes(cohort)) {
1288
+ const latest = this.store.attention.latestOwnerQuestion(scope, cohort);
1289
+ if (latest === undefined || latest === 'unverifiable' || latest.questionId !== questionId)
1290
+ continue;
1291
+ this.applyAttentionOwnerQuestionReply({ scope, cohort, questionId, choice });
1292
+ return 'applied';
1293
+ }
1294
+ }
1295
+ // The notice was genuinely issued under this source and key, but the
1296
+ // local ledger no longer holds that exact question: consume the
1297
+ // recognized choice instead of sending it to the Agent.
1298
+ return 'applied';
1299
+ }
1300
+ catch {
1301
+ return 'transient';
1302
+ }
1303
+ }
1098
1304
  /**
1099
1305
  * Send-time answer for one owner-question notice. It stays sendable only
1100
1306
  * while its question is still the cohort's latest, open, unanswered, inside
@@ -1171,6 +1377,9 @@ export class AssistantEvaluationService extends Service {
1171
1377
  continue;
1172
1378
  }
1173
1379
  this.bindOwnerQuestionWithdrawal(delivery);
1380
+ // A replaced Delivery instance needs the reply consumer bound as well;
1381
+ // the tick is the existing retry point, so no extra loop is added.
1382
+ this.bindOwnerQuestionReply(delivery);
1174
1383
  // Only queue (or adopt) while this service's own withdrawal check is
1175
1384
  // bound to this exact Delivery instance; otherwise a closed question
1176
1385
  // could be sent unchecked.
@@ -1232,7 +1441,7 @@ export class AssistantEvaluationService extends Service {
1232
1441
  scope: { principalId: owner.principalId, ...principal, workspace: scope.workspace, preset: scope.preset },
1233
1442
  sessionId,
1234
1443
  idempotencyKey,
1235
- text: attentionOwnerQuestionText(question, scope),
1444
+ text: attentionOwnerQuestionText(question, scope, { repliesRead: true }),
1236
1445
  expiresAt,
1237
1446
  attention: { purpose: 'result' },
1238
1447
  withdrawable: true,
@@ -1255,8 +1464,10 @@ export class AssistantEvaluationService extends Service {
1255
1464
  }
1256
1465
  /**
1257
1466
  * Host-internal record-only owner reply to the cohort's latest question.
1258
- * It never records a grant or changes a tuning version; an owner who
1259
- * authorizes still records a grant through `grantAttentionLoosening`.
1467
+ * It never records a grant or changes a tuning version, and it is not the
1468
+ * slice-5 apply path; an owner who authorizes uses
1469
+ * `applyAttentionOwnerQuestionReply` (the only path the Delivery reply
1470
+ * consumer calls) or records a host grant through `grantAttentionLoosening`.
1260
1471
  */
1261
1472
  recordAttentionOwnerQuestionReply(input) {
1262
1473
  this.assertActive();
@@ -1272,6 +1483,23 @@ export class AssistantEvaluationService extends Service {
1272
1483
  || this.now() >= latest.question.createdAt + this.config.attentionOwnerQuestionNoticeTtlMs;
1273
1484
  return this.store.attentionQuestions.recordReply(input.scope, cohort, latest.questionId, input.choice, latest.open, expired);
1274
1485
  }
1486
+ /**
1487
+ * Slice 5 evidence-backed apply path for one exact owner reply; the
1488
+ * Delivery consumer and Host callers use this instead of the record-only
1489
+ * seam. `authorize-once` records the reply and creates one bounded one-time
1490
+ * grant for exactly the questioned proposal (parent/candidate bounds, the
1491
+ * proposal's step size, one adoption); the next tuning tick still re-runs
1492
+ * the independent holdout replay before adopting. `decline` closes the
1493
+ * proposal. A proposal that already had an immutable decision returns
1494
+ * `proposal-terminal` and nothing adopts.
1495
+ */
1496
+ applyAttentionOwnerQuestionReply(input) {
1497
+ this.assertActive();
1498
+ const cohort = this.#attentionCohort(input.cohort);
1499
+ if (!this.config.attentionOwnerQuestionDeliveryEnabled)
1500
+ return Object.freeze({ status: 'rejected', reason: 'delivery-disabled' });
1501
+ return this.store.attention.applyOwnerQuestionReply(input.scope, cohort, input.questionId, input.choice, { noticeTtlMs: this.config.attentionOwnerQuestionNoticeTtlMs });
1502
+ }
1275
1503
  /** Per-question delivery decisions and recorded replies for one cohort. */
1276
1504
  attentionOwnerQuestionStatus(input) {
1277
1505
  this.assertActive();
@@ -1303,6 +1531,14 @@ export class AssistantEvaluationService extends Service {
1303
1531
  return this.active && typeof registration === 'object' && registration !== null
1304
1532
  && this.activeOwnerQuestionWithdrawals.has(registration);
1305
1533
  }
1534
+ /**
1535
+ * Delivery's slot-anchored proof that the slice-5 reply consumer under the
1536
+ * `assistantEvaluation` authority is this live service's own registration.
1537
+ */
1538
+ ownsOwnerNotificationReply(registration) {
1539
+ return this.active && typeof registration === 'object' && registration !== null
1540
+ && this.activeOwnerQuestionReplies.has(registration);
1541
+ }
1306
1542
  /** Delivery uses the same ownership boundary as Automations. */
1307
1543
  ownsTrustedDeliveryEvaluationRegistration(registration) {
1308
1544
  return this.active && typeof registration === 'object' && registration !== null