blun-king-cli 9.1.450 → 9.1.452

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.
@@ -13,6 +13,7 @@ const TRIGGER_KINDS = new Set([
13
13
  'immediate', 'external_event', 'time', 'dependency', 'user_decision',
14
14
  ]);
15
15
  const VERIFICATION_SUBJECTS = new Set(['result', 'verifier']);
16
+ const VERIFICATION_KINDS = new Set(['inspection', 'integrity', 'syntax', 'test', 'reachability']);
16
17
  const MODEL_KEYS = new Set([
17
18
  'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'verificationProof', 'nextTrigger', 'problemFrame', 'updatedAt',
18
19
  ]);
@@ -24,6 +25,7 @@ const PROBLEM_FRAME_KEYS = new Set([
24
25
  const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition', 'dueAt']);
25
26
  const EVIDENCE_INPUT_KEYS = new Set([
26
27
  'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs', 'toolArgs',
28
+ 'resultEvidenceKinds', 'resultEvidenceScopes',
27
29
  ]);
28
30
  const REQUIRED_EVIDENCE_INPUT_KEYS = new Set([
29
31
  'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
@@ -36,14 +38,20 @@ const ACTION_ONLY_TOOL_NAMES = new Set([
36
38
  'ExitPlanMode', 'GenerateImage', 'GenerateSpeech', 'GenerateVideo', 'LipSyncMedia',
37
39
  'MistakeRecord', 'SetGoalBudget', 'TaskStop', 'TaskUpdate', 'UpdateGoal', 'Write',
38
40
  ]);
39
- const VERIFICATION_TOOL_NAMES = new Set([
41
+ const INSPECTION_TOOL_NAMES = new Set([
40
42
  'codebasesearch', 'cronlist', 'fetchurl', 'getgoal', 'getmedia', 'glob', 'grep',
41
- 'read', 'readmediafile', 'taskoutput', 'test', 'understandimage', 'understandvideo',
43
+ 'read', 'readmediafile', 'taskoutput', 'understandimage', 'understandvideo',
42
44
  'websearch',
43
45
  ]);
46
+ const TEST_TOOL_NAMES = new Set(['test']);
44
47
  const COMMAND_TOOL_NAMES = new Set(['bash', 'command', 'exec_command', 'shell']);
45
48
  const MUTATING_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:cp|mv|rm|mkdir|touch|tee|install|scp|sftp|ssh)\b|(?:sed\s+-i\b)|(?:git\s+(?:add|commit|push|checkout|switch|reset|clean|merge|rebase)\b)|(?:Set-Content|Add-Content|Copy-Item|Move-Item|Remove-Item|New-Item|Start-Process)\b)/iu;
46
- const VERIFICATION_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:node\s+--(?:test|check))|(?:node\s+[^\s;&|]*(?:check|test|verify|validate|lint|gate|probe)[^\s;&|]*\b)|(?:(?:npm|pnpm|yarn)\s+(?:test|(?:run\s+)?(?:test|lint|check|typecheck|build)))|(?:python(?:3)?\s+-m\s+pytest)|(?:pytest)|(?:go\s+test)|(?:cargo\s+test)|(?:dotnet\s+test)|(?:npx\s+)?(?:tsc|eslint|biome\s+check)|(?:git\s+(?:diff(?:\s+--check)?|fsck|status|rev-parse|show))|(?:sha(?:1|256|512)sum|shasum|cmp|diff|wc|rg|grep|cat|ls|stat)\b|(?:certutil\s+-hashfile)|(?:Get-FileHash|Get-Content|Get-Item|Test-Path|Compare-Object|Measure-Object)\b)/iu;
49
+ const INSPECTION_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:git\s+(?:status|rev-parse|show))|(?:wc|rg|grep|cat|ls|stat)\b|(?:Get-Content|Get-Item|Test-Path|Measure-Object)\b)/iu;
50
+ const INTEGRITY_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:git\s+(?:diff(?:\s+--check)?|fsck))|(?:sha(?:1|256|512)sum|shasum|cmp|diff)\b|(?:certutil\s+-hashfile)|(?:Get-FileHash|Compare-Object)\b)/iu;
51
+ const SYNTAX_COMMAND = /(?:^|(?:&&|\|\||;|\s))(?:(?:node\s+--check\b)|(?:(?:npx\s+)?tsc\b)|(?:(?:npm|pnpm|yarn)\s+(?:(?:run\s+)?typecheck)\b))/iu;
52
+ 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
+ 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;
54
+ const EVIDENCE_SCOPE_MARKER = /(?:^|\s)BLUN_EVIDENCE_SCOPE=([A-Za-z0-9._:/\\-]{1,256})(?=\s|$)/gu;
47
55
  const MAX_SUCCESSFUL_TOOL_DIGESTS = 32;
