blun-king-cli 9.1.451 → 9.1.453
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/bin/cognitive-action-checkpoint.cjs +214 -13
- package/bin/goal-completion-evidence-policy.cjs +15 -3
- package/blun.mjs +14 -1
- package/package.json +1 -1
|
@@ -25,13 +25,14 @@ const PROBLEM_FRAME_KEYS = new Set([
|
|
|
25
25
|
const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition', 'dueAt']);
|
|
26
26
|
const EVIDENCE_INPUT_KEYS = new Set([
|
|
27
27
|
'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs', 'toolArgs',
|
|
28
|
-
'resultEvidenceKinds',
|
|
28
|
+
'resultEvidenceKinds', 'resultEvidenceScopes',
|
|
29
29
|
]);
|
|
30
30
|
const REQUIRED_EVIDENCE_INPUT_KEYS = new Set([
|
|
31
31
|
'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
|
|
32
32
|
]);
|
|
33
33
|
const EVIDENCE_DIGEST_RE = /^[a-f0-9]{16}$/u;
|
|
34
34
|
const DECISION_BASIS_RE = /^[a-f0-9]{16}$/u;
|
|
35
|
+
const COMPLETION_CRITERION_REF_RE = /^[a-f0-9]{16}$/u;
|
|
35
36
|
const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/u;
|
|
36
37
|
const ACTION_ONLY_TOOL_NAMES = new Set([
|
|
37
38
|
'CreateGoal', 'CronCreate', 'CronDelete', 'DubVideo', 'Edit', 'EnterPlanMode',
|
|
@@ -51,6 +52,7 @@ const INTEGRITY_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:git\s+(?:diff(?:\s+--chec
|
|
|
51
52
|
const SYNTAX_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:node\s+--check\b)|(?:(?:npx\s+)?tsc\b)|(?:(?:npm|pnpm|yarn)\s+(?:(?:run\s+)?typecheck)\b))/iu;
|
|
52
53
|
const TEST_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:node\s+--test\b)|(?:node\s+(?!--)[^\s;&|]*(?:check|test|verify|validate|lint|gate|probe)[^\s;&|]*\b)|(?:(?:npm|pnpm|yarn)\s+(?:test|(?:run\s+)?(?:test|lint|check|build))\b)|(?:python(?:3)?\s+-m\s+pytest\b)|(?:pytest\b)|(?:go\s+test\b)|(?:cargo\s+test\b)|(?:dotnet\s+test\b)|(?:(?:npx\s+)?(?:eslint|biome\s+check)\b))/iu;
|
|
53
54
|
const RUNTIME_EVIDENCE_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:node\s+(?!--check\b)(?:-e\b|[^\s;&|]+)|(?:python(?:3)?\s+(?!-m\s+pytest\b)[^\s;&|]+)|(?:npm|pnpm|yarn)\s+(?:test|run\b)|pytest\b|go\s+test\b|cargo\s+test\b|dotnet\s+test\b)/iu;
|
|
55
|
+
const EVIDENCE_SCOPE_MARKER = /(?:^|\s)BLUN_EVIDENCE_SCOPE=([A-Za-z0-9._:/\\-]{1,256})(?=\s|$)/gu;
|
|
54
56
|
const MAX_SUCCESSFUL_TOOL_DIGESTS = 32;
|
|
55
57
|
|
|
56
58
|
function bounded(value, field, max = 512) {
|
|
@@ -199,6 +201,31 @@ function successfulVerificationCallDigest(turnId, toolCallId, toolName, kind) {
|
|
|
199
201
|
.slice(0, 16);
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
function normalizedVerificationScope(value, field = 'verificationProof scope') {
|
|
205
|
+
return bounded(value, field, 256);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function successfulVerificationScopeDigest(turnId, toolCallId, toolName, kind, scope) {
|
|
209
|
+
const turn = normalizedTurnId(turnId);
|
|
210
|
+
const callId = bounded(toolCallId, 'verificationProof toolCallId', 256);
|
|
211
|
+
const name = bounded(toolName, 'verificationProof toolName', 128);
|
|
212
|
+
const normalizedKind = normalizedVerificationKind(kind);
|
|
213
|
+
const normalizedScope = normalizedVerificationScope(scope);
|
|
214
|
+
return crypto.createHash('sha256')
|
|
215
|
+
.update(`turn:${turn}\0call:${callId}\0tool:${name}\0kind:${normalizedKind}\0scope:${normalizedScope}`)
|
|
216
|
+
.digest('hex')
|
|
217
|
+
.slice(0, 16);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function completionCriterionRef(value) {
|
|
221
|
+
const criterion = String(value ?? '').trim();
|
|
222
|
+
if (!criterion) throw new TypeError('completion criterion is required');
|
|
223
|
+
return crypto.createHash('sha256')
|
|
224
|
+
.update(`completion-criterion:${criterion}`)
|
|
225
|
+
.digest('hex')
|
|
226
|
+
.slice(0, 16);
|
|
227
|
+
}
|
|
228
|
+
|
|
202
229
|
function isActionOnlyTool(toolName) {
|
|
203
230
|
return ACTION_ONLY_TOOL_NAMES.has(String(toolName ?? '').trim());
|
|
204
231
|
}
|
|
@@ -209,6 +236,43 @@ function verificationCommand(toolArgs) {
|
|
|
209
236
|
return typeof command === 'string' ? command.trim().replace(/\s+/gu, ' ') : '';
|
|
210
237
|
}
|
|
211
238
|
|
|
239
|
+
function toolArgumentScope(toolName, toolArgs) {
|
|
240
|
+
if (!toolArgs || typeof toolArgs !== 'object' || Array.isArray(toolArgs)) return '';
|
|
241
|
+
const name = String(toolName ?? '').trim().toLowerCase();
|
|
242
|
+
const first = (...keys) => {
|
|
243
|
+
for (const key of keys) {
|
|
244
|
+
if (typeof toolArgs[key] === 'string' && toolArgs[key].trim().length > 0) {
|
|
245
|
+
return normalizedVerificationScope(toolArgs[key], 'verification target scope');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return '';
|
|
249
|
+
};
|
|
250
|
+
if (['read', 'readmediafile', 'understandimage', 'understandvideo'].includes(name)) {
|
|
251
|
+
return first('file_path', 'path', 'image_path', 'video_path');
|
|
252
|
+
}
|
|
253
|
+
if (name === 'getmedia') return first('media_id', 'id', 'path');
|
|
254
|
+
if (name === 'taskoutput') return first('task_id', 'id');
|
|
255
|
+
if (name === 'fetchurl') return first('url', 'uri');
|
|
256
|
+
if (name === 'websearch') return first('query', 'q');
|
|
257
|
+
if (name === 'grep' || name === 'glob' || name === 'codebasesearch') {
|
|
258
|
+
return first('path', 'cwd', 'directory', 'root', 'query', 'pattern');
|
|
259
|
+
}
|
|
260
|
+
if (name === 'getgoal') return 'runtime:active-goal';
|
|
261
|
+
if (name === 'cronlist') return 'runtime:cron-jobs';
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function commandEvidenceScopes(toolName, toolArgs, providedScopes) {
|
|
266
|
+
const normalizedName = String(toolName ?? '').trim().toLowerCase();
|
|
267
|
+
if (!COMMAND_TOOL_NAMES.has(normalizedName)) return Object.freeze([]);
|
|
268
|
+
const command = verificationCommand(toolArgs);
|
|
269
|
+
if (command.length < 1 || MUTATING_COMMAND.test(command)) return Object.freeze([]);
|
|
270
|
+
const declared = new Set();
|
|
271
|
+
EVIDENCE_SCOPE_MARKER.lastIndex = 0;
|
|
272
|
+
for (const match of command.matchAll(EVIDENCE_SCOPE_MARKER)) declared.add(match[1]);
|
|
273
|
+
return Object.freeze(providedScopes.filter((scope) => declared.has(scope)));
|
|
274
|
+
}
|
|
275
|
+
|
|
212
276
|
function verificationKindsForToolCall(toolName, toolArgs) {
|
|
213
277
|
const normalizedName = String(toolName ?? '').trim().toLowerCase();
|
|
214
278
|
if (INSPECTION_TOOL_NAMES.has(normalizedName)) return Object.freeze(['inspection']);
|
|
@@ -250,6 +314,17 @@ function normalizedResultEvidenceKinds(value) {
|
|
|
250
314
|
return Object.freeze(kinds);
|
|
251
315
|
}
|
|
252
316
|
|
|
317
|
+
function normalizedResultEvidenceScopes(value) {
|
|
318
|
+
if (value === undefined) return Object.freeze([]);
|
|
319
|
+
const scopes = Array.isArray(value)
|
|
320
|
+
? value.map((item, index) => normalizedVerificationScope(item, `resultEvidenceScopes[${index}]`))
|
|
321
|
+
: [];
|
|
322
|
+
if (!Array.isArray(value) || value.length > 5 || new Set(scopes).size !== scopes.length) {
|
|
323
|
+
throw new TypeError('result evidence scopes are invalid');
|
|
324
|
+
}
|
|
325
|
+
return Object.freeze(scopes);
|
|
326
|
+
}
|
|
327
|
+
|
|
253
328
|
function normalizeSuccessfulToolDigests(value) {
|
|
254
329
|
if (value === undefined) return Object.freeze([]);
|
|
255
330
|
if (!Array.isArray(value) || value.length > MAX_SUCCESSFUL_TOOL_DIGESTS
|
|
@@ -262,13 +337,15 @@ function normalizeSuccessfulToolDigests(value) {
|
|
|
262
337
|
|
|
263
338
|
function normalizeExactVerificationCall(input, receipt, label, options = {}) {
|
|
264
339
|
const requireKind = options.requireKind === true;
|
|
265
|
-
const
|
|
340
|
+
const requireScope = options.requireScope === true;
|
|
341
|
+
const expectedKeys = 3 + (requireKind ? 1 : 0) + (requireScope ? 1 : 0);
|
|
266
342
|
if (!input || typeof input !== 'object' || Array.isArray(input)
|
|
267
343
|
|| Object.keys(input).length !== expectedKeys
|
|
268
344
|
|| !Object.hasOwn(input, 'toolCallId')
|
|
269
345
|
|| !Object.hasOwn(input, 'toolName')
|
|
270
346
|
|| !Object.hasOwn(input, 'claim')
|
|
271
|
-
|| requireKind !== Object.hasOwn(input, 'kind')
|
|
347
|
+
|| requireKind !== Object.hasOwn(input, 'kind')
|
|
348
|
+
|| requireScope !== Object.hasOwn(input, 'scope')) {
|
|
272
349
|
throw new TypeError(`${label} fields are invalid`);
|
|
273
350
|
}
|
|
274
351
|
const toolCallId = bounded(input.toolCallId, `${label} toolCallId`, 256);
|
|
@@ -288,6 +365,16 @@ function normalizeExactVerificationCall(input, receipt, label, options = {}) {
|
|
|
288
365
|
if (!receipt.successfulVerificationCallDigests.includes(typedDigest)) {
|
|
289
366
|
throw new TypeError(`${label} kind must match the successful current-turn verification call`);
|
|
290
367
|
}
|
|
368
|
+
if (requireScope) {
|
|
369
|
+
const scope = normalizedVerificationScope(input.scope, `${label} scope`);
|
|
370
|
+
const scopeDigest = successfulVerificationScopeDigest(
|
|
371
|
+
receipt.turnId, toolCallId, toolName, kind, scope,
|
|
372
|
+
);
|
|
373
|
+
if (!receipt.successfulVerificationScopeDigests.includes(scopeDigest)) {
|
|
374
|
+
throw new TypeError(`${label} scope must match the successful current-turn verification call target`);
|
|
375
|
+
}
|
|
376
|
+
return Object.freeze({ toolCallId, toolName, kind, scope, claim });
|
|
377
|
+
}
|
|
291
378
|
return Object.freeze({ toolCallId, toolName, kind, claim });
|
|
292
379
|
}
|
|
293
380
|
return Object.freeze({ toolCallId, toolName, claim });
|
|
@@ -320,21 +407,78 @@ function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
|
|
|
320
407
|
&& Object.hasOwn(input, 'toolName')
|
|
321
408
|
&& Object.hasOwn(input, 'claim')
|
|
322
409
|
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'claim', 'sharpnessProof'].includes(key));
|
|
323
|
-
const
|
|
410
|
+
const legacyTypedSubjectResult = options.allowLegacy === true
|
|
411
|
+
&& subject === 'result'
|
|
324
412
|
&& keys.length === 5
|
|
325
413
|
&& Object.hasOwn(input, 'toolCallId')
|
|
326
414
|
&& Object.hasOwn(input, 'toolName')
|
|
327
415
|
&& Object.hasOwn(input, 'kind')
|
|
328
416
|
&& Object.hasOwn(input, 'claim');
|
|
329
|
-
const
|
|
417
|
+
const legacyTypedSubjectVerifier = options.allowLegacy === true
|
|
418
|
+
&& subject === 'verifier'
|
|
330
419
|
&& (keys.length === 5 || keys.length === 6)
|
|
331
420
|
&& Object.hasOwn(input, 'toolCallId')
|
|
332
421
|
&& Object.hasOwn(input, 'toolName')
|
|
333
422
|
&& Object.hasOwn(input, 'kind')
|
|
334
423
|
&& Object.hasOwn(input, 'claim')
|
|
335
424
|
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'claim', 'sharpnessProof'].includes(key));
|
|
425
|
+
const legacyScopedResult = options.allowLegacy === true
|
|
426
|
+
&& subject === 'result'
|
|
427
|
+
&& keys.length === 6
|
|
428
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
429
|
+
&& Object.hasOwn(input, 'toolName')
|
|
430
|
+
&& Object.hasOwn(input, 'kind')
|
|
431
|
+
&& Object.hasOwn(input, 'scope')
|
|
432
|
+
&& Object.hasOwn(input, 'claim');
|
|
433
|
+
const legacyScopedVerifier = options.allowLegacy === true
|
|
434
|
+
&& subject === 'verifier'
|
|
435
|
+
&& (keys.length === 6 || keys.length === 7)
|
|
436
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
437
|
+
&& Object.hasOwn(input, 'toolName')
|
|
438
|
+
&& Object.hasOwn(input, 'kind')
|
|
439
|
+
&& Object.hasOwn(input, 'scope')
|
|
440
|
+
&& Object.hasOwn(input, 'claim')
|
|
441
|
+
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'scope', 'claim', 'sharpnessProof'].includes(key));
|
|
442
|
+
const freshResult = subject === 'result'
|
|
443
|
+
&& keys.length === 7
|
|
444
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
445
|
+
&& Object.hasOwn(input, 'toolName')
|
|
446
|
+
&& Object.hasOwn(input, 'kind')
|
|
447
|
+
&& Object.hasOwn(input, 'scope')
|
|
448
|
+
&& Object.hasOwn(input, 'criterion')
|
|
449
|
+
&& Object.hasOwn(input, 'claim');
|
|
450
|
+
const freshVerifier = subject === 'verifier'
|
|
451
|
+
&& (keys.length === 7 || keys.length === 8)
|
|
452
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
453
|
+
&& Object.hasOwn(input, 'toolName')
|
|
454
|
+
&& Object.hasOwn(input, 'kind')
|
|
455
|
+
&& Object.hasOwn(input, 'scope')
|
|
456
|
+
&& Object.hasOwn(input, 'criterion')
|
|
457
|
+
&& Object.hasOwn(input, 'claim')
|
|
458
|
+
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'scope', 'criterion', 'claim', 'sharpnessProof'].includes(key));
|
|
459
|
+
const replayResult = options.allowLegacy === true
|
|
460
|
+
&& subject === 'result'
|
|
461
|
+
&& keys.length === 7
|
|
462
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
463
|
+
&& Object.hasOwn(input, 'toolName')
|
|
464
|
+
&& Object.hasOwn(input, 'kind')
|
|
465
|
+
&& Object.hasOwn(input, 'scope')
|
|
466
|
+
&& Object.hasOwn(input, 'criterionRef')
|
|
467
|
+
&& Object.hasOwn(input, 'claim');
|
|
468
|
+
const replayVerifier = options.allowLegacy === true
|
|
469
|
+
&& subject === 'verifier'
|
|
470
|
+
&& (keys.length === 7 || keys.length === 8)
|
|
471
|
+
&& Object.hasOwn(input, 'toolCallId')
|
|
472
|
+
&& Object.hasOwn(input, 'toolName')
|
|
473
|
+
&& Object.hasOwn(input, 'kind')
|
|
474
|
+
&& Object.hasOwn(input, 'scope')
|
|
475
|
+
&& Object.hasOwn(input, 'criterionRef')
|
|
476
|
+
&& Object.hasOwn(input, 'claim')
|
|
477
|
+
&& keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'scope', 'criterionRef', 'claim', 'sharpnessProof'].includes(key));
|
|
336
478
|
if (!legacyName && !legacyExact && !legacySubjectResult && !legacySubjectVerifier
|
|
337
|
-
&& !
|
|
479
|
+
&& !legacyTypedSubjectResult && !legacyTypedSubjectVerifier
|
|
480
|
+
&& !legacyScopedResult && !legacyScopedVerifier
|
|
481
|
+
&& !freshResult && !freshVerifier && !replayResult && !replayVerifier) {
|
|
338
482
|
throw new TypeError('verificationProof fields are invalid');
|
|
339
483
|
}
|
|
340
484
|
const receipt = normalizeActionEvidenceReceipt(evidenceReceipt);
|
|
@@ -350,26 +494,49 @@ function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
|
|
|
350
494
|
return Object.freeze({ toolName, claim });
|
|
351
495
|
}
|
|
352
496
|
const legacySubject = legacySubjectResult || legacySubjectVerifier;
|
|
497
|
+
const legacyTypedSubject = legacyTypedSubjectResult || legacyTypedSubjectVerifier;
|
|
353
498
|
const primary = normalizeExactVerificationCall(legacyExact ? input : {
|
|
354
499
|
toolCallId: input.toolCallId,
|
|
355
500
|
toolName: input.toolName,
|
|
356
501
|
...(legacySubject ? {} : { kind: input.kind }),
|
|
502
|
+
...(legacyScopedResult || legacyScopedVerifier || freshResult || freshVerifier
|
|
503
|
+
|| replayResult || replayVerifier ? { scope: input.scope } : {}),
|
|
357
504
|
claim: input.claim,
|
|
358
|
-
}, receipt, 'verificationProof', {
|
|
505
|
+
}, receipt, 'verificationProof', {
|
|
506
|
+
requireKind: !legacyExact && !legacySubject,
|
|
507
|
+
requireScope: legacyScopedResult || legacyScopedVerifier || freshResult || freshVerifier
|
|
508
|
+
|| replayResult || replayVerifier,
|
|
509
|
+
});
|
|
359
510
|
if (legacyExact) return primary;
|
|
360
511
|
if (!VERIFICATION_SUBJECTS.has(subject)) throw new TypeError('verificationProof subject is invalid');
|
|
361
|
-
|
|
512
|
+
const criterionRef = freshResult || freshVerifier
|
|
513
|
+
? completionCriterionRef(input.criterion)
|
|
514
|
+
: replayResult || replayVerifier
|
|
515
|
+
? String(input.criterionRef ?? '')
|
|
516
|
+
: undefined;
|
|
517
|
+
if (criterionRef !== undefined && !COMPLETION_CRITERION_REF_RE.test(criterionRef)) {
|
|
518
|
+
throw new TypeError('verificationProof criterionRef is invalid');
|
|
519
|
+
}
|
|
520
|
+
if (subject === 'result') {
|
|
521
|
+
return Object.freeze({ subject, ...primary, ...(criterionRef === undefined ? {} : { criterionRef }) });
|
|
522
|
+
}
|
|
362
523
|
if (input.sharpnessProof === undefined) {
|
|
363
524
|
throw new TypeError('verifier proof requires a sharpnessProof');
|
|
364
525
|
}
|
|
365
526
|
const sharpnessProof = normalizeExactVerificationCall(input.sharpnessProof, receipt, 'sharpnessProof', {
|
|
366
527
|
requireKind: !legacySubject,
|
|
528
|
+
requireScope: !legacySubject && !legacyTypedSubject,
|
|
367
529
|
});
|
|
368
530
|
if (sharpnessProof.toolCallId === primary.toolCallId
|
|
369
531
|
&& sharpnessProof.toolName === primary.toolName) {
|
|
370
532
|
throw new TypeError('sharpnessProof must name a distinct verification call');
|
|
371
533
|
}
|
|
372
|
-
return Object.freeze({
|
|
534
|
+
return Object.freeze({
|
|
535
|
+
subject,
|
|
536
|
+
...primary,
|
|
537
|
+
...(criterionRef === undefined ? {} : { criterionRef }),
|
|
538
|
+
sharpnessProof,
|
|
539
|
+
});
|
|
373
540
|
}
|
|
374
541
|
|
|
375
542
|
function emptyActionEvidenceReceipt(turnId) {
|
|
@@ -382,6 +549,7 @@ function emptyActionEvidenceReceipt(turnId) {
|
|
|
382
549
|
successfulToolDigests: Object.freeze([]),
|
|
383
550
|
successfulToolCallDigests: Object.freeze([]),
|
|
384
551
|
successfulVerificationCallDigests: Object.freeze([]),
|
|
552
|
+
successfulVerificationScopeDigests: Object.freeze([]),
|
|
385
553
|
digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
|
|
386
554
|
});
|
|
387
555
|
}
|
|
@@ -391,7 +559,7 @@ function normalizeActionEvidenceReceipt(input) {
|
|
|
391
559
|
const requiredKeys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
|
|
392
560
|
const allowedKeys = new Set([
|
|
393
561
|
...requiredKeys, 'successfulToolDigests', 'successfulToolCallDigests',
|
|
394
|
-
'successfulVerificationCallDigests',
|
|
562
|
+
'successfulVerificationCallDigests', 'successfulVerificationScopeDigests',
|
|
395
563
|
]);
|
|
396
564
|
if (!Object.keys(input).every((key) => allowedKeys.has(key))
|
|
397
565
|
|| ![...requiredKeys].every((key) => Object.hasOwn(input, key))) {
|
|
@@ -407,6 +575,9 @@ function normalizeActionEvidenceReceipt(input) {
|
|
|
407
575
|
successfulVerificationCallDigests: normalizeSuccessfulToolDigests(
|
|
408
576
|
input.successfulVerificationCallDigests,
|
|
409
577
|
),
|
|
578
|
+
successfulVerificationScopeDigests: normalizeSuccessfulToolDigests(
|
|
579
|
+
input.successfulVerificationScopeDigests,
|
|
580
|
+
),
|
|
410
581
|
digest: String(input.digest ?? ''),
|
|
411
582
|
};
|
|
412
583
|
if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
|
|
@@ -440,6 +611,7 @@ function advanceActionEvidenceReceipt(current, input) {
|
|
|
440
611
|
const successfulToolDigests = [...prior.successfulToolDigests];
|
|
441
612
|
const successfulToolCallDigests = [...prior.successfulToolCallDigests];
|
|
442
613
|
const successfulVerificationCallDigests = [...prior.successfulVerificationCallDigests];
|
|
614
|
+
const successfulVerificationScopeDigests = [...prior.successfulVerificationScopeDigests];
|
|
443
615
|
const toolDigest = successfulToolDigest(toolName);
|
|
444
616
|
if (successful && !successfulToolDigests.includes(toolDigest)) {
|
|
445
617
|
successfulToolDigests.push(toolDigest);
|
|
@@ -452,6 +624,15 @@ function advanceActionEvidenceReceipt(current, input) {
|
|
|
452
624
|
const verificationKinds = successful
|
|
453
625
|
? [...new Set([...verificationKindsForToolCall(toolName, input.toolArgs), ...explicitResultKinds])]
|
|
454
626
|
: [];
|
|
627
|
+
const argumentScope = successful ? toolArgumentScope(toolName, input.toolArgs) : '';
|
|
628
|
+
const providedResultScopes = normalizedResultEvidenceScopes(input.resultEvidenceScopes);
|
|
629
|
+
const explicitResultScopes = successful
|
|
630
|
+
? commandEvidenceScopes(toolName, input.toolArgs, providedResultScopes)
|
|
631
|
+
: [];
|
|
632
|
+
const verificationScopes = [...new Set([
|
|
633
|
+
...(argumentScope ? [argumentScope] : []),
|
|
634
|
+
...explicitResultScopes,
|
|
635
|
+
])];
|
|
455
636
|
const verificationCall = verificationKinds.length > 0;
|
|
456
637
|
if (verificationCall) {
|
|
457
638
|
const callDigest = successfulToolCallDigest(turnId, toolCallId, toolName);
|
|
@@ -465,11 +646,22 @@ function advanceActionEvidenceReceipt(current, input) {
|
|
|
465
646
|
if (successfulVerificationCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
|
|
466
647
|
successfulVerificationCallDigests.shift();
|
|
467
648
|
}
|
|
649
|
+
for (const scope of verificationScopes) {
|
|
650
|
+
const scopeDigest = successfulVerificationScopeDigest(
|
|
651
|
+
turnId, toolCallId, toolName, kind, scope,
|
|
652
|
+
);
|
|
653
|
+
if (!successfulVerificationScopeDigests.includes(scopeDigest)) {
|
|
654
|
+
successfulVerificationScopeDigests.push(scopeDigest);
|
|
655
|
+
}
|
|
656
|
+
if (successfulVerificationScopeDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
|
|
657
|
+
successfulVerificationScopeDigests.shift();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
468
660
|
}
|
|
469
661
|
}
|
|
470
662
|
const digest = crypto.createHash('sha256').update([
|
|
471
663
|
prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
|
|
472
|
-
verificationKinds.join(','),
|
|
664
|
+
verificationKinds.join(','), verificationScopes.join(','),
|
|
473
665
|
].join('\0')).digest('hex').slice(0, 16);
|
|
474
666
|
return Object.freeze({
|
|
475
667
|
turnId,
|
|
@@ -479,6 +671,7 @@ function advanceActionEvidenceReceipt(current, input) {
|
|
|
479
671
|
successfulToolDigests: Object.freeze(successfulToolDigests),
|
|
480
672
|
successfulToolCallDigests: Object.freeze(successfulToolCallDigests),
|
|
481
673
|
successfulVerificationCallDigests: Object.freeze(successfulVerificationCallDigests),
|
|
674
|
+
successfulVerificationScopeDigests: Object.freeze(successfulVerificationScopeDigests),
|
|
482
675
|
digest,
|
|
483
676
|
});
|
|
484
677
|
}
|
|
@@ -598,11 +791,18 @@ function projectActionCheckpoint(checkpoint) {
|
|
|
598
791
|
const kind = value.verificationProof.kind === undefined
|
|
599
792
|
? ''
|
|
600
793
|
: ` [${value.verificationProof.kind}]`;
|
|
601
|
-
|
|
794
|
+
const scope = value.verificationProof.scope === undefined
|
|
795
|
+
? ''
|
|
796
|
+
: ` [scope: ${value.verificationProof.scope}]`;
|
|
797
|
+
const criterion = value.verificationProof.criterionRef === undefined
|
|
798
|
+
? ''
|
|
799
|
+
: ` [criterion: ${value.verificationProof.criterionRef}]`;
|
|
800
|
+
lines.push(`Verification proof${subject}${kind}${scope}${criterion}: ${call} - ${value.verificationProof.claim}`);
|
|
602
801
|
if (value.verificationProof.sharpnessProof !== undefined) {
|
|
603
802
|
const sharpness = value.verificationProof.sharpnessProof;
|
|
604
803
|
const sharpnessKind = sharpness.kind === undefined ? '' : ` [${sharpness.kind}]`;
|
|
605
|
-
|
|
804
|
+
const sharpnessScope = sharpness.scope === undefined ? '' : ` [scope: ${sharpness.scope}]`;
|
|
805
|
+
lines.push(`Sharpness proof${sharpnessKind}${sharpnessScope}: ${sharpness.toolName} call ${sharpness.toolCallId} - ${sharpness.claim}`);
|
|
606
806
|
}
|
|
607
807
|
}
|
|
608
808
|
if (value.nextTrigger !== undefined) {
|
|
@@ -638,6 +838,7 @@ module.exports = {
|
|
|
638
838
|
advanceActionEvidenceReceipt,
|
|
639
839
|
assertActionCheckpointEvidenceBasis,
|
|
640
840
|
assertActionCheckpointRevision,
|
|
841
|
+
completionCriterionRef,
|
|
641
842
|
emptyActionEvidenceReceipt,
|
|
642
843
|
normalizeActionCheckpoint,
|
|
643
844
|
normalizeVerificationProof,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
+
completionCriterionRef,
|
|
4
5
|
normalizeVerificationProof,
|
|
5
6
|
} = require('./cognitive-action-checkpoint.cjs');
|
|
6
7
|
|
|
@@ -14,12 +15,19 @@ function successfulRuntimeEvidence(checkpoint) {
|
|
|
14
15
|
return Number.isSafeInteger(successfulTools) && successfulTools > 0;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
function verificationProofGaps(checkpoint) {
|
|
18
|
+
function verificationProofGaps(checkpoint, criterion) {
|
|
18
19
|
if (!checkpoint?.verificationProof) {
|
|
19
20
|
return ['Bind the completion claim to a successful verification tool from the checkpoint turn.'];
|
|
20
21
|
}
|
|
21
22
|
try {
|
|
22
|
-
|
|
23
|
+
const proof = normalizeVerificationProof(
|
|
24
|
+
checkpoint.verificationProof,
|
|
25
|
+
checkpoint.evidenceReceipt,
|
|
26
|
+
{ allowLegacy: true },
|
|
27
|
+
);
|
|
28
|
+
if (proof.criterionRef !== completionCriterionRef(criterion)) {
|
|
29
|
+
return ['Bind the completion proof to the active completion criterion.'];
|
|
30
|
+
}
|
|
23
31
|
return [];
|
|
24
32
|
} catch (error) {
|
|
25
33
|
if (/verifier proof requires a sharpnessProof|sharpnessProof must name a distinct verification call/u
|
|
@@ -34,6 +42,10 @@ function verificationProofGaps(checkpoint) {
|
|
|
34
42
|
.test(String(error?.message ?? ''))) {
|
|
35
43
|
return ['The completion proof kind exceeds what its exact verification call measured.'];
|
|
36
44
|
}
|
|
45
|
+
if (/(?:verificationProof|sharpnessProof) scope must match the successful current-turn verification call target/u
|
|
46
|
+
.test(String(error?.message ?? ''))) {
|
|
47
|
+
return ['Bind the completion proof scope to the exact target measured by its successful current-turn verification call.'];
|
|
48
|
+
}
|
|
37
49
|
if (/action-only tool/u.test(String(error?.message ?? ''))) {
|
|
38
50
|
return ['The completion proof names an action-only tool, not a verification tool.'];
|
|
39
51
|
}
|
|
@@ -82,7 +94,7 @@ function evaluateGoalCompletionEvidence(goal) {
|
|
|
82
94
|
&& checkpoint.evidenceBasis === 'runtime_tool'
|
|
83
95
|
&& checkpoint.epistemicState === 'verified'
|
|
84
96
|
&& hasRuntimeEvidence) {
|
|
85
|
-
gaps.push(...verificationProofGaps(checkpoint));
|
|
97
|
+
gaps.push(...verificationProofGaps(checkpoint, goal.completionCriterion));
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
return {
|
package/blun.mjs
CHANGED
|
@@ -260353,11 +260353,14 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
|
|
|
260353
260353
|
toolCallId: string().min(1).max(256),
|
|
260354
260354
|
toolName: string().min(1).max(128),
|
|
260355
260355
|
kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
|
|
260356
|
+
scope: string().min(1).max(256),
|
|
260357
|
+
criterion: string().min(1),
|
|
260356
260358
|
claim: string().min(1).max(512),
|
|
260357
260359
|
sharpnessProof: object({
|
|
260358
260360
|
toolCallId: string().min(1).max(256),
|
|
260359
260361
|
toolName: string().min(1).max(128),
|
|
260360
260362
|
kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
|
|
260363
|
+
scope: string().min(1).max(256),
|
|
260361
260364
|
claim: string().min(1).max(512)
|
|
260362
260365
|
}).strict().optional()
|
|
260363
260366
|
}).strict().superRefine((value, ctx) => {
|
|
@@ -261698,6 +261701,13 @@ function explicitToolResultEvidenceKinds(result) {
|
|
|
261698
261701
|
const text = toolResultText(result);
|
|
261699
261702
|
return /(?:^|\s)BLUN_EVIDENCE_KIND=reachability(?:\s|$)/u.test(text) ? ["reachability"] : [];
|
|
261700
261703
|
}
|
|
261704
|
+
function explicitToolResultEvidenceScopes(result) {
|
|
261705
|
+
const text = toolResultText(result);
|
|
261706
|
+
return [...text.matchAll(/(?:^|\s)BLUN_EVIDENCE_SCOPE=([A-Za-z0-9._:/\\-]{1,256})(?=\s|$)/gu)]
|
|
261707
|
+
.map((match) => match[1])
|
|
261708
|
+
.filter((scope, index, scopes) => scopes.indexOf(scope) === index)
|
|
261709
|
+
.slice(0, 5);
|
|
261710
|
+
}
|
|
261701
261711
|
function abandonedToolResultOutput(ended) {
|
|
261702
261712
|
return `Tool call did not complete: ${ended.reason === "cancelled" ? "the turn was cancelled" : ended.reason === "failed" ? `the turn failed${ended.error !== void 0 ? ` (${ended.error.message})` : ""}` : "the turn ended"} before its result was recorded. Do not assume the tool completed successfully.`;
|
|
261703
261713
|
}
|
|
@@ -262801,7 +262811,8 @@ var init_turn = __esmMin((() => {
|
|
|
262801
262811
|
outcome,
|
|
262802
262812
|
durationMs: Date.now() - started.startedAt,
|
|
262803
262813
|
toolArgs: started.args,
|
|
262804
|
-
resultEvidenceKinds: explicitToolResultEvidenceKinds(event.result)
|
|
262814
|
+
resultEvidenceKinds: explicitToolResultEvidenceKinds(event.result),
|
|
262815
|
+
resultEvidenceScopes: explicitToolResultEvidenceScopes(event.result)
|
|
262805
262816
|
});
|
|
262806
262817
|
this.agent.telemetry.track("tool_call", properties);
|
|
262807
262818
|
this.agent.feedRootMissionContract("result", {
|
|
@@ -262907,7 +262918,9 @@ var update_goal_default;
|
|
|
262907
262918
|
var init_update_goal$1 = __esmMin((() => {
|
|
262908
262919
|
update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, exact `nextTrigger`, and an explicit evidence basis. Persist the exact `nextTrigger` that releases `nextAction`: use `immediate` outside the `wait` phase; while waiting, name the external event, time, dependency, or user decision instead of pretending work can continue. A `time` trigger must include the exact ISO timestamp in `dueAt`; no other trigger kind may include `dueAt`. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
|
|
262909
262920
|
update_goal_default += "\nBefore completing a goal with a criterion, bind the verified claim to the exact successful current-turn verification call in `verificationProof`, including its `toolCallId`. A write, edit, copy, deploy, or other action is not proof that the changed behavior works, even when it shares a mixed-use tool such as `Bash` with tests. Use `subject: result` for a result, report, measurement, or download. Use `subject: verifier` only when the new or changed test, gate, harness, or detector itself is the completion subject; then bind `sharpnessProof` to a separate successful current-turn counterexample or mutation call. Do not require a red probe for a normal report or measurement.\n";
|
|
262921
|
+
update_goal_default += "\nCopy the active goal's exact `completionCriterion` into `verificationProof.criterion`. The runtime stores only its bounded reference and refuses completion if the proof belongs to a different or superseded completion criterion.\n";
|
|
262910
262922
|
update_goal_default += "\nSet the proof `kind` to the exact capability of that call: `inspection` reads or searches, `integrity` compares bytes or hashes, `syntax` parses or type-checks, and `test` runs assertions. None of these alone proves a stronger kind. Use `reachability` only for a successful runtime probe that actually invokes the changed path and emits the exact marker `BLUN_EVIDENCE_KIND=reachability` after its assertions; loading a module without reaching the changed path is not reachability.\n";
|
|
262923
|
+
update_goal_default += "\nBind each proof `scope` to the exact target measured by that successful call, never to a free-text claim or intended file. Read and search tools derive scope from their target arguments. For shell or command tools, include the same safe token `BLUN_EVIDENCE_SCOPE=<scope>` in the launched non-mutating verification command and emit it only after that exact target succeeds; the runtime requires both sides.\n";
|
|
262911
262924
|
update_goal_default += "\nFor a non-trivial or unfamiliar problem, preserve `problemFrame` with the success criterion, missing knowledge, bounded candidate actions, selected action and reason, support choice, risk, and reversibility. The selected action must match one candidate. Bind each selected action to the projected durable facts or assumptions it relies on by copying their explicit refs into `decisionBasis`. A stale or unknown decision basis requires replanning before execution. Problem framing is descriptive state and never grants permission.\n";
|
|
262912
262925
|
}));
|
|
262913
262926
|
//#endregion
|