@dsh-enhanced/assistant-evaluation 0.1.23 → 0.1.30

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 (132) hide show
  1. package/README.md +109 -3
  2. package/bin/dsh-benchmark.js +21 -0
  3. package/lib/benchmark/cli.d.ts +6 -0
  4. package/lib/benchmark/cli.d.ts.map +1 -0
  5. package/lib/benchmark/cli.js +332 -0
  6. package/lib/benchmark/cli.js.map +1 -0
  7. package/lib/benchmark/corpus.d.ts +28 -0
  8. package/lib/benchmark/corpus.d.ts.map +1 -0
  9. package/lib/benchmark/corpus.js +173 -0
  10. package/lib/benchmark/corpus.js.map +1 -0
  11. package/lib/benchmark/deepseek.d.ts +15 -0
  12. package/lib/benchmark/deepseek.d.ts.map +1 -0
  13. package/lib/benchmark/deepseek.js +143 -0
  14. package/lib/benchmark/deepseek.js.map +1 -0
  15. package/lib/benchmark/holdout-evidence.d.ts +126 -0
  16. package/lib/benchmark/holdout-evidence.d.ts.map +1 -0
  17. package/lib/benchmark/holdout-evidence.js +466 -0
  18. package/lib/benchmark/holdout-evidence.js.map +1 -0
  19. package/lib/benchmark/holdout-protocol.d.ts +68 -0
  20. package/lib/benchmark/holdout-protocol.d.ts.map +1 -0
  21. package/lib/benchmark/holdout-protocol.js +271 -0
  22. package/lib/benchmark/holdout-protocol.js.map +1 -0
  23. package/lib/benchmark/holdout-provider.d.ts +26 -0
  24. package/lib/benchmark/holdout-provider.d.ts.map +1 -0
  25. package/lib/benchmark/holdout-provider.js +382 -0
  26. package/lib/benchmark/holdout-provider.js.map +1 -0
  27. package/lib/benchmark/holdout.d.ts +83 -0
  28. package/lib/benchmark/holdout.d.ts.map +1 -0
  29. package/lib/benchmark/holdout.js +391 -0
  30. package/lib/benchmark/holdout.js.map +1 -0
  31. package/lib/benchmark/index.d.ts +9 -0
  32. package/lib/benchmark/index.d.ts.map +1 -0
  33. package/lib/benchmark/index.js +7 -0
  34. package/lib/benchmark/index.js.map +1 -0
  35. package/lib/benchmark/memory-corpus.d.ts +55 -0
  36. package/lib/benchmark/memory-corpus.d.ts.map +1 -0
  37. package/lib/benchmark/memory-corpus.js +153 -0
  38. package/lib/benchmark/memory-corpus.js.map +1 -0
  39. package/lib/benchmark/memory-runtime.d.ts +63 -0
  40. package/lib/benchmark/memory-runtime.d.ts.map +1 -0
  41. package/lib/benchmark/memory-runtime.js +75 -0
  42. package/lib/benchmark/memory-runtime.js.map +1 -0
  43. package/lib/benchmark/native.d.ts +46 -0
  44. package/lib/benchmark/native.d.ts.map +1 -0
  45. package/lib/benchmark/native.js +300 -0
  46. package/lib/benchmark/native.js.map +1 -0
  47. package/lib/benchmark/report.d.ts +4 -0
  48. package/lib/benchmark/report.d.ts.map +1 -0
  49. package/lib/benchmark/report.js +153 -0
  50. package/lib/benchmark/report.js.map +1 -0
  51. package/lib/benchmark/runner.d.ts +25 -0
  52. package/lib/benchmark/runner.d.ts.map +1 -0
  53. package/lib/benchmark/runner.js +119 -0
  54. package/lib/benchmark/runner.js.map +1 -0
  55. package/lib/benchmark/schema.d.ts +20 -0
  56. package/lib/benchmark/schema.d.ts.map +1 -0
  57. package/lib/benchmark/schema.js +183 -0
  58. package/lib/benchmark/schema.js.map +1 -0
  59. package/lib/benchmark/store.d.ts +18 -0
  60. package/lib/benchmark/store.d.ts.map +1 -0
  61. package/lib/benchmark/store.js +321 -0
  62. package/lib/benchmark/store.js.map +1 -0
  63. package/lib/benchmark/strategy-capabilities.d.ts +154 -0
  64. package/lib/benchmark/strategy-capabilities.d.ts.map +1 -0
  65. package/lib/benchmark/strategy-capabilities.js +300 -0
  66. package/lib/benchmark/strategy-capabilities.js.map +1 -0
  67. package/lib/benchmark/strategy-config.d.ts +26 -0
  68. package/lib/benchmark/strategy-config.d.ts.map +1 -0
  69. package/lib/benchmark/strategy-config.js +80 -0
  70. package/lib/benchmark/strategy-config.js.map +1 -0
  71. package/lib/benchmark/strategy-corpus.d.ts +21 -0
  72. package/lib/benchmark/strategy-corpus.d.ts.map +1 -0
  73. package/lib/benchmark/strategy-corpus.js +95 -0
  74. package/lib/benchmark/strategy-corpus.js.map +1 -0
  75. package/lib/benchmark/strategy-evidence.d.ts +161 -0
  76. package/lib/benchmark/strategy-evidence.d.ts.map +1 -0
  77. package/lib/benchmark/strategy-evidence.js +520 -0
  78. package/lib/benchmark/strategy-evidence.js.map +1 -0
  79. package/lib/benchmark/strategy-executor.d.ts +27 -0
  80. package/lib/benchmark/strategy-executor.d.ts.map +1 -0
  81. package/lib/benchmark/strategy-executor.js +178 -0
  82. package/lib/benchmark/strategy-executor.js.map +1 -0
  83. package/lib/benchmark/strategy-goal-runtime.d.ts +78 -0
  84. package/lib/benchmark/strategy-goal-runtime.d.ts.map +1 -0
  85. package/lib/benchmark/strategy-goal-runtime.js +324 -0
  86. package/lib/benchmark/strategy-goal-runtime.js.map +1 -0
  87. package/lib/benchmark/strategy-meter.d.ts +62 -0
  88. package/lib/benchmark/strategy-meter.d.ts.map +1 -0
  89. package/lib/benchmark/strategy-meter.js +236 -0
  90. package/lib/benchmark/strategy-meter.js.map +1 -0
  91. package/lib/benchmark/strategy-owner.d.ts +71 -0
  92. package/lib/benchmark/strategy-owner.d.ts.map +1 -0
  93. package/lib/benchmark/strategy-owner.js +282 -0
  94. package/lib/benchmark/strategy-owner.js.map +1 -0
  95. package/lib/benchmark/strategy-plan.d.ts +51 -0
  96. package/lib/benchmark/strategy-plan.d.ts.map +1 -0
  97. package/lib/benchmark/strategy-plan.js +71 -0
  98. package/lib/benchmark/strategy-plan.js.map +1 -0
  99. package/lib/benchmark/strategy-policy.d.ts +16 -0
  100. package/lib/benchmark/strategy-policy.d.ts.map +1 -0
  101. package/lib/benchmark/strategy-policy.js +77 -0
  102. package/lib/benchmark/strategy-policy.js.map +1 -0
  103. package/lib/benchmark/strategy.d.ts +17 -0
  104. package/lib/benchmark/strategy.d.ts.map +1 -0
  105. package/lib/benchmark/strategy.js +21 -0
  106. package/lib/benchmark/strategy.js.map +1 -0
  107. package/lib/benchmark/types.d.ts +133 -0
  108. package/lib/benchmark/types.d.ts.map +1 -0
  109. package/lib/benchmark/types.js +3 -0
  110. package/lib/benchmark/types.js.map +1 -0
  111. package/lib/benchmark/usage.d.ts +36 -0
  112. package/lib/benchmark/usage.d.ts.map +1 -0
  113. package/lib/benchmark/usage.js +84 -0
  114. package/lib/benchmark/usage.js.map +1 -0
  115. package/lib/service.d.ts +50 -1
  116. package/lib/service.d.ts.map +1 -1
  117. package/lib/service.js +556 -9
  118. package/lib/service.js.map +1 -1
  119. package/lib/sqlite.d.ts +1 -1
  120. package/lib/sqlite.d.ts.map +1 -1
  121. package/lib/sqlite.js +94 -2
  122. package/lib/sqlite.js.map +1 -1
  123. package/lib/store.d.ts +27 -5
  124. package/lib/store.d.ts.map +1 -1
  125. package/lib/store.js +324 -25
  126. package/lib/store.js.map +1 -1
  127. package/lib/types.d.ts +114 -5
  128. package/lib/types.d.ts.map +1 -1
  129. package/lib/types.js.map +1 -1
  130. package/lib/version.d.ts +1 -1
  131. package/lib/version.js +1 -1
  132. package/package.json +125 -18