48
56
 
49
57
  function bounded(value, field, max = 512) {
@@ -181,6 +189,33 @@ function successfulToolCallDigest(turnId, toolCallId, toolName) {
181
189
  .slice(0, 16);
182
190
  }
183
191
 
192
+ function successfulVerificationCallDigest(turnId, toolCallId, toolName, kind) {
193
+ const turn = normalizedTurnId(turnId);
194
+ const callId = bounded(toolCallId, 'verificationProof toolCallId', 256);
195
+ const name = bounded(toolName, 'verificationProof toolName', 128);
196
+ const normalizedKind = normalizedVerificationKind(kind);
197
+ return crypto.createHash('sha256')
198
+ .update(`turn:${turn}\0call:${callId}\0tool:${name}\0kind:${normalizedKind}`)
199
+ .digest('hex')
200
+ .slice(0, 16);
201
+ }
202
+
203
+ function normalizedVerificationScope(value, field = 'verificationProof scope') {
204
+ return bounded(value, field, 256);
205
+ }
206
+
207
+ function successfulVerificationScopeDigest(turnId, toolCallId, toolName, kind, scope) {
208
+ const turn = normalizedTurnId(turnId);
209
+ const callId = bounded(toolCallId, 'verificationProof toolCallId', 256);
210
+ const name = bounded(toolName, 'verificationProof toolName', 128);
211
+ const normalizedKind = normalizedVerificationKind(kind);
212
+ const normalizedScope = normalizedVerificationScope(scope);
213
+ return crypto.createHash('sha256')
214
+ .update(`turn:${turn}\0call:${callId}\0tool:${name}\0kind:${normalizedKind}\0scope:${normalizedScope}`)
215
+ .digest('hex')
216
+ .slice(0, 16);
217
+ }
218
+
184
219
  function isActionOnlyTool(toolName) {
185
220
  return ACTION_ONLY_TOOL_NAMES.has(String(toolName ?? '').trim());
186
221
  }
@@ -191,14 +226,93 @@ function verificationCommand(toolArgs) {
191
226
  return typeof command === 'string' ? command.trim().replace(/\s+/gu, ' ') : '';
192
227
  }
193
228
 
194
- function isVerificationToolCall(toolName, toolArgs) {
229
+ function toolArgumentScope(toolName, toolArgs) {
230
+ if (!toolArgs || typeof toolArgs !== 'object' || Array.isArray(toolArgs)) return '';
231
+ const name = String(toolName ?? '').trim().toLowerCase();
232
+ const first = (...keys) => {
233
+ for (const key of keys) {
234
+ if (typeof toolArgs[key] === 'string' && toolArgs[key].trim().length > 0) {
235
+ return normalizedVerificationScope(toolArgs[key], 'verification target scope');
236
+ }
237
+ }
238
+ return '';
239
+ };
240
+ if (['read', 'readmediafile', 'understandimage', 'understandvideo'].includes(name)) {
241
+ return first('file_path', 'path', 'image_path', 'video_path');
242
+ }
243
+ if (name === 'getmedia') return first('media_id', 'id', 'path');
244
+ if (name === 'taskoutput') return first('task_id', 'id');
245
+ if (name === 'fetchurl') return first('url', 'uri');
246
+ if (name === 'websearch') return first('query', 'q');
247
+ if (name === 'grep' || name === 'glob' || name === 'codebasesearch') {
248
+ return first('path', 'cwd', 'directory', 'root', 'query', 'pattern');
249
+ }
250
+ if (name === 'getgoal') return 'runtime:active-goal';
251
+ if (name === 'cronlist') return 'runtime:cron-jobs';
252
+ return '';
253
+ }
254
+
255
+ function commandEvidenceScopes(toolName, toolArgs, providedScopes) {
256
+ const normalizedName = String(toolName ?? '').trim().toLowerCase();
257
+ if (!COMMAND_TOOL_NAMES.has(normalizedName)) return Object.freeze([]);
258
+ const command = verificationCommand(toolArgs);
259
+ if (command.length < 1 || MUTATING_COMMAND.test(command)) return Object.freeze([]);
260
+ const declared = new Set();
261
+ EVIDENCE_SCOPE_MARKER.lastIndex = 0;
262
+ for (const match of command.matchAll(EVIDENCE_SCOPE_MARKER)) declared.add(match[1]);
263
+ return Object.freeze(providedScopes.filter((scope) => declared.has(scope)));
264
+ }
265
+
266
+ function verificationKindsForToolCall(toolName, toolArgs) {
267
+ const normalizedName = String(toolName ?? '').trim().toLowerCase();
268
+ if (INSPECTION_TOOL_NAMES.has(normalizedName)) return Object.freeze(['inspection']);
269
+ if (TEST_TOOL_NAMES.has(normalizedName)) return Object.freeze(['test']);
270
+ if (!COMMAND_TOOL_NAMES.has(normalizedName)) return Object.freeze([]);
271
+ const command = verificationCommand(toolArgs);
272
+ if (command.length < 1 || MUTATING_COMMAND.test(command)) return Object.freeze([]);
273
+ const kinds = [];
274
+ if (INSPECTION_COMMAND.test(command)) kinds.push('inspection');
275
+ if (INTEGRITY_COMMAND.test(command)) kinds.push('integrity');
276
+ if (SYNTAX_COMMAND.test(command)) kinds.push('syntax');
277
+ if (TEST_COMMAND.test(command)) kinds.push('test');
278
+ return Object.freeze(kinds);
279
+ }
280
+
281
+ function canCarryExplicitResultEvidence(toolName, toolArgs) {
195
282
  const normalizedName = String(toolName ?? '').trim().toLowerCase();
196
- if (VERIFICATION_TOOL_NAMES.has(normalizedName)) return true;
197
283
  if (!COMMAND_TOOL_NAMES.has(normalizedName)) return false;
198
284
  const command = verificationCommand(toolArgs);
199
285
  return command.length > 0
200
286
  && !MUTATING_COMMAND.test(command)
201
- && VERIFICATION_COMMAND.test(command);
287
+ && RUNTIME_EVIDENCE_COMMAND.test(command);
288
+ }
289
+
290
+ function normalizedVerificationKind(value, field = 'verificationProof kind') {
291
+ const kind = String(value ?? '').trim();
292
+ if (!VERIFICATION_KINDS.has(kind)) throw new TypeError(`${field} is invalid`);
293
+ return kind;
294
+ }
295
+
296
+ function normalizedResultEvidenceKinds(value) {
297
+ if (value === undefined) return Object.freeze([]);
298
+ const kinds = Array.isArray(value) ? value.map((item) => String(item ?? '').trim()) : [];
299
+ if (!Array.isArray(value) || value.length > VERIFICATION_KINDS.size
300
+ || kinds.some((item) => !VERIFICATION_KINDS.has(item))
301
+ || new Set(kinds).size !== kinds.length) {
302
+ throw new TypeError('result evidence kinds are invalid');
303
+ }
304
+ return Object.freeze(kinds);
305
+ }
306
+
307
+ function normalizedResultEvidenceScopes(value) {
308
+ if (value === undefined) return Object.freeze([]);
309
+ const scopes = Array.isArray(value)
310
+ ? value.map((item, index) => normalizedVerificationScope(item, `resultEvidenceScopes[${index}]`))
311
+ : [];
312
+ if (!Array.isArray(value) || value.length > 5 || new Set(scopes).size !== scopes.length) {
313
+ throw new TypeError('result evidence scopes are invalid');
314
+ }
315
+ return Object.freeze(scopes);
202
316
  }
203
317
 
204
318
  function normalizeSuccessfulToolDigests(value) {
@@ -211,12 +325,17 @@ function normalizeSuccessfulToolDigests(value) {
211
325
  return Object.freeze([...value]);
212
326
  }
213
327
 
214
- function normalizeExactVerificationCall(input, receipt, label) {
328
+ function normalizeExactVerificationCall(input, receipt, label, options = {}) {
329
+ const requireKind = options.requireKind === true;
330
+ const requireScope = options.requireScope === true;
331
+ const expectedKeys = 3 + (requireKind ? 1 : 0) + (requireScope ? 1 : 0);
215
332
  if (!input || typeof input !== 'object' || Array.isArray(input)
216
- || Object.keys(input).length !== 3
333
+ || Object.keys(input).length !== expectedKeys
217
334
  || !Object.hasOwn(input, 'toolCallId')
218
335
  || !Object.hasOwn(input, 'toolName')
219
- || !Object.hasOwn(input, 'claim')) {
336
+ || !Object.hasOwn(input, 'claim')
337
+ || requireKind !== Object.hasOwn(input, 'kind')
338
+ || requireScope !== Object.hasOwn(input, 'scope')) {
220
339
  throw new TypeError(`${label} fields are invalid`);
221
340
  }
222
341
  const toolCallId = bounded(input.toolCallId, `${label} toolCallId`, 256);
@@ -230,6 +349,24 @@ function normalizeExactVerificationCall(input, receipt, label) {
230
349
  if (!receipt.successfulToolCallDigests.includes(callDigest)) {
231
350
  throw new TypeError(`${label} must match a successful current-turn verification call`);
232
351
  }
352
+ if (requireKind) {
353
+ const kind = normalizedVerificationKind(input.kind, `${label} kind`);
354
+ const typedDigest = successfulVerificationCallDigest(receipt.turnId, toolCallId, toolName, kind);
355
+ if (!receipt.successfulVerificationCallDigests.includes(typedDigest)) {
356
+ throw new TypeError(`${label} kind must match the successful current-turn verification call`);
357
+ }
358
+ if (requireScope) {
359
+ const scope = normalizedVerificationScope(input.scope, `${label} scope`);
360
+ const scopeDigest = successfulVerificationScopeDigest(
361
+ receipt.turnId, toolCallId, toolName, kind, scope,
362
+ );
363
+ if (!receipt.successfulVerificationScopeDigests.includes(scopeDigest)) {
364
+ throw new TypeError(`${label} scope must match the successful current-turn verification call target`);
365
+ }
366
+ return Object.freeze({ toolCallId, toolName, kind, scope, claim });
367
+ }
368
+ return Object.freeze({ toolCallId, toolName, kind, claim });
369
+ }
233
370
  return Object.freeze({ toolCallId, toolName, claim });
234
371
  }
235
372
 
@@ -247,18 +384,52 @@ function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
247
384
  && Object.hasOwn(input, 'toolName')
248
385
  && Object.hasOwn(input, 'claim');
249
386
  const subject = String(input?.subject ?? '').trim();
250
- const currentResult = subject === 'result'
387
+ const legacySubjectResult = options.allowLegacy === true
388
+ && subject === 'result'
251
389
  && keys.length === 4
252
390
  && Object.hasOwn(input, 'toolCallId')
253
391
  && Object.hasOwn(input, 'toolName')
254
392
  && Object.hasOwn(input, 'claim');
255
- const currentVerifier = subject === 'verifier'
393
+ const legacySubjectVerifier = options.allowLegacy === true
394
+ && subject === 'verifier'
256
395
  && (keys.length === 4 || keys.length === 5)
257
396
  && Object.hasOwn(input, 'toolCallId')
258
397
  && Object.hasOwn(input, 'toolName')
259
398
  && Object.hasOwn(input, 'claim')
260
399
  && keys.every((key) => ['subject', 'toolCallId', 'toolName', 'claim', 'sharpnessProof'].includes(key));
261
- if (!legacyName && !legacyExact && !currentResult && !currentVerifier) {
400
+ const legacyTypedSubjectResult = options.allowLegacy === true
401
+ && subject === 'result'
402
+ && keys.length === 5
403
+ && Object.hasOwn(input, 'toolCallId')
404
+ && Object.hasOwn(input, 'toolName')
405
+ && Object.hasOwn(input, 'kind')
406
+ && Object.hasOwn(input, 'claim');
407
+ const legacyTypedSubjectVerifier = options.allowLegacy === true
408
+ && subject === 'verifier'
409
+ && (keys.length === 5 || keys.length === 6)
410
+ && Object.hasOwn(input, 'toolCallId')
411
+ && Object.hasOwn(input, 'toolName')
412
+ && Object.hasOwn(input, 'kind')
413
+ && Object.hasOwn(input, 'claim')
414
+ && keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'claim', 'sharpnessProof'].includes(key));
415
+ const currentResult = subject === 'result'
416
+ && keys.length === 6
417
+ && Object.hasOwn(input, 'toolCallId')
418
+ && Object.hasOwn(input, 'toolName')
419
+ && Object.hasOwn(input, 'kind')
420
+ && Object.hasOwn(input, 'scope')
421
+ && Object.hasOwn(input, 'claim');
422
+ const currentVerifier = subject === 'verifier'
423
+ && (keys.length === 6 || keys.length === 7)
424
+ && Object.hasOwn(input, 'toolCallId')
425
+ && Object.hasOwn(input, 'toolName')
426
+ && Object.hasOwn(input, 'kind')
427
+ && Object.hasOwn(input, 'scope')
428
+ && Object.hasOwn(input, 'claim')
429
+ && keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'scope', 'claim', 'sharpnessProof'].includes(key));
430
+ if (!legacyName && !legacyExact && !legacySubjectResult && !legacySubjectVerifier
431
+ && !legacyTypedSubjectResult && !legacyTypedSubjectVerifier
432
+ && !currentResult && !currentVerifier) {
262
433
  throw new TypeError('verificationProof fields are invalid');
263
434
  }
264
435
  const receipt = normalizeActionEvidenceReceipt(evidenceReceipt);
@@ -273,18 +444,28 @@ function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
273
444
  }
274
445
  return Object.freeze({ toolName, claim });
275
446
  }
