blun-king-cli 9.1.449 → 9.1.451

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.
@@ -12,6 +12,8 @@ const EPISTEMIC_STATES = new Set([
12
12
  const TRIGGER_KINDS = new Set([
13
13
  'immediate', 'external_event', 'time', 'dependency', 'user_decision',
14
14
  ]);
15
+ const VERIFICATION_SUBJECTS = new Set(['result', 'verifier']);
16
+ const VERIFICATION_KINDS = new Set(['inspection', 'integrity', 'syntax', 'test', 'reachability']);
15
17
  const MODEL_KEYS = new Set([
16
18
  'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'verificationProof', 'nextTrigger', 'problemFrame', 'updatedAt',
17
19
  ]);
@@ -23,6 +25,7 @@ const PROBLEM_FRAME_KEYS = new Set([
23
25
  const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition', 'dueAt']);
24
26
  const EVIDENCE_INPUT_KEYS = new Set([
25
27
  'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs', 'toolArgs',
28
+ 'resultEvidenceKinds',
26
29
  ]);
27
30
  const REQUIRED_EVIDENCE_INPUT_KEYS = new Set([
28
31
  'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
@@ -35,14 +38,19 @@ const ACTION_ONLY_TOOL_NAMES = new Set([
35
38
  'ExitPlanMode', 'GenerateImage', 'GenerateSpeech', 'GenerateVideo', 'LipSyncMedia',
36
39
  'MistakeRecord', 'SetGoalBudget', 'TaskStop', 'TaskUpdate', 'UpdateGoal', 'Write',
37
40
  ]);
38
- const VERIFICATION_TOOL_NAMES = new Set([
41
+ const INSPECTION_TOOL_NAMES = new Set([
39
42
  'codebasesearch', 'cronlist', 'fetchurl', 'getgoal', 'getmedia', 'glob', 'grep',
40
- 'read', 'readmediafile', 'taskoutput', 'test', 'understandimage', 'understandvideo',
43
+ 'read', 'readmediafile', 'taskoutput', 'understandimage', 'understandvideo',
41
44
  'websearch',
42
45
  ]);
46
+ const TEST_TOOL_NAMES = new Set(['test']);
43
47
  const COMMAND_TOOL_NAMES = new Set(['bash', 'command', 'exec_command', 'shell']);
44
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;
45
- 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;
46
54
  const MAX_SUCCESSFUL_TOOL_DIGESTS = 32;
47
55
 
48
56
  function bounded(value, field, max = 512) {
@@ -180,6 +188,17 @@ function successfulToolCallDigest(turnId, toolCallId, toolName) {
180
188
  .slice(0, 16);
181
189
  }
182
190
 
191
+ function successfulVerificationCallDigest(turnId, toolCallId, toolName, kind) {
192
+ const turn = normalizedTurnId(turnId);
193
+ const callId = bounded(toolCallId, 'verificationProof toolCallId', 256);
194
+ const name = bounded(toolName, 'verificationProof toolName', 128);
195
+ const normalizedKind = normalizedVerificationKind(kind);
196
+ return crypto.createHash('sha256')
197
+ .update(`turn:${turn}\0call:${callId}\0tool:${name}\0kind:${normalizedKind}`)
198
+ .digest('hex')
199
+ .slice(0, 16);
200
+ }
201
+
183
202
  function isActionOnlyTool(toolName) {
184
203
  return ACTION_ONLY_TOOL_NAMES.has(String(toolName ?? '').trim());
185
204
  }
@@ -190,14 +209,45 @@ function verificationCommand(toolArgs) {
190
209
  return typeof command === 'string' ? command.trim().replace(/\s+/gu, ' ') : '';
191
210
  }
192
211
 
193
- function isVerificationToolCall(toolName, toolArgs) {
212
+ function verificationKindsForToolCall(toolName, toolArgs) {
213
+ const normalizedName = String(toolName ?? '').trim().toLowerCase();
214
+ if (INSPECTION_TOOL_NAMES.has(normalizedName)) return Object.freeze(['inspection']);
215
+ if (TEST_TOOL_NAMES.has(normalizedName)) return Object.freeze(['test']);
216
+ if (!COMMAND_TOOL_NAMES.has(normalizedName)) return Object.freeze([]);
217
+ const command = verificationCommand(toolArgs);
218
+ if (command.length < 1 || MUTATING_COMMAND.test(command)) return Object.freeze([]);
219
+ const kinds = [];
220
+ if (INSPECTION_COMMAND.test(command)) kinds.push('inspection');
221
+ if (INTEGRITY_COMMAND.test(command)) kinds.push('integrity');
222
+ if (SYNTAX_COMMAND.test(command)) kinds.push('syntax');
223
+ if (TEST_COMMAND.test(command)) kinds.push('test');
224
+ return Object.freeze(kinds);
225
+ }
226
+
227
+ function canCarryExplicitResultEvidence(toolName, toolArgs) {
194
228
  const normalizedName = String(toolName ?? '').trim().toLowerCase();
195
- if (VERIFICATION_TOOL_NAMES.has(normalizedName)) return true;
196
229
  if (!COMMAND_TOOL_NAMES.has(normalizedName)) return false;
197
230
  const command = verificationCommand(toolArgs);
198
231
  return command.length > 0
199
232
  && !MUTATING_COMMAND.test(command)
200
- && VERIFICATION_COMMAND.test(command);
233
+ && RUNTIME_EVIDENCE_COMMAND.test(command);
234
+ }
235
+
236
+ function normalizedVerificationKind(value, field = 'verificationProof kind') {
237
+ const kind = String(value ?? '').trim();
238
+ if (!VERIFICATION_KINDS.has(kind)) throw new TypeError(`${field} is invalid`);
239
+ return kind;
240
+ }
241
+
242
+ function normalizedResultEvidenceKinds(value) {
243
+ if (value === undefined) return Object.freeze([]);
244
+ const kinds = Array.isArray(value) ? value.map((item) => String(item ?? '').trim()) : [];
245
+ if (!Array.isArray(value) || value.length > VERIFICATION_KINDS.size
246
+ || kinds.some((item) => !VERIFICATION_KINDS.has(item))
247
+ || new Set(kinds).size !== kinds.length) {
248
+ throw new TypeError('result evidence kinds are invalid');
249
+ }
250
+ return Object.freeze(kinds);
201
251
  }
202
252
 
203
253
  function normalizeSuccessfulToolDigests(value) {
@@ -210,39 +260,116 @@ function normalizeSuccessfulToolDigests(value) {
210
260
  return Object.freeze([...value]);
211
261
  }
212
262
 
263
+ function normalizeExactVerificationCall(input, receipt, label, options = {}) {
264
+ const requireKind = options.requireKind === true;
265
+ const expectedKeys = requireKind ? 4 : 3;
266
+ if (!input || typeof input !== 'object' || Array.isArray(input)
267
+ || Object.keys(input).length !== expectedKeys
268
+ || !Object.hasOwn(input, 'toolCallId')
269
+ || !Object.hasOwn(input, 'toolName')
270
+ || !Object.hasOwn(input, 'claim')
271
+ || requireKind !== Object.hasOwn(input, 'kind')) {
272
+ throw new TypeError(`${label} fields are invalid`);
273
+ }
274
+ const toolCallId = bounded(input.toolCallId, `${label} toolCallId`, 256);
275
+ const toolName = bounded(input.toolName, `${label} toolName`, 128);
276
+ const claim = bounded(input.claim, `${label} claim`);
277
+ if (isActionOnlyTool(toolName)) {
278
+ const purpose = label === 'verificationProof' ? 'verification proof' : label;
279
+ throw new TypeError(`action-only tool cannot serve as ${purpose}`);
280
+ }
281
+ const callDigest = successfulToolCallDigest(receipt.turnId, toolCallId, toolName);
282
+ if (!receipt.successfulToolCallDigests.includes(callDigest)) {
283
+ throw new TypeError(`${label} must match a successful current-turn verification call`);
284
+ }
285
+ if (requireKind) {
286
+ const kind = normalizedVerificationKind(input.kind, `${label} kind`);
287
+ const typedDigest = successfulVerificationCallDigest(receipt.turnId, toolCallId, toolName, kind);
288
+ if (!receipt.successfulVerificationCallDigests.includes(typedDigest)) {
289
+ throw new TypeError(`${label} kind must match the successful current-turn verification call`);
290
+ }
291
+ return Object.freeze({ toolCallId, toolName, kind, claim });
292
+ }
293
+ return Object.freeze({ toolCallId, toolName, claim });
294
+ }
295
+
213
296
  function normalizeVerificationProof(input, evidenceReceipt, options = {}) {
214
297
  const keys = input && typeof input === 'object' && !Array.isArray(input)
215
298
  ? Object.keys(input)
216
299
  : [];
217
- const legacy = options.allowLegacy === true
300
+ const legacyName = options.allowLegacy === true
218
301
  && keys.length === 2
219
302
  && Object.hasOwn(input, 'toolName')
220
303
  && Object.hasOwn(input, 'claim');
221
- const current = keys.length === 3
304
+ const legacyExact = options.allowLegacy === true
305
+ && keys.length === 3
306
+ && Object.hasOwn(input, 'toolCallId')
307
+ && Object.hasOwn(input, 'toolName')
308
+ && Object.hasOwn(input, 'claim');
309
+ const subject = String(input?.subject ?? '').trim();
310
+ const legacySubjectResult = options.allowLegacy === true
311
+ && subject === 'result'
312
+ && keys.length === 4
222
313
  && Object.hasOwn(input, 'toolCallId')
223
314
  && Object.hasOwn(input, 'toolName')
224
315
  && Object.hasOwn(input, 'claim');
225
- if (!legacy && !current) {
316
+ const legacySubjectVerifier = options.allowLegacy === true
317
+ && subject === 'verifier'
318
+ && (keys.length === 4 || keys.length === 5)
319
+ && Object.hasOwn(input, 'toolCallId')
320
+ && Object.hasOwn(input, 'toolName')
321
+ && Object.hasOwn(input, 'claim')
322
+ && keys.every((key) => ['subject', 'toolCallId', 'toolName', 'claim', 'sharpnessProof'].includes(key));
323
+ const currentResult = subject === 'result'
324
+ && keys.length === 5
325
+ && Object.hasOwn(input, 'toolCallId')
326
+ && Object.hasOwn(input, 'toolName')
327
+ && Object.hasOwn(input, 'kind')
328
+ && Object.hasOwn(input, 'claim');
329
+ const currentVerifier = subject === 'verifier'
330
+ && (keys.length === 5 || keys.length === 6)
331
+ && Object.hasOwn(input, 'toolCallId')
332
+ && Object.hasOwn(input, 'toolName')
333
+ && Object.hasOwn(input, 'kind')
334
+ && Object.hasOwn(input, 'claim')
335
+ && keys.every((key) => ['subject', 'toolCallId', 'toolName', 'kind', 'claim', 'sharpnessProof'].includes(key));
336
+ if (!legacyName && !legacyExact && !legacySubjectResult && !legacySubjectVerifier
337
+ && !currentResult && !currentVerifier) {
226
338
  throw new TypeError('verificationProof fields are invalid');
227
339
  }
228
- const toolName = bounded(input.toolName, 'verificationProof toolName', 128);
229
- const claim = bounded(input.claim, 'verificationProof claim');
230
- if (isActionOnlyTool(toolName)) {
231
- throw new TypeError('action-only tool cannot serve as verification proof');
232
- }
233
340
  const receipt = normalizeActionEvidenceReceipt(evidenceReceipt);
234
- if (legacy) {
341
+ if (legacyName) {
342
+ const toolName = bounded(input.toolName, 'verificationProof toolName', 128);
343
+ const claim = bounded(input.claim, 'verificationProof claim');
344
+ if (isActionOnlyTool(toolName)) {
345
+ throw new TypeError('action-only tool cannot serve as verification proof');
346
+ }
235
347
  if (!receipt.successfulToolDigests.includes(successfulToolDigest(toolName))) {
236
348
  throw new TypeError('verificationProof must name a successful current-turn tool');
237
349
  }
238
350
  return Object.freeze({ toolName, claim });
239
351
  }
240
- const toolCallId = bounded(input.toolCallId, 'verificationProof toolCallId', 256);
241
- const callDigest = successfulToolCallDigest(receipt.turnId, toolCallId, toolName);
242
- if (!receipt.successfulToolCallDigests.includes(callDigest)) {
243
- throw new TypeError('verificationProof must match a successful current-turn verification call');
352
+ const legacySubject = legacySubjectResult || legacySubjectVerifier;
353
+ const primary = normalizeExactVerificationCall(legacyExact ? input : {
354
+ toolCallId: input.toolCallId,
355
+ toolName: input.toolName,
356
+ ...(legacySubject ? {} : { kind: input.kind }),
357
+ claim: input.claim,
358
+ }, receipt, 'verificationProof', { requireKind: !legacyExact && !legacySubject });
359
+ if (legacyExact) return primary;
360
+ if (!VERIFICATION_SUBJECTS.has(subject)) throw new TypeError('verificationProof subject is invalid');
361
+ if (subject === 'result') return Object.freeze({ subject, ...primary });
362
+ if (input.sharpnessProof === undefined) {
363
+ throw new TypeError('verifier proof requires a sharpnessProof');
364
+ }
365
+ const sharpnessProof = normalizeExactVerificationCall(input.sharpnessProof, receipt, 'sharpnessProof', {
366
+ requireKind: !legacySubject,
367
+ });
368
+ if (sharpnessProof.toolCallId === primary.toolCallId
369
+ && sharpnessProof.toolName === primary.toolName) {
370
+ throw new TypeError('sharpnessProof must name a distinct verification call');
244
371
  }
245
- return Object.freeze({ toolCallId, toolName, claim });
372
+ return Object.freeze({ subject, ...primary, sharpnessProof });
246
373
  }
247
374
 
248
375
  function emptyActionEvidenceReceipt(turnId) {
@@ -254,6 +381,7 @@ function emptyActionEvidenceReceipt(turnId) {
254
381
  failedTools: 0,
255
382
  successfulToolDigests: Object.freeze([]),
256
383
  successfulToolCallDigests: Object.freeze([]),
384
+ successfulVerificationCallDigests: Object.freeze([]),
257
385
  digest: crypto.createHash('sha256').update(`turn:${normalized}:empty`).digest('hex').slice(0, 16),
258
386
  });
259
387
  }
@@ -261,7 +389,10 @@ function emptyActionEvidenceReceipt(turnId) {
261
389
  function normalizeActionEvidenceReceipt(input) {
262
390
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('evidence receipt must be an object');
263
391
  const requiredKeys = new Set(['turnId', 'completedTools', 'successfulTools', 'failedTools', 'digest']);
264
- const allowedKeys = new Set([...requiredKeys, 'successfulToolDigests', 'successfulToolCallDigests']);
392
+ const allowedKeys = new Set([
393
+ ...requiredKeys, 'successfulToolDigests', 'successfulToolCallDigests',
394
+ 'successfulVerificationCallDigests',
395
+ ]);
265
396
  if (!Object.keys(input).every((key) => allowedKeys.has(key))
266
397
  || ![...requiredKeys].every((key) => Object.hasOwn(input, key))) {
267
398
  throw new TypeError('evidence receipt fields are invalid');
@@ -273,6 +404,9 @@ function normalizeActionEvidenceReceipt(input) {
273
404
  failedTools: Number(input.failedTools),
274
405
  successfulToolDigests: normalizeSuccessfulToolDigests(input.successfulToolDigests),
275
406
  successfulToolCallDigests: normalizeSuccessfulToolDigests(input.successfulToolCallDigests),
407
+ successfulVerificationCallDigests: normalizeSuccessfulToolDigests(
408
+ input.successfulVerificationCallDigests,
409
+ ),
276
410
  digest: String(input.digest ?? ''),
277
411
  };
278
412
  if (![receipt.completedTools, receipt.successfulTools, receipt.failedTools]
@@ -305,20 +439,37 @@ function advanceActionEvidenceReceipt(current, input) {
305
439
  const successful = decision === 'passed' && outcome === 'success';
306
440
  const successfulToolDigests = [...prior.successfulToolDigests];
307
441
  const successfulToolCallDigests = [...prior.successfulToolCallDigests];
442
+ const successfulVerificationCallDigests = [...prior.successfulVerificationCallDigests];
308
443
  const toolDigest = successfulToolDigest(toolName);
309
444
  if (successful && !successfulToolDigests.includes(toolDigest)) {
310
445
  successfulToolDigests.push(toolDigest);
311
446
  if (successfulToolDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolDigests.shift();
312
447
  }
313
- const verificationCall = successful && isVerificationToolCall(toolName, input.toolArgs);
448
+ const providedResultKinds = normalizedResultEvidenceKinds(input.resultEvidenceKinds);
449
+ const explicitResultKinds = successful && canCarryExplicitResultEvidence(toolName, input.toolArgs)
450
+ ? providedResultKinds
451
+ : [];
452
+ const verificationKinds = successful
453
+ ? [...new Set([...verificationKindsForToolCall(toolName, input.toolArgs), ...explicitResultKinds])]
454
+ : [];
455
+ const verificationCall = verificationKinds.length > 0;
314
456
  if (verificationCall) {
315
457
  const callDigest = successfulToolCallDigest(turnId, toolCallId, toolName);
316
458
  if (!successfulToolCallDigests.includes(callDigest)) successfulToolCallDigests.push(callDigest);
317
459
  if (successfulToolCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) successfulToolCallDigests.shift();
460
+ for (const kind of verificationKinds) {
461
+ const typedDigest = successfulVerificationCallDigest(turnId, toolCallId, toolName, kind);
462
+ if (!successfulVerificationCallDigests.includes(typedDigest)) {
463
+ successfulVerificationCallDigests.push(typedDigest);
464
+ }
465
+ if (successfulVerificationCallDigests.length > MAX_SUCCESSFUL_TOOL_DIGESTS) {
466
+ successfulVerificationCallDigests.shift();
467
+ }
468
+ }
318
469
  }
319
470
  const digest = crypto.createHash('sha256').update([
320
471
  prior.digest, String(turnId), toolCallId, toolName, decision, outcome, String(durationMs),
321
- String(verificationCall),
472
+ verificationKinds.join(','),
322
473
  ].join('\0')).digest('hex').slice(0, 16);
323
474
  return Object.freeze({
324
475
  turnId,
@@ -327,6 +478,7 @@ function advanceActionEvidenceReceipt(current, input) {
327
478
  failedTools: prior.failedTools + (successful ? 0 : 1),
328
479
  successfulToolDigests: Object.freeze(successfulToolDigests),
329
480
  successfulToolCallDigests: Object.freeze(successfulToolCallDigests),
481
+ successfulVerificationCallDigests: Object.freeze(successfulVerificationCallDigests),
330
482
  digest,
331
483
  });
332
484
  }
@@ -440,7 +592,18 @@ function projectActionCheckpoint(checkpoint) {
440
592
  const call = value.verificationProof.toolCallId === undefined
441
593
  ? value.verificationProof.toolName
442
594
  : `${value.verificationProof.toolName} call ${value.verificationProof.toolCallId}`;
443
- lines.push(`Verification proof: ${call} - ${value.verificationProof.claim}`);
595
+ const subject = value.verificationProof.subject === undefined
596
+ ? ''
597
+ : ` [${value.verificationProof.subject}]`;
598
+ const kind = value.verificationProof.kind === undefined
599
+ ? ''
600
+ : ` [${value.verificationProof.kind}]`;
601
+ lines.push(`Verification proof${subject}${kind}: ${call} - ${value.verificationProof.claim}`);
602
+ if (value.verificationProof.sharpnessProof !== undefined) {
603
+ const sharpness = value.verificationProof.sharpnessProof;
604
+ const sharpnessKind = sharpness.kind === undefined ? '' : ` [${sharpness.kind}]`;
605
+ lines.push(`Sharpness proof${sharpnessKind}: ${sharpness.toolName} call ${sharpness.toolCallId} - ${sharpness.claim}`);
606
+ }
444
607
  }
445
608
  if (value.nextTrigger !== undefined) {
446
609
  lines.push(`Next trigger: ${value.nextTrigger.kind.replaceAll('_', ' ')} - ${value.nextTrigger.condition}`);
@@ -480,5 +643,7 @@ module.exports = {
480
643
  normalizeVerificationProof,
481
644
  projectActionCheckpoint,
482
645
  successfulToolCallDigest,
646
+ successfulVerificationCallDigest,
483
647
  successfulToolDigest,
648
+ verificationKindsForToolCall,
484
649
  };
@@ -22,6 +22,18 @@ function verificationProofGaps(checkpoint) {
22
22
  normalizeVerificationProof(checkpoint.verificationProof, checkpoint.evidenceReceipt, { allowLegacy: true });
23
23
  return [];
24
24
  } catch (error) {
25
+ if (/verifier proof requires a sharpnessProof|sharpnessProof must name a distinct verification call/u
26
+ .test(String(error?.message ?? ''))) {
27
+ return ['A verifier completion needs a distinct current-turn counterexample or mutation call.'];
28
+ }
29
+ if (/sharpnessProof must match a successful current-turn verification call/u
30
+ .test(String(error?.message ?? ''))) {
31
+ return ['The verifier sharpness proof does not match a successful current-turn verification call.'];
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
+ }
25
37
  if (/action-only tool/u.test(String(error?.message ?? ''))) {
26
38
  return ['The completion proof names an action-only tool, not a verification tool.'];
27
39
  }
package/blun.mjs CHANGED
@@ -260349,10 +260349,36 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
260349
260349
  nextAction: string().min(1).max(512),
260350
260350
  expectedEvidence: string().min(1).max(512),
260351
260351
  verificationProof: object({
260352
+ subject: _enum(["result", "verifier"]),
260352
260353
  toolCallId: string().min(1).max(256),
260353
260354
  toolName: string().min(1).max(128),
260354
- claim: string().min(1).max(512)
260355
- }).strict().optional(),
260355
+ kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
260356
+ claim: string().min(1).max(512),
260357
+ sharpnessProof: object({
260358
+ toolCallId: string().min(1).max(256),
260359
+ toolName: string().min(1).max(128),
260360
+ kind: _enum(["inspection", "integrity", "syntax", "test", "reachability"]),
260361
+ claim: string().min(1).max(512)
260362
+ }).strict().optional()
260363
+ }).strict().superRefine((value, ctx) => {
260364
+ if (value.subject === "verifier" && value.sharpnessProof === void 0) ctx.addIssue({
260365
+ code: "custom",
260366
+ path: ["sharpnessProof"],
260367
+ message: "verifier subject requires sharpnessProof"
260368
+ });
260369
+ if (value.subject === "result" && value.sharpnessProof !== void 0) ctx.addIssue({
260370
+ code: "custom",
260371
+ path: ["sharpnessProof"],
260372
+ message: "result subject cannot carry sharpnessProof"
260373
+ });
260374
+ if (value.sharpnessProof !== void 0
260375
+ && value.sharpnessProof.toolCallId === value.toolCallId
260376
+ && value.sharpnessProof.toolName === value.toolName) ctx.addIssue({
260377
+ code: "custom",
260378
+ path: ["sharpnessProof"],
260379
+ message: "sharpnessProof must name a distinct verification call"
260380
+ });
260381
+ }).optional(),
260356
260382
  nextTrigger: object({
260357
260383
  kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
260358
260384
  condition: string().min(1).max(512),
@@ -261668,6 +261694,10 @@ function telemetryToolErrorType(result) {
261668
261694
  function toolResultText(result) {
261669
261695
  return toolOutputText(result.output);
261670
261696
  }
261697
+ function explicitToolResultEvidenceKinds(result) {
261698
+ const text = toolResultText(result);
261699
+ return /(?:^|\s)BLUN_EVIDENCE_KIND=reachability(?:\s|$)/u.test(text) ? ["reachability"] : [];
261700
+ }
261671
261701
  function abandonedToolResultOutput(ended) {
261672
261702
  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.`;
261673
261703
  }
@@ -262770,7 +262800,8 @@ var init_turn = __esmMin((() => {
262770
262800
  toolName: started.name,
262771
262801
  outcome,
262772
262802
  durationMs: Date.now() - started.startedAt,
262773
- toolArgs: started.args
262803
+ toolArgs: started.args,
262804
+ resultEvidenceKinds: explicitToolResultEvidenceKinds(event.result)
262774
262805
  });
262775
262806
  this.agent.telemetry.track("tool_call", properties);
262776
262807
  this.agent.feedRootMissionContract("result", {
@@ -262875,7 +262906,8 @@ var init_outcome_prompts = __esmMin((() => {}));
262875
262906
  var update_goal_default;
262876
262907
  var init_update_goal$1 = __esmMin((() => {
262877
262908
  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";
262878
- 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. Run a separate observation, test, or counterexample probe and cite that exact successful call plus the claim it supports.\n";
262909
+ 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";
262910
+ 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";
262879
262911
  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";
262880
262912
  }));
262881
262913
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.449",
3
+ "version": "9.1.451",
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": {