@dsh-enhanced/assistant-evaluation 0.1.24 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -3
- package/bin/dsh-benchmark.js +21 -0
- package/lib/benchmark/cli.d.ts +6 -0
- package/lib/benchmark/cli.d.ts.map +1 -0
- package/lib/benchmark/cli.js +332 -0
- package/lib/benchmark/cli.js.map +1 -0
- package/lib/benchmark/corpus.d.ts +28 -0
- package/lib/benchmark/corpus.d.ts.map +1 -0
- package/lib/benchmark/corpus.js +173 -0
- package/lib/benchmark/corpus.js.map +1 -0
- package/lib/benchmark/deepseek.d.ts +15 -0
- package/lib/benchmark/deepseek.d.ts.map +1 -0
- package/lib/benchmark/deepseek.js +143 -0
- package/lib/benchmark/deepseek.js.map +1 -0
- package/lib/benchmark/holdout-evidence.d.ts +126 -0
- package/lib/benchmark/holdout-evidence.d.ts.map +1 -0
- package/lib/benchmark/holdout-evidence.js +466 -0
- package/lib/benchmark/holdout-evidence.js.map +1 -0
- package/lib/benchmark/holdout-protocol.d.ts +68 -0
- package/lib/benchmark/holdout-protocol.d.ts.map +1 -0
- package/lib/benchmark/holdout-protocol.js +271 -0
- package/lib/benchmark/holdout-protocol.js.map +1 -0
- package/lib/benchmark/holdout-provider.d.ts +26 -0
- package/lib/benchmark/holdout-provider.d.ts.map +1 -0
- package/lib/benchmark/holdout-provider.js +382 -0
- package/lib/benchmark/holdout-provider.js.map +1 -0
- package/lib/benchmark/holdout.d.ts +83 -0
- package/lib/benchmark/holdout.d.ts.map +1 -0
- package/lib/benchmark/holdout.js +391 -0
- package/lib/benchmark/holdout.js.map +1 -0
- package/lib/benchmark/index.d.ts +9 -0
- package/lib/benchmark/index.d.ts.map +1 -0
- package/lib/benchmark/index.js +7 -0
- package/lib/benchmark/index.js.map +1 -0
- package/lib/benchmark/memory-corpus.d.ts +55 -0
- package/lib/benchmark/memory-corpus.d.ts.map +1 -0
- package/lib/benchmark/memory-corpus.js +153 -0
- package/lib/benchmark/memory-corpus.js.map +1 -0
- package/lib/benchmark/memory-runtime.d.ts +63 -0
- package/lib/benchmark/memory-runtime.d.ts.map +1 -0
- package/lib/benchmark/memory-runtime.js +75 -0
- package/lib/benchmark/memory-runtime.js.map +1 -0
- package/lib/benchmark/native.d.ts +46 -0
- package/lib/benchmark/native.d.ts.map +1 -0
- package/lib/benchmark/native.js +300 -0
- package/lib/benchmark/native.js.map +1 -0
- package/lib/benchmark/report.d.ts +4 -0
- package/lib/benchmark/report.d.ts.map +1 -0
- package/lib/benchmark/report.js +153 -0
- package/lib/benchmark/report.js.map +1 -0
- package/lib/benchmark/runner.d.ts +25 -0
- package/lib/benchmark/runner.d.ts.map +1 -0
- package/lib/benchmark/runner.js +119 -0
- package/lib/benchmark/runner.js.map +1 -0
- package/lib/benchmark/schema.d.ts +20 -0
- package/lib/benchmark/schema.d.ts.map +1 -0
- package/lib/benchmark/schema.js +183 -0
- package/lib/benchmark/schema.js.map +1 -0
- package/lib/benchmark/store.d.ts +18 -0
- package/lib/benchmark/store.d.ts.map +1 -0
- package/lib/benchmark/store.js +321 -0
- package/lib/benchmark/store.js.map +1 -0
- package/lib/benchmark/strategy-capabilities.d.ts +154 -0
- package/lib/benchmark/strategy-capabilities.d.ts.map +1 -0
- package/lib/benchmark/strategy-capabilities.js +300 -0
- package/lib/benchmark/strategy-capabilities.js.map +1 -0
- package/lib/benchmark/strategy-config.d.ts +26 -0
- package/lib/benchmark/strategy-config.d.ts.map +1 -0
- package/lib/benchmark/strategy-config.js +80 -0
- package/lib/benchmark/strategy-config.js.map +1 -0
- package/lib/benchmark/strategy-corpus.d.ts +21 -0
- package/lib/benchmark/strategy-corpus.d.ts.map +1 -0
- package/lib/benchmark/strategy-corpus.js +95 -0
- package/lib/benchmark/strategy-corpus.js.map +1 -0
- package/lib/benchmark/strategy-evidence.d.ts +161 -0
- package/lib/benchmark/strategy-evidence.d.ts.map +1 -0
- package/lib/benchmark/strategy-evidence.js +520 -0
- package/lib/benchmark/strategy-evidence.js.map +1 -0
- package/lib/benchmark/strategy-executor.d.ts +27 -0
- package/lib/benchmark/strategy-executor.d.ts.map +1 -0
- package/lib/benchmark/strategy-executor.js +178 -0
- package/lib/benchmark/strategy-executor.js.map +1 -0
- package/lib/benchmark/strategy-goal-runtime.d.ts +78 -0
- package/lib/benchmark/strategy-goal-runtime.d.ts.map +1 -0
- package/lib/benchmark/strategy-goal-runtime.js +324 -0
- package/lib/benchmark/strategy-goal-runtime.js.map +1 -0
- package/lib/benchmark/strategy-meter.d.ts +62 -0
- package/lib/benchmark/strategy-meter.d.ts.map +1 -0
- package/lib/benchmark/strategy-meter.js +236 -0
- package/lib/benchmark/strategy-meter.js.map +1 -0
- package/lib/benchmark/strategy-owner.d.ts +71 -0
- package/lib/benchmark/strategy-owner.d.ts.map +1 -0
- package/lib/benchmark/strategy-owner.js +282 -0
- package/lib/benchmark/strategy-owner.js.map +1 -0
- package/lib/benchmark/strategy-plan.d.ts +51 -0
- package/lib/benchmark/strategy-plan.d.ts.map +1 -0
- package/lib/benchmark/strategy-plan.js +71 -0
- package/lib/benchmark/strategy-plan.js.map +1 -0
- package/lib/benchmark/strategy-policy.d.ts +16 -0
- package/lib/benchmark/strategy-policy.d.ts.map +1 -0
- package/lib/benchmark/strategy-policy.js +77 -0
- package/lib/benchmark/strategy-policy.js.map +1 -0
- package/lib/benchmark/strategy.d.ts +17 -0
- package/lib/benchmark/strategy.d.ts.map +1 -0
- package/lib/benchmark/strategy.js +21 -0
- package/lib/benchmark/strategy.js.map +1 -0
- package/lib/benchmark/types.d.ts +133 -0
- package/lib/benchmark/types.d.ts.map +1 -0
- package/lib/benchmark/types.js +3 -0
- package/lib/benchmark/types.js.map +1 -0
- package/lib/benchmark/usage.d.ts +36 -0
- package/lib/benchmark/usage.d.ts.map +1 -0
- package/lib/benchmark/usage.js +84 -0
- package/lib/benchmark/usage.js.map +1 -0
- package/lib/service.d.ts +50 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +556 -9
- package/lib/service.js.map +1 -1
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +94 -2
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +27 -5
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +324 -25
- package/lib/store.js.map +1 -1
- package/lib/types.d.ts +114 -5
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- 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 {
|
|
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
|
-
|
|
773
|
-
const
|
|
774
|
-
|
|
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)
|