447
+ const legacySubject = legacySubjectResult || legacySubjectVerifier;
448
+ const legacyTypedSubject = legacyTypedSubjectResult || legacyTypedSubjectVerifier;
276
449
  const primary = normalizeExactVerificationCall(legacyExact ? input : {
277
450
  toolCallId: input.toolCallId,
278
451
  toolName: input.toolName,
452
+ ...(legacySubject ? {} : { kind: input.kind }),
453
+ ...(currentResult || currentVerifier ? { scope: input.scope } : {}),
279
454
  claim: input.claim,
280
- }, receipt, 'verificationProof');
455
+ }, receipt, 'verificationProof', {
456
+ requireKind: !legacyExact && !legacySubject,
457
+ requireScope: currentResult || currentVerifier,
458
+ });
281
459
  if (legacyExact) return primary;
282
460
  if (!VERIFICATION_SUBJECTS.has(subject)) throw new TypeError('verificationProof subject is invalid');
283
461
  if (subject === 'result') return Object.freeze({ subject, ...primary });
284
462
  if (input.sharpnessProof === undefined) {
285
463
  throw new TypeError('verifier proof requires a sharpnessProof');
286
464
  }
287
- const sharpnessProof = normalizeExactVerificationCall(input.sharpnessProof, receipt, 'sharpnessProof');
465
+ const sharpnessProof = normalizeExactVerificationCall(input.sharpnessProof, receipt, 'sharpnessProof', {
466
+ requireKind: !legacySubject,
467
+ requireScope: !legacySubject && !legacyTypedSubject,
468
+ });
288
469
  if (sharpnessProof.toolCallId === primary.toolCallId
289
470
  && sharpnessProof.toolName === primary.toolName) {
290
471
  throw new TypeError('sharpnessProof must name a distinct verification call');
@@ -301,6 +482,8 @@ function emptyActionEvidenceReceipt(turnId) {
301
482
  failedTools: 0,
302
483
  successfulToolDigests: Object.freeze([]),
303
484
  successfulToolCallDigests: Object.freeze([]),
485
+ successfulVerificationCallDigests: Object.freeze([]),
486
+ successfulVerificationScopeDigests: Object.freeze([]),
304
487
  digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
305
488
  });