package/lib/service.js CHANGED
@@ -1,8 +1,9 @@
1
- import { Context, Service } from '@deepseek-ai/cordis';
1
+ import { Context, Service, symbols } from '@deepseek-ai/cordis';
2
2
  import Schema from '@deepseek-ai/schemastery';
3
- import { EvaluationStore, canonicalEvaluationScope } from './store.js';
3
+ import { acceptanceDigest, validateTaskAcceptanceContract, validateTaskVerificationReceipt } from '@dsh-enhanced/task-acceptance-contract';
4
+ import { EvaluationStore, canonicalEvaluationScope, evaluationLearningProjectionDigest, } from './store.js';
4
5
  import { registerEvaluationTools } from './tools.js';
5
- import { TRUSTED_EVALUATION_PRODUCER_PROTOCOL } from './types.js';
6
+ import { executionStatuses, objectiveStatuses, outcomeSourceKinds, TRUSTED_EVALUATION_PRODUCER_PROTOCOL, } from './types.js';
6
7
  export const ASSISTANT_EVALUATION_SKILL = `# Personal assistant self-evaluation
7
8
 
8
9
  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.
@@ -88,6 +89,71 @@ function hostIdentifier(value, label, maxBytes) {
88
89
  function hostOutcomeId(value) {
89
90
  return hostIdentifier(value, 'outcomeId', 200);
90
91
  }
92
+ function recordValue(value) {
93
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
94
+ ? value
95
+ : undefined;
96
+ }
97
+ function serviceIdentity(value) {
98
+ try {
99
+ const original = Reflect.get(value, symbols.original);
100
+ return (typeof original === 'object' && original !== null) || typeof original === 'function'
101
+ ? original
102
+ : value;
103
+ }
104
+ catch {
105
+ return value;
106
+ }
107
+ }
108
+ function exactHostIdentifier(value, label, maxBytes) {
109
+ if (typeof value !== 'string')
110
+ return false;
111
+ try {
112
+ return hostIdentifier(value, label, maxBytes) === value;
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ }
118
+ function validTrustedEvidence(value, maximum) {
119
+ if (!Array.isArray(value) || value.length > maximum)
120
+ return false;
121
+ return value.every((item, index) => {
122
+ const entry = recordValue(item);
123
+ return entry !== undefined
124
+ && exactHostIdentifier(entry.kind, `evidence[${index}].kind`, 64)
125
+ && exactHostIdentifier(entry.ref, `evidence[${index}].ref`, 512)
126
+ && (entry.digest === undefined
127
+ || exactHostIdentifier(entry.digest, `evidence[${index}].digest`, 128));
128
+ });
129
+ }
130
+ function validTrustedTaskComponent(value, statuses, maximumEvidence) {
131
+ const component = recordValue(value);
132
+ const source = recordValue(component?.source);
133
+ const evaluator = recordValue(component?.evaluator);
134
+ return component !== undefined
135
+ && exactHostIdentifier(component.outcomeId, 'component.outcomeId', 200)
136
+ && typeof component.status === 'string' && statuses.includes(component.status)
137
+ && source !== undefined
138
+ && typeof source.kind === 'string' && outcomeSourceKinds.includes(source.kind)
139
+ && exactHostIdentifier(source.id, 'component.source.id', 200)
140
+ && validTrustedEvidence(component.evidence, maximumEvidence)
141
+ && Number.isSafeInteger(component.occurredAt) && component.occurredAt >= 0
142
+ && evaluator !== undefined
143
+ && exactHostIdentifier(evaluator.id, 'component.evaluator.id', 200)
144
+ && exactHostIdentifier(evaluator.version, 'component.evaluator.version', 100);
145
+ }
146
+ function isTrustedGoalsOutcomeOwnerResolver(value) {
147
+ return typeof value === 'object' && value !== null
148
+ && typeof value
149
+ .resolveOwnerGoalOutcomeFeedbackTarget === 'function';
150
+ }
151
+ function isTrustedVerifierTaskInspector(value) {
152
+ return typeof value === 'object' && value !== null
153
+ && typeof value
154
+ .trustedVerificationProducerGeneration === 'function'
155
+ && typeof value.inspectAcceptedTask === 'function';
156
+ }
91
157
  function isTrustedAutomationEvaluationProducer(value) {
92
158
  return typeof value === 'object' && value !== null
93
159
  && typeof value
@@ -102,6 +168,11 @@ function isTrustedDeliveryEvaluationProducer(value) {
102
168
  && typeof value
103
169
  .registerTrustedDeliveryEvaluationSink === 'function';
104
170
  }
171
+ function isTrustedVerifierEvaluationProducer(value) {
172
+ return typeof value === 'object' && value !== null
173
+ && typeof value.trustedVerificationProducerGeneration === 'function'
174
+ && typeof value.registerTrustedVerifierEvaluationSink === 'function';
175
+ }
105
176
  function isTrustedEvaluationProjector(value) {
106
177
  return typeof value === 'object' && value !== null
107
178
  && typeof value.projectTrustedEvaluationTaskRevision === 'function';
@@ -122,8 +193,10 @@ export class AssistantEvaluationService extends Service {
122
193
  projector;
123
194
  automationBinding;
124
195
  deliveryBinding;
196
+ verifierBinding;
125
197
  activeAutomationRegistrations = new WeakSet();
126
198
  activeDeliveryRegistrations = new WeakSet();
199
+ activeVerifierRegistrations = new WeakSet();
127
200
  activeProjections = new Map();
128
201
  projectionTimer;
129
202
  active = true;
@@ -212,6 +285,15 @@ export class AssistantEvaluationService extends Service {
212
285
  return;
213
286
  return this.bindDeliveryProducer(producer);
214
287
  });
288
+ const currentVerifier = ctx.get('assistantVerifier');
289
+ if (isTrustedVerifierEvaluationProducer(currentVerifier))
290
+ this.bindVerifierProducer(currentVerifier);
291
+ ctx.inject(['assistantVerifier'], verifierCtx => {
292
+ const producer = verifierCtx.get('assistantVerifier');
293
+ if (!isTrustedVerifierEvaluationProducer(producer))
294
+ return;
295
+ return this.bindVerifierProducer(producer);
296
+ });
215
297
  if (this.config.projectionIntervalMs > 0) {
216
298
  this.projectionTimer = setInterval(() => {
217
299
  void this.reconcileProjections().catch(() => { });
@@ -226,6 +308,7 @@ export class AssistantEvaluationService extends Service {
226
308
  disposeCurrentProjector?.();
227
309
  this.automationBinding?.dispose();
228
310
  this.deliveryBinding?.dispose();
311
+ this.verifierBinding?.dispose();
229
312
  disposeCurrentTools?.();
230
313
  disposeCurrentSkill?.();
231
314
  await Promise.allSettled(this.activeProjections.values());
@@ -333,6 +416,15 @@ export class AssistantEvaluationService extends Service {
333
416
  await Promise.allSettled(this.activeProjections.values());
334
417
  }
335
418
  }
419
+ /**
420
+ * Mint the opaque scope token required by trusted Host APIs. Consumers of an
421
+ * optionally injected Evaluation service can call this instance method and
422
+ * keep their package dependency type-only.
423
+ */
424
+ canonicalHostScope(input) {
425
+ this.assertActive();
426
+ return canonicalEvaluationHostScope(input);
427
+ }
336
428
  /**
337
429
  * Exact Host-only lookup used to project quality evidence into another local
338
430
  * ledger. Untrusted and missing rows are deliberately indistinguishable.
@@ -364,6 +456,73 @@ export class AssistantEvaluationService extends Service {
364
456
  const scope = exactEvaluationHostScope(input.scope);
365
457
  return this.store.getTaskLearningProjection(scope, hostOutcomeId(input.outcomeId));
366
458
  }
459
+ /** Host-only exact canonical run proof; conflicted tasks have no ready proof. */
460
+ getTrustedAutomationRunLearningProjection(input) {
461
+ this.assertActive();
462
+ return this.store.getAutomationRunLearningProjection(exactEvaluationHostScope(input.scope), input.runId);
463
+ }
464
+ /** Host-only exact canonical whole-goal proof, addressed by assessment id. */
465
+ getTrustedGoalOutcomeLearningProjection(input) {
466
+ this.assertActive();
467
+ return this.store.getGoalOutcomeLearningProjection(exactEvaluationHostScope(input.scope), hostIdentifier(input.assessmentId, 'assessmentId', 1_000));
468
+ }
469
+ /**
470
+ * Validate that a structurally untrusted value is the exact current trusted
471
+ * Evaluation projection. This is the runtime seam for optional consumers:
472
+ * malformed, forged, stale, cross-scope and post-disposal values return false.
473
+ */
474
+ isTrustedTaskLearningProjectionReceipt(input) {
475
+ try {
476
+ this.assertActive();
477
+ const receipt = recordValue(input);
478
+ const scope = recordValue(receipt?.scope);
479
+ const projection = recordValue(receipt?.projection);
480
+ if (receipt === undefined || scope === undefined || projection === undefined
481
+ || !exactHostIdentifier(receipt.triggerOutcomeId, 'triggerOutcomeId', 200)
482
+ || !exactHostIdentifier(scope.workspace, 'scope.workspace', 4_096)
483
+ || !exactHostIdentifier(scope.preset, 'scope.preset', 200)
484
+ || typeof receipt.scopeKey !== 'string'
485
+ || !Number.isSafeInteger(receipt.scopeWatermark) || receipt.scopeWatermark < 1
486
+ || !exactHostIdentifier(receipt.situation, 'situation', this.config.maxSituationBytes)
487
+ || (receipt.execution !== undefined
488
+ && !validTrustedTaskComponent(receipt.execution, executionStatuses, this.config.maxEvidenceRefs))
489
+ || (receipt.objective !== undefined
490
+ && !validTrustedTaskComponent(receipt.objective, objectiveStatuses, this.config.maxEvidenceRefs))
491
+ || (projection.subjectKind !== 'automation-run'
492
+ && projection.subjectKind !== 'foreground-turn'
493
+ && projection.subjectKind !== 'goal-step'
494
+ && projection.subjectKind !== 'goal-outcome'
495
+ && projection.subjectKind !== 'outcome')
496
+ || !exactHostIdentifier(projection.subjectRef, 'projection.subjectRef', 1_000)
497
+ || !Number.isSafeInteger(projection.version) || projection.version < 1
498
+ || projection.version > 1_000_000_000
499
+ || typeof projection.digest !== 'string' || !/^[a-f\d]{64}$/u.test(projection.digest)
500
+ || (projection.disposition !== 'upsert' && projection.disposition !== 'retract')
501
+ || (projection.evidenceOutcomeId !== undefined
502
+ && !exactHostIdentifier(projection.evidenceOutcomeId, 'projection.evidenceOutcomeId', 200))) {
503
+ return false;
504
+ }
505
+ const canonical = canonicalEvaluationScope(scope);
506
+ if (canonical.scope.workspace !== scope.workspace || canonical.scope.preset !== scope.preset
507
+ || canonical.scopeKey !== receipt.scopeKey
508
+ || evaluationLearningProjectionDigest(input) !== projection.digest)
509
+ return false;
510
+ const current = this.store.getTaskLearningProjection(canonical.scope, receipt.triggerOutcomeId);
511
+ return current !== undefined
512
+ && current.scopeKey === receipt.scopeKey
513
+ && current.scopeWatermark === receipt.scopeWatermark
514
+ && current.situation === receipt.situation
515
+ && current.projection.subjectKind === projection.subjectKind
516
+ && current.projection.subjectRef === projection.subjectRef
517
+ && current.projection.version === projection.version
518
+ && current.projection.digest === projection.digest
519
+ && current.projection.disposition === projection.disposition
520
+ && current.projection.evidenceOutcomeId === projection.evidenceOutcomeId;
521
+ }
522
+ catch {
523
+ return false;
524
+ }
525
+ }
367
526
  /**
368
527
  * Synchronous cross-ledger writer fence. Evaluation's writer lock is always
369
528
  * acquired before the callback may acquire Evolution's writer lock.
@@ -379,6 +538,36 @@ export class AssistantEvaluationService extends Service {
379
538
  evidence: input.evidence,
380
539
  }, callback);
381
540
  }
541
+ /**
542
+ * Canonical task writer fence for Host consumers whose commit depends on
543
+ * Evaluation state itself, rather than on delivery of that state to Evolution.
544
+ */
545
+ withTrustedCanonicalLearningWriterFence(input, callback) {
546
+ this.assertActive();
547
+ if (typeof callback !== 'function') {
548
+ throw new AssistantEvaluationError('invalid-input', 'writer fence callback is required');
549
+ }
550
+ const scope = exactEvaluationHostScope(input.scope);
551
+ return this.store.withLearningWriterFence(scope, {
552
+ scopeWatermark: input.scopeWatermark,
553
+ evidence: input.evidence,
554
+ }, callback, { requireProjectionDelivery: false });
555
+ }
556
+ /**
557
+ * Exact canonical task fence for reconciliation. It accepts a current
558
+ * retraction so a downstream ledger can invalidate stale state atomically.
559
+ */
560
+ withTrustedCanonicalTaskWriterFence(input, callback) {
561
+ this.assertActive();
562
+ if (typeof callback !== 'function') {
563
+ throw new AssistantEvaluationError('invalid-input', 'writer fence callback is required');
564
+ }
565
+ const scope = exactEvaluationHostScope(input.scope);
566
+ return this.store.withCanonicalTaskWriterFence(scope, {
567
+ scopeWatermark: input.scopeWatermark,
568
+ evidence: input.evidence,
569
+ }, callback);
570
+ }
382
571
  /** Host-only seam for a memory-assisted/model evaluator; always stored as self-reported. */
383
572
  appendSelfAssessment(input) {
384
573
  this.assertActive();
@@ -473,6 +662,10 @@ export class AssistantEvaluationService extends Service {
473
662
  return this.active && typeof registration === 'object' && registration !== null
474
663
  && this.activeDeliveryRegistrations.has(registration);
475
664
  }
665
+ ownsTrustedVerifierEvaluationRegistration(registration) {
666
+ return this.active && typeof registration === 'object' && registration !== null
667
+ && this.activeVerifierRegistrations.has(registration);
668
+ }
476
669
  review(agent, input = {}) {
477
670
  this.assertActive();
478
671
  const scope = this.agentScope(agent, 'evaluation_review');
@@ -622,15 +815,35 @@ export class AssistantEvaluationService extends Service {
622
815
  const registration = Object.freeze({
623
816
  protocol: TRUSTED_EVALUATION_PRODUCER_PROTOCOL,
624
817
  producer: 'assistant-delivery',
818
+ ownerRevisionProtocol: 'owner-objective-revision/v2',
625
819
  generation,
626
820
  owner: this,
627
821
  issueCapability: (claims) => {
628
822
  this.assertCurrentProducer(producer, generation, registered, 'delivery');
629
823
  const normalized = this.deliveryClaims(claims);
824
+ if (normalized.subjectKind === 'goal-outcome') {
825
+ const { proof, verifierOutcome } = this.resolveGoalOutcomeOwnerProof(normalized);
826
+ if (this.store.adoptTrustedGoalOutcomeOwnerBaseline(normalized, proof, verifierOutcome)) {
827
+ this.notifyTrustedTaskChange();
828
+ }
829
+ }
830
+ else
831
+ this.store.adoptLegacyOwnerFeedback(normalized);
630
832
  const capability = Object.freeze(Object.create(null));
631
833
  capabilities.set(capability, normalized);
632
834
  return capability;
633
835
  },
836
+ inspect: (capabilityReceipt) => {
837
+ this.assertCurrentProducer(producer, generation, registered, 'delivery');
838
+ if (typeof capabilityReceipt !== 'object' || capabilityReceipt === null)
839
+ throw new AssistantEvaluationError('forbidden', 'invalid owner capability');
840
+ const claims = capabilities.get(capabilityReceipt);
841
+ if (claims?.ownerCommand === undefined)
842
+ throw new AssistantEvaluationError('forbidden', 'missing owner lineage');
843
+ if (claims.subjectKind === 'goal-outcome')
844
+ this.resolveGoalOutcomeOwnerProof(claims);
845
+ return this.store.ownerObjectiveState(claims.scope, claims.subjectKind ?? 'automation-run', claims.subjectRef ?? claims.runId, claims.ownerCommand.principalRecordId, claims.ownerCommand.principalVersion);
846
+ },
634
847
  append: (input) => {
635
848
  this.assertCurrentProducer(producer, generation, registered, 'delivery');
636
849
  if (typeof input !== 'object' || input === null || Array.isArray(input)
@@ -649,6 +862,8 @@ export class AssistantEvaluationService extends Service {
649
862
  || hostIdentifier(input.idempotencyKey, 'idempotencyKey', 200) !== claims.idempotencyKey) {
650
863
  throw new AssistantEvaluationError('forbidden', 'delivery Evaluation capability identity changed');
651
864
  }
865
+ if (claims.subjectKind === 'goal-outcome')
866
+ this.resolveGoalOutcomeOwnerProof(claims);
652
867
  return this.appendTrusted({
653
868
  scope: claims.scope,
654
869
  situation: claims.situation,
@@ -658,14 +873,14 @@ export class AssistantEvaluationService extends Service {
658
873
  source: { kind: 'user-feedback', id: 'assistant-delivery/typed-owner-feedback' },
659
874
  trust: 'trusted',
660
875
  evidence: [
661
- { kind: 'automation-run', ref: claims.runId },
876
+ { kind: claims.subjectKind ?? 'automation-run', ref: claims.subjectRef ?? claims.runId },
662
877
  { kind: 'delivery-outbox', ref: claims.outboxId },
663
878
  ],
664
879
  metrics: {},
665
880
  occurredAt: claims.occurredAt,
666
881
  idempotencyKey: claims.idempotencyKey,
667
882
  evaluator: { id: 'assistant-delivery-owner-feedback', version: '2' },
668
- });
883
+ }, claims.ownerCommand);
669
884
  },
670
885
  });
671
886
  this.activeDeliveryRegistrations.add(registration);
@@ -700,6 +915,115 @@ export class AssistantEvaluationService extends Service {
700
915
  previous.dispose();
701
916
  return dispose;
702
917
  }
918
+ bindVerifierProducer(producer) {
919
+ const generation = hostIdentifier(producer.trustedVerificationProducerGeneration(), 'trusted verifier generation', 200);
920
+ const current = this.verifierBinding;
921
+ if (current?.generation === generation)
922
+ return current.dispose;
923
+ let registered = true;
924
+ let disposeRegistration;
925
+ let binding;
926
+ const assertCurrent = () => {
927
+ this.assertActive();
928
+ if (!registered || this.verifierBinding?.producer !== producer
929
+ || this.verifierBinding.generation !== generation
930
+ || producer.trustedVerificationProducerGeneration() !== generation) {
931
+ throw new AssistantEvaluationError('forbidden', 'stale verifier Evaluation producer capability');
932
+ }
933
+ };
934
+ const registration = Object.freeze({
935
+ protocol: 'assistant-verifier/evaluation/v1',
936
+ generation,
937
+ owner: this,
938
+ append: async (input) => {
939
+ assertCurrent();
940
+ if (typeof input !== 'object' || input === null || Array.isArray(input)
941
+ || Object.keys(input).sort().join(',') !== 'contract,execution,receipt') {
942
+ throw new AssistantEvaluationError('forbidden', 'verifier Evaluation input is invalid');
943
+ }
944
+ let contract;
945
+ let receipt;
946
+ try {
947
+ contract = validateTaskAcceptanceContract(input.contract);
948
+ receipt = validateTaskVerificationReceipt(contract, input.receipt);
949
+ }
950
+ catch {
951
+ throw new AssistantEvaluationError('forbidden', 'verifier Evaluation contract or receipt is invalid');
952
+ }
953
+ const execution = input.execution;
954
+ const completedAt = execution?.completedAt;
955
+ if (execution === null || typeof execution !== 'object' || Array.isArray(execution)
956
+ || !['succeeded', 'failed', 'timed-out', 'cancelled', 'unknown'].includes(execution.status)
957
+ || typeof execution.quiescent !== 'boolean'
958
+ || (!execution.quiescent && receipt.objectiveStatus !== 'unknown') || !Number.isSafeInteger(completedAt)
959
+ || completedAt < contract.issuedAt || receipt.startedAt < completedAt
960
+ || receipt.validUntil <= this.now() || receipt.completedAt < receipt.startedAt
961
+ || receipt.completedAt > this.now()
962
+ || execution.executionRef !== contract.task.ref) {
963
+ throw new AssistantEvaluationError('forbidden', 'verifier Evaluation execution is invalid');
964
+ }
965
+ const taskRef = hostIdentifier(contract.task.ref, 'verification task ref', 1_000);
966
+ const scope = canonicalEvaluationScope(contract.scope).scope;
967
+ let situation;
968
+ if (contract.task.kind === 'foreground-turn')
969
+ situation = `foreground:${taskRef}`;
970
+ else if (contract.task.kind === 'goal-step' || contract.task.kind === 'goal-outcome') {
971
+ const goal = contract.task.goal;
972
+ situation = `goal:${hostIdentifier(goal.id, 'goal id', 1_000)}:definition:${goal.definitionVersion}`;
973
+ }
974
+ else {
975
+ const projection = this.store.getAutomationRunLearningProjection(scope, taskRef);
976
+ if (projection === undefined || projection.execution === undefined) {
977
+ throw new AssistantEvaluationError('forbidden', 'automation terminal Evaluation projection is not available');
978
+ }
979
+ situation = projection.situation;
980
+ }
981
+ this.appendTrusted({
982
+ scope, situation,
983
+ executionStatus: execution.status,
984
+ objectiveStatus: receipt.objectiveStatus,
985
+ deliveryStatus: 'not-required',
986
+ source: { kind: 'evaluator', id: 'assistant-verifier' }, trust: 'trusted',
987
+ evidence: [
988
+ { kind: contract.task.kind, ref: taskRef },
989
+ { kind: 'acceptance-contract', ref: contract.id, digest: contract.digest },
990
+ { kind: 'verification-receipt', ref: receipt.id, digest: receipt.digest },
991
+ { kind: 'execution', ref: execution.executionRef },
992
+ ],
993
+ metrics: {}, occurredAt: receipt.completedAt,
994
+ idempotencyKey: `assistant-verifier:${receipt.id}`,
995
+ evaluator: { id: 'assistant-verifier', version: '1' },
996
+ });
997
+ },
998
+ });
999
+ this.activeVerifierRegistrations.add(registration);
1000
+ const dispose = () => {
1001
+ if (!registered)
1002
+ return;
1003
+ registered = false;
1004
+ this.activeVerifierRegistrations.delete(registration);
1005
+ if (this.verifierBinding === binding)
1006
+ this.verifierBinding = undefined;
1007
+ disposeRegistration?.();
1008
+ };
1009
+ binding = Object.freeze({ producer, generation, dispose });
1010
+ const previous = this.verifierBinding;
1011
+ this.verifierBinding = binding;
1012
+ try {
1013
+ disposeRegistration = producer.registerTrustedVerifierEvaluationSink(registration);
1014
+ if (typeof disposeRegistration !== 'function')
1015
+ throw new AssistantEvaluationError('forbidden', 'verifier returned no registration disposer');
1016
+ }
1017
+ catch (error) {
1018
+ registered = false;
1019
+ this.activeVerifierRegistrations.delete(registration);
1020
+ if (this.verifierBinding === binding)
1021
+ this.verifierBinding = previous;
1022
+ throw error;
1023
+ }
1024
+ previous?.dispose();
1025
+ return dispose;
1026
+ }
703
1027
  producerGeneration(producer) {
704
1028
  return hostIdentifier(producer.trustedEvaluationProducerGeneration(), 'trusted Evaluation producer generation', 200);
705
1029
  }
@@ -752,13 +1076,44 @@ export class AssistantEvaluationService extends Service {
752
1076
  deliveryClaims(input) {
753
1077
  if (typeof input !== 'object' || input === null || Array.isArray(input)
754
1078
  || (input.objectiveStatus !== 'achieved' && input.objectiveStatus !== 'partial'
755
- && input.objectiveStatus !== 'not-achieved')
1079
+ && input.objectiveStatus !== 'not-achieved'
1080
+ && !(input.objectiveStatus === 'unknown' && input.ownerCommand?.action === 'withdraw'))
756
1081
  || !Number.isSafeInteger(input.occurredAt) || input.occurredAt < 0) {
757
1082
  throw new AssistantEvaluationError('forbidden', 'delivery Evaluation claims are invalid');
758
1083
  }
1084
+ const subjectKind = input.subjectKind ?? 'automation-run';
1085
+ const subjectRef = input.subjectRef ?? input.runId;
1086
+ if ((input.subjectKind !== undefined && input.subjectKind !== 'automation-run'
1087
+ && input.subjectKind !== 'foreground-turn' && input.subjectKind !== 'goal-outcome')
1088
+ || (input.subjectRef !== undefined && input.subjectRef.trim() === '')) {
1089
+ throw new AssistantEvaluationError('forbidden', 'delivery Evaluation subject is invalid');
1090
+ }
1091
+ const command = input.ownerCommand;
1092
+ if (command !== undefined && ((command.action === 'withdraw') !== (input.objectiveStatus === 'unknown')
1093
+ || (command.action === 'initial' && (command.expectedVersion !== undefined || command.previousStatus !== undefined))
1094
+ || !['initial', 'correct', 'withdraw'].includes(command.action)
1095
+ || !Number.isSafeInteger(command.principalVersion) || command.principalVersion < 1
1096
+ || (command.action !== 'initial' && (!Number.isSafeInteger(command.expectedVersion)
1097
+ || command.expectedVersion < 1 || !['achieved', 'partial', 'not-achieved', 'unknown'].includes(command.previousStatus))))) {
1098
+ throw new AssistantEvaluationError('forbidden', 'owner revision precondition is invalid');
1099
+ }
1100
+ if (subjectKind === 'goal-outcome' && (input.subjectRef === undefined
1101
+ || subjectRef !== input.runId || command === undefined
1102
+ || typeof input.goalOutcomeCapability !== 'object' || input.goalOutcomeCapability === null)) {
1103
+ throw new AssistantEvaluationError('forbidden', 'goal outcome owner capability is invalid');
1104
+ }
759
1105
  return Object.freeze({
1106
+ ...(subjectKind === 'goal-outcome'
1107
+ ? { goalOutcomeCapability: input.goalOutcomeCapability } : {}),
1108
+ ...(input.initialIdempotencyKey === undefined ? {} : { initialIdempotencyKey: hostIdentifier(input.initialIdempotencyKey, 'initial feedback key', 200) }),
1109
+ ...(command === undefined ? {} : { ownerCommand: Object.freeze({ ...command,
1110
+ operationId: hostIdentifier(command.operationId, 'owner operation', 200),
1111
+ principalRecordId: hostIdentifier(command.principalRecordId, 'owner lineage', 1000),
1112
+ }) }),
760
1113
  scope: Object.freeze({ ...canonicalEvaluationScope(input.scope).scope }),
761
1114
  situation: hostIdentifier(input.situation, 'situation', this.config.maxSituationBytes),
1115
+ ...(input.subjectKind === undefined ? {} : { subjectKind }),
1116
+ ...(input.subjectRef === undefined ? {} : { subjectRef: hostIdentifier(subjectRef, 'subjectRef', 1_000) }),
762
1117
  runId: hostIdentifier(input.runId, 'runId', 1_000),
763
1118
  outboxId: hostIdentifier(input.outboxId, 'outboxId', 1_000),
764
1119
  chatId: hostIdentifier(input.chatId, 'chatId', 1_000),
@@ -769,11 +1124,203 @@ export class AssistantEvaluationService extends Service {
769
1124
  idempotencyKey: hostIdentifier(input.idempotencyKey, 'idempotencyKey', 200),
770
1125
  });
771
1126
  }
772
- appendTrusted(input) {
773
- const outcome = this.store.append(input);
774
- void this.reconcileProjections({ limit: 1 }).catch(() => { });
1127
+ resolveGoalOutcomeOwnerProof(claims) {
1128
+ const goals = this.ctx.get('assistantGoals', false);
1129
+ if (!isTrustedGoalsOutcomeOwnerResolver(goals)) {
1130
+ throw new AssistantEvaluationError('forbidden', 'current Goals owner capability resolver is unavailable');
1131
+ }
1132
+ let value;
1133
+ try {
1134
+ value = goals.resolveOwnerGoalOutcomeFeedbackTarget(claims.goalOutcomeCapability);
1135
+ }
1136
+ catch {
1137
+ throw new AssistantEvaluationError('forbidden', 'Goals owner capability is stale or invalid');
1138
+ }
1139
+ const proof = recordValue(value);
1140
+ const locator = recordValue(proof?.locator);
1141
+ const goal = recordValue(proof?.goal);
1142
+ const profile = recordValue(proof?.profile);
1143
+ const contract = recordValue(proof?.contract);
1144
+ const receipt = recordValue(proof?.receipt);
1145
+ const digestPattern = /^[a-f\d]{64}$/u;
1146
+ const exactKeys = (input, keys) => {
1147
+ if (input === undefined || (Object.getPrototypeOf(input) !== Object.prototype
1148
+ && Object.getPrototypeOf(input) !== null) || Object.getOwnPropertySymbols(input).length !== 0
1149
+ || Object.getOwnPropertyNames(input).sort().join(',') !== [...keys].sort().join(','))
1150
+ return false;
1151
+ return Object.values(Object.getOwnPropertyDescriptors(input))
1152
+ .every(descriptor => descriptor.enumerable && 'value' in descriptor);
1153
+ };
1154
+ if (!exactKeys(proof, ['protocol', 'locator', 'goal', 'runId', 'profile', 'contract', 'receipt', 'proofDigest'])
1155
+ || !exactKeys(locator, ['protocol', 'ownerRouteId', 'principalId', 'principalRecordId', 'principalVersion', 'workspace', 'preset', 'bindingId', 'bindingVersion', 'bindingGeneration', 'sessionId', 'goalId', 'assessmentId'])
1156
+ || !exactKeys(goal, ['definitionVersion', 'definitionDigest', 'nativeGoalId', 'phase'])
1157
+ || !exactKeys(profile, ['id', 'version', 'digest'])
1158
+ || !exactKeys(contract, ['id', 'digest'])
1159
+ || !exactKeys(receipt, ['id', 'digest', 'objectiveStatus', 'completedAt', 'validUntil'])
1160
+ || proof.protocol !== 'assistant-goals/owner-goal-outcome-feedback/v1'
1161
+ || locator.protocol !== 'assistant-goals/owner-goal-outcome-locator/v1'
1162
+ || !Number.isSafeInteger(locator.principalVersion) || locator.principalVersion < 1
1163
+ || !Number.isSafeInteger(locator.bindingVersion) || locator.bindingVersion < 1
1164
+ || !Number.isSafeInteger(locator.bindingGeneration) || locator.bindingGeneration < 1
1165
+ || !Number.isSafeInteger(goal.definitionVersion) || goal.definitionVersion < 1
1166
+ || (goal.phase !== 'complete' && goal.phase !== 'blocked')
1167
+ || !Number.isSafeInteger(profile.version) || profile.version < 1
1168
+ || !Number.isSafeInteger(receipt.completedAt) || receipt.completedAt < 0
1169
+ || !Number.isSafeInteger(receipt.validUntil)
1170
+ || receipt.validUntil <= receipt.completedAt
1171
+ || (receipt.objectiveStatus !== 'achieved' && receipt.objectiveStatus !== 'not-achieved')
1172
+ || ![goal.definitionDigest, profile.digest, contract.digest, receipt.digest, proof.proofDigest]
1173
+ .every(item => typeof item === 'string' && digestPattern.test(item))
1174
+ || ![locator.ownerRouteId, locator.principalId, locator.principalRecordId, locator.workspace,
1175
+ locator.preset, locator.bindingId, locator.sessionId, locator.goalId, locator.assessmentId,
1176
+ goal.nativeGoalId, proof.runId, profile.id, contract.id, receipt.id]
1177
+ .every(item => exactHostIdentifier(item, 'Goals owner proof identity', 4_096))
1178
+ || acceptanceDigest({
1179
+ protocol: proof.protocol, locator, goal, runId: proof.runId, profile, contract, receipt,
1180
+ }) !== proof.proofDigest) {
1181
+ throw new AssistantEvaluationError('forbidden', 'Goals owner proof is invalid');
1182
+ }
1183
+ const valid = value;
1184
+ const expectedSituation = `goal:${valid.locator.goalId}:definition:${valid.goal.definitionVersion}`;
1185
+ if (claims.subjectKind !== 'goal-outcome' || claims.subjectRef !== valid.locator.assessmentId
1186
+ || claims.runId !== valid.locator.assessmentId || claims.scope.workspace !== valid.locator.workspace
1187
+ || claims.scope.preset !== valid.locator.preset || claims.situation !== expectedSituation
1188
+ || claims.principalId !== valid.locator.principalId || claims.bindingId !== valid.locator.bindingId
1189
+ || claims.ownerCommand === undefined
1190
+ || claims.ownerCommand.principalRecordId !== valid.locator.principalRecordId
1191
+ || claims.ownerCommand.principalVersion !== valid.locator.principalVersion) {
1192
+ throw new AssistantEvaluationError('forbidden', 'Goals owner proof does not match delivery claims');
1193
+ }
1194
+ const verifier = this.ctx.get('assistantVerifier', false);
1195
+ if (!isTrustedVerifierTaskInspector(verifier)) {
1196
+ throw new AssistantEvaluationError('forbidden', 'current Verifier goal outcome proof is unavailable');
1197
+ }
1198
+ const expectedVerifierBinding = this.verifierBinding;
1199
+ let inspectedVerifierGeneration;
1200
+ try {
1201
+ inspectedVerifierGeneration = hostIdentifier(verifier.trustedVerificationProducerGeneration(), 'trusted verifier generation', 200);
1202
+ }
1203
+ catch {
1204
+ throw new AssistantEvaluationError('forbidden', 'current Verifier goal outcome proof is unavailable');
1205
+ }
1206
+ if (expectedVerifierBinding === undefined
1207
+ || serviceIdentity(verifier) !== serviceIdentity(expectedVerifierBinding.producer)
1208
+ || inspectedVerifierGeneration !== expectedVerifierBinding.generation) {
1209
+ throw new AssistantEvaluationError('forbidden', 'current Verifier goal outcome proof is unavailable');
1210
+ }
1211
+ let accepted;
1212
+ try {
1213
+ accepted = verifier.inspectAcceptedTask(valid.contract.id);
1214
+ }
1215
+ catch {
1216
+ throw new AssistantEvaluationError('forbidden', 'current Verifier goal outcome proof is unavailable');
1217
+ }
1218
+ let acceptedContract;
1219
+ let acceptedReceipt;
1220
+ try {
1221
+ acceptedContract = validateTaskAcceptanceContract(accepted?.contract);
1222
+ acceptedReceipt = validateTaskVerificationReceipt(acceptedContract, accepted?.receipt);
1223
+ }
1224
+ catch {
1225
+ throw new AssistantEvaluationError('forbidden', 'current Verifier goal outcome proof is invalid');
1226
+ }
1227
+ const execution = recordValue(accepted?.execution);
1228
+ if (accepted?.state !== 'done' || acceptedContract.task.kind !== 'goal-outcome'
1229
+ || !exactKeys(execution, ['status', 'quiescent', 'completedAt', 'executionRef'])
1230
+ || !['succeeded', 'failed', 'timed-out', 'cancelled', 'unknown'].includes(execution.status)
1231
+ || execution.quiescent !== true || !Number.isSafeInteger(execution.completedAt)
1232
+ || execution.completedAt < acceptedContract.issuedAt
1233
+ || acceptedReceipt.startedAt < execution.completedAt
1234
+ || acceptedReceipt.completedAt < acceptedReceipt.startedAt
1235
+ || acceptedReceipt.completedAt > this.now()
1236
+ || execution.executionRef !== acceptedContract.task.ref
1237
+ || acceptedContract.id !== valid.contract.id || acceptedContract.digest !== valid.contract.digest
1238
+ || acceptedContract.task.ref !== valid.locator.assessmentId
1239
+ || acceptedContract.task.goal.assessmentId !== valid.locator.assessmentId
1240
+ || acceptedContract.task.goal.id !== valid.locator.goalId
1241
+ || acceptedContract.task.goal.definitionVersion !== valid.goal.definitionVersion
1242
+ || acceptedContract.task.goal.definitionDigest !== valid.goal.definitionDigest
1243
+ || acceptedContract.task.goal.sessionId !== valid.locator.sessionId
1244
+ || acceptedContract.task.goal.nativeGoalId !== valid.goal.nativeGoalId
1245
+ || acceptedContract.scope.workspace !== valid.locator.workspace
1246
+ || acceptedContract.scope.preset !== valid.locator.preset
1247
+ || acceptedContract.owner.principalRecordId !== valid.locator.principalRecordId
1248
+ || acceptedContract.owner.principalVersion !== valid.locator.principalVersion
1249
+ || acceptedContract.profile.id !== valid.profile.id
1250
+ || acceptedContract.profile.version !== valid.profile.version
1251
+ || acceptedContract.profile.digest !== valid.profile.digest
1252
+ || acceptedReceipt.id !== valid.receipt.id || acceptedReceipt.digest !== valid.receipt.digest
1253
+ || acceptedReceipt.objectiveStatus !== valid.receipt.objectiveStatus
1254
+ || acceptedReceipt.completedAt !== valid.receipt.completedAt
1255
+ || acceptedReceipt.validUntil !== valid.receipt.validUntil) {
1256
+ throw new AssistantEvaluationError('forbidden', 'Goals owner proof does not match the current Verifier receipt');
1257
+ }
1258
+ const currentGoals = this.ctx.get('assistantGoals', false);
1259
+ try {
1260
+ if (!isTrustedGoalsOutcomeOwnerResolver(currentGoals)
1261
+ || acceptanceDigest(currentGoals.resolveOwnerGoalOutcomeFeedbackTarget(claims.goalOutcomeCapability)) !== acceptanceDigest(valid)) {
1262
+ throw new Error('Goals generation changed');
1263
+ }
1264
+ }
1265
+ catch {
1266
+ throw new AssistantEvaluationError('forbidden', 'Goals owner capability is stale or invalid');
1267
+ }
1268
+ const currentVerifier = this.ctx.get('assistantVerifier', false);
1269
+ try {
1270
+ if (!isTrustedVerifierTaskInspector(currentVerifier)
1271
+ || serviceIdentity(currentVerifier) !== serviceIdentity(expectedVerifierBinding.producer)
1272
+ || hostIdentifier(currentVerifier.trustedVerificationProducerGeneration(), 'trusted verifier generation', 200) !== inspectedVerifierGeneration
1273
+ || this.verifierBinding !== expectedVerifierBinding
1274
+ || expectedVerifierBinding.generation !== inspectedVerifierGeneration) {
1275
+ throw new Error('Verifier generation changed');
1276
+ }
1277
+ }
1278
+ catch {
1279
+ throw new AssistantEvaluationError('forbidden', 'current Verifier goal outcome proof is unavailable');
1280
+ }
1281
+ return Object.freeze({
1282
+ proof: valid,
1283
+ verifierOutcome: Object.freeze({
1284
+ scope: Object.freeze({ ...acceptedContract.scope }),
1285
+ situation: expectedSituation,
1286
+ executionStatus: execution.status,
1287
+ objectiveStatus: acceptedReceipt.objectiveStatus,
1288
+ deliveryStatus: 'not-required',
1289
+ source: Object.freeze({ kind: 'evaluator', id: 'assistant-verifier' }),
1290
+ trust: 'trusted',
1291
+ evidence: Object.freeze([
1292
+ Object.freeze({ kind: 'goal-outcome', ref: acceptedContract.task.ref }),
1293
+ Object.freeze({ kind: 'acceptance-contract', ref: acceptedContract.id, digest: acceptedContract.digest }),
1294
+ Object.freeze({ kind: 'verification-receipt', ref: acceptedReceipt.id, digest: acceptedReceipt.digest }),
1295
+ Object.freeze({ kind: 'execution', ref: execution.executionRef }),
1296
+ ]),
1297
+ metrics: Object.freeze({}),
1298
+ occurredAt: acceptedReceipt.completedAt,
1299
+ idempotencyKey: `assistant-verifier:${acceptedReceipt.id}`,
1300
+ evaluator: Object.freeze({ id: 'assistant-verifier', version: '1' }),
1301
+ }),
1302
+ });
1303
+ }
1304
+ taskChangeListeners = new Set();
1305
+ /** Host lifecycle subscription; consumers must re-read canonical proof and recover on startup. */
1306
+ onTrustedTaskChange(listener) {
1307
+ this.taskChangeListeners.add(listener);
1308
+ return () => { this.taskChangeListeners.delete(listener); };
1309
+ }
1310
+ appendTrusted(input, command) {
1311
+ const outcome = this.store.append(input, command);
1312
+ this.notifyTrustedTaskChange();
775
1313
  return outcome;
776
1314
  }
1315
+ notifyTrustedTaskChange() {
1316
+ for (const listener of this.taskChangeListeners) {
1317
+ try {
1318
+ listener();
1319
+ }
1320
+ catch { /* Consumer startup / dispatch revalidation retries durable evidence. */ }
1321
+ }
1322
+ void this.reconcileProjections({ limit: 1 }).catch(() => { });
1323
+ }
777
1324
  async projectOne(entry) {
778
1325
  const projector = this.projector;
779
1326
  if (projector === undefined)