306
489
  }
@@ -308,7 +491,10 @@ function emptyActionEvidenceReceipt(turnId) {
308
491
  function normalizeActionEvidenceReceipt(input) {
309
492
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('evidence receipt must be an object');
310
493
  const requiredKeys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
311
- const allowedKeys = new Set([...requiredKeys, 'successfulToolDigests', 'successfulToolCallDigests']);
494
+ const allowedKeys = new Set([
495
+ ...requiredKeys, 'successfulToolDigests', 'successfulToolCallDigests',
496
+ 'successfulVerificationCallDigests', 'successfulVerificationScopeDigests',
497
+ ]);
312
498
  if (!Object.keys(input).every((key) => allowedKeys.has(key))
313
499
  || ![...requiredKeys].every((key) => Object.hasOwn(input, key))) {
314
500
  throw new TypeError('evidence receipt fields are invalid');
@@ -320,6 +506,12 @@ function normalizeActionEvidenceReceipt(input) {
320
506
  failedTools: Number(input.failedTools),
321
507
  successfulToolDigests: normalizeSuccessfulToolDigests(input.successfulToolDigests),
322
508
  successfulToolCallDigests: normalizeSuccessfulToolDigests(input.successfulToolCallDigests),
509
+ successfulVerificationCallDigests: normalizeSuccessfulToolDigests(
510
+ input.successfulVerificationCallDigests,
511
+ ),
512
+ successfulVerificationScopeDigests: normalizeSuccessfulToolDigests(
513
+ input.successfulVerificationScopeDigests,
514
+ ),
323
515
  digest: String(input.digest ?? ''),
324
516
  };
325
517
  if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
@@ -352,20 +544,58 @@ function advanceActionEvidenceReceipt(current, input) {
352
544
  const successful = decision === 'passed' && outcome === 'success';
353
545
  const successfulToolDigests = [...prior.successfulToolDigests];
354
546
  const successfulToolCallDigests = [...prior.successfulToolCallDigests];
547
+ const successfulVerificationCallDigests = [...prior.successfulVerificationCallDigests];
548
+ const successfulVerificationScopeDigests = [...prior.successfulVerificationScopeDigests];
355
549
  const toolDigest = successfulToolDigest(toolName);
356
550
  if (successful && !successfulToolDigests.includes(toolDigest)) {
357
551
  successfulToolDigests.push(toolDigest);
358
552
  if (successfulToolDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolDigests.shift();
359
553
  }
360
- const verificationCall = successful && isVerificationToolCall(toolName, input.toolArgs);
554
+ const providedResultKinds = normalizedResultEvidenceKinds(input.resultEvidenceKinds);
555
+ const explicitResultKinds = successful && canCarryExplicitResultEvidence(toolName, input.toolArgs)
556
+ ? providedResultKinds
557
+ : [];
558
+ const verificationKinds = successful
559
+ ? [...new Set([...verificationKindsForToolCall(toolName, input.toolArgs), ...explicitResultKinds])]
560
+ : [];
561
+ const argumentScope = successful ? toolArgumentScope(toolName, input.toolArgs) : '';
562
+ const providedResultScopes = normalizedResultEvidenceScopes(input.resultEvidenceScopes);
563
+ const explicitResultScopes = successful
564
+ ? commandEvidenceScopes(toolName, input.toolArgs, providedResultScopes)
565
+ : [];
566
+ const verificationScopes = [...new Set([
567
+ ...(argumentScope ? [argumentScope] : []),
568
+ ...explicitResultScopes,
569
+ ])];
570
+ const verificationCall = verificationKinds.length > 0;
361
571
  if (verificationCall) {
362
572
  const callDigest = successfulToolCallDigest(turnId, toolCallId, toolName);
363
573
  if (!successfulToolCallDigests.includes(callDigest)) successfulToolCallDigests.push(callDigest);
364
574
  if (successfulToolCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolCallDigests.shift();
575
+ for (const kind of verificationKinds) {
576
+ const typedDigest = successfulVerificationCallDigest(turnId, toolCallId, toolName, kind);
577
+ if (!successfulVerificationCallDigests.includes(typedDigest)) {
578
+ successfulVerificationCallDigests.push(typedDigest);
579
+ }
580
+ if (successfulVerificationCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
581
+ successfulVerificationCallDigests.shift();
582
+ }
583
+ for (const scope of verificationScopes) {
584
+ const scopeDigest = successfulVerificationScopeDigest(
585
+ turnId, toolCallId, toolName, kind, scope,
586
+ );
587
+ if (!successfulVerificationScopeDigests.includes(scopeDigest)) {
588
+ successfulVerificationScopeDigests.push(scopeDigest);
589
+ }
590
+ if (successfulVerificationScopeDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
591
+ successfulVerificationScopeDigests.shift();
592
+ }
593
+ }
594
+ }
365
595
  }
366
596
  const digest = crypto.createHash('sha256').update([
367
597
  prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
368
- String(verificationCall),
598
+ verificationKinds.join(','), verificationScopes.join(','),
369
599
  ].join('\0')).digest('hex').slice(0, 16);
370
600
  return Object.freeze({
371
601
  turnId,
@@ -374,6 +604,8 @@ function advanceActionEvidenceReceipt(current, input) {
374
604
  failedTools: prior.failedTools + (successful ? 0 : 1),
375
605
  successfulToolDigests: Object.freeze(successfulToolDigests),
376
606
  successfulToolCallDigests: Object.freeze(successfulToolCallDigests),
607
+ successfulVerificationCallDigests: Object.freeze(successfulVerificationCallDigests),
608
+ successfulVerificationScopeDigests: Object.freeze(successfulVerificationScopeDigests),
377
609
  digest,
378
610
  });
379
611
  }
@@ -490,10 +722,18 @@ function projectActionCheckpoint(checkpoint) {
490
722
  const subject = value.verificationProof.subject === undefined
491
723
  ? ''
492
724
  : ` [${value.verificationProof.subject}]`;
493
- lines.push(`Verification proof${subject}: ${call} - ${value.verificationProof.claim}`);
725
+ const kind = value.verificationProof.kind === undefined
726
+ ? ''
727
+ : ` [${value.verificationProof.kind}]`;
728
+ const scope = value.verificationProof.scope === undefined
729
+ ? ''
730
+ : ` [scope: ${value.verificationProof.scope}]`;
731
+ lines.push(`Verification proof${subject}${kind}${scope}: ${call} - ${value.verificationProof.claim}`);
494
732
  if (value.verificationProof.sharpnessProof !== undefined) {
495
733
  const sharpness = value.verificationProof.sharpnessProof;
496
- lines.push(`Sharpness proof: ${sharpness.toolName} call ${sharpness.toolCallId} - ${sharpness.claim}`);
734
+ const sharpnessKind = sharpness.kind === undefined ? '' : ` [${sharpness.kind}]`;
735
+ const sharpnessScope = sharpness.scope === undefined ? '' : ` [scope: ${sharpness.scope}]`;
736
+ lines.push(`Sharpness proof${sharpnessKind}${sharpnessScope}: ${sharpness.toolName} call ${sharpness.toolCallId} - ${sharpness.claim}`);
497
737
  }
498
738
  }
499
739
  if (value.nextTrigger !== undefined) {
@@ -534,5 +774,7 @@ module.exports = {
534
774
  normalizeVerificationProof,
535
775
  projectActionCheckpoint,
536
776
  successfulToolCallDigest,
777
+ successfulVerificationCallDigest,
537
778
  successfulToolDigest,
779
+ verificationKindsForToolCall,
538
780
  };
@@ -30,6 +30,14 @@ function verificationProofGaps(checkpoint) {
30
30
  .test(String(error?.message ?? ''))) {
31
31
  return ['The verifier sharpness proof does not match a successful current-turn verification call.'];
32
32
  }
33
+ if (/(?:verificationProof|sharpnessProof) kind must match the successful current-turn verification call/u
34
+ .test(String(error?.message ?? ''))) {
35
+ return ['The completion proof kind exceeds what its exact verification call measured.'];
36
+ }
37
+ if (/(?:verificationProof|sharpnessProof) scope must match the successful current-turn verification call target/u
38
+ .test(String(error?.message ?? ''))) {
39
+ return ['Bind the completion proof scope to the exact target measured by its successful current-turn verification call.'];
40
+ }
33
41
  if (/action-only tool/u.test(String(error?.message ?? ''))) {
34
42
  return ['The completion proof names an action-only tool, not a verification tool.'];
35
43
  }
package/blun.mjs CHANGED
@@ -260352,10 +260352,14 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
260352
260352
  subject: _enum(["result", "verifier"]),
260353
260353
  toolCallId: string().min(1).max(256),
260354
260354
  toolName: string().min(1).max(128),
260355
+ kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
260356
+ scope: string().min(1).max(256),
260355
260357
  claim: string().min(1).max(512),
260356
260358
  sharpnessProof: object({
260357
260359
  toolCallId: string().min(1).max(256),
260358
260360
  toolName: string().min(1).max(128),
260361
+ kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
260362
+ scope: string().min(1).max(256),
260359
260363
  claim: string().min(1).max(512)
260360
260364
  }).strict().optional()
260361
260365
  }).strict().superRefine((value, ctx) => {
@@ -261692,6 +261696,17 @@ function telemetryToolErrorType(result) {
261692
261696
  function toolResultText(result) {
261693
261697
  return toolOutputText(result.output);
261694
261698
  }
261699
+ function explicitToolResultEvidenceKinds(result) {
261700
+ const text = toolResultText(result);
261701
+ return /(?:^|\s)BLUN_EVIDENCE_KIND=reachability(?:\s|$)/u.test(text) ? ["reachability"] : [];
261702
+ }
261703
+ function explicitToolResultEvidenceScopes(result) {
261704
+ const text = toolResultText(result);
261705
+ return [...text.matchAll(/(?:^|\s)BLUN_EVIDENCE_SCOPE=([A-Za-z0-9._:/\\-]{1,256})(?=\s|$)/gu)]
261706
+ .map((match) => match[1])
261707
+ .filter((scope, index, scopes) => scopes.indexOf(scope) === index)
261708
+ .slice(0, 5);
261709
+ }
261695
261710
  function abandonedToolResultOutput(ended) {
261696
261711
  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.`;
261697
261712
  }
@@ -262794,7 +262809,9 @@ var init_turn = __esmMin((() => {
262794
262809
  toolName: started.name,
262795
262810
  outcome,
262796
262811
  durationMs: Date.now() - started.startedAt,
262797
- toolArgs: started.args
262812
+ toolArgs: started.args,
262813
+ resultEvidenceKinds: explicitToolResultEvidenceKinds(event.result),
262814
+ resultEvidenceScopes: explicitToolResultEvidenceScopes(event.result)
262798
262815
  });
262799
262816
  this.agent.telemetry.track("tool_call", properties);
262800
262817
  this.agent.feedRootMissionContract("result", {
@@ -262900,6 +262917,8 @@ var update_goal_default;
262900
262917
  var init_update_goal$1 = __esmMin((() => {
262901
262918
  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";
262902
262919
  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";
262920
+ 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";
262921
+ 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";
262903
262922
  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";
262904
262923
  }));
262905
262924
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.450",
3
+ "version": "9.1.452",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {