@openclawbrain/cli 0.4.13 → 0.4.15

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.
@@ -44,6 +44,32 @@ function sanitizeToken(value) {
44
44
  function slugifyIdentity(value) {
45
45
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
46
46
  }
47
+ function sortSessionRecordsDeterministically(records) {
48
+ return [...records].sort((left, right) => {
49
+ const leftTimestamp = Date.parse(left.timestamp);
50
+ const rightTimestamp = Date.parse(right.timestamp);
51
+ if (leftTimestamp !== rightTimestamp) {
52
+ return leftTimestamp - rightTimestamp;
53
+ }
54
+ const leftId = typeof left.id === "string" ? left.id : "";
55
+ const rightId = typeof right.id === "string" ? right.id : "";
56
+ const leftParentId = typeof left.parentId === "string" ? left.parentId : "";
57
+ const rightParentId = typeof right.parentId === "string" ? right.parentId : "";
58
+ if (rightParentId === leftId && leftParentId !== rightId) {
59
+ return -1;
60
+ }
61
+ if (leftParentId === rightId && rightParentId !== leftId) {
62
+ return 1;
63
+ }
64
+ if (leftId !== rightId) {
65
+ return leftId.localeCompare(rightId);
66
+ }
67
+ if (leftParentId !== rightParentId) {
68
+ return leftParentId.localeCompare(rightParentId);
69
+ }
70
+ return left.type.localeCompare(right.type);
71
+ });
72
+ }
47
73
  function deriveChannel(sessionKey, entry) {
48
74
  const deliveryChannel = normalizeString(entry.deliveryContext?.channel);
49
75
  if (deliveryChannel !== null) {
@@ -256,7 +282,7 @@ export function buildPassiveLearningSessionExportFromOpenClawSessionStore(input)
256
282
  let droppedRuntimeNoiseCount = 0;
257
283
  let nextSequence = sequenceStart;
258
284
  let latestAssistantInteractionId = null;
259
- for (const record of input.records) {
285
+ for (const record of sortSessionRecordsDeterministically(input.records)) {
260
286
  if (record.type !== "message") {
261
287
  continue;
262
288
  }
@@ -451,4 +477,4 @@ export function buildPassiveLearningStoreExportFromOpenClawSessionIndex(input) {
451
477
  warnings: sessions.flatMap((session) => session.warnings)
452
478
  };
453
479
  }
454
- //# sourceMappingURL=local-session-passive-learning.js.map
480
+ //# sourceMappingURL=local-session-passive-learning.js.map
@@ -0,0 +1,11 @@
1
+ import { reindexCandidatePackBuildResultWithEmbedder } from "./local-learner.js";
2
+
3
+ export async function reindexMaterializationCandidateWithEmbedder(materialization, embedder) {
4
+ if (materialization === null || embedder === null) {
5
+ return materialization;
6
+ }
7
+ return {
8
+ ...materialization,
9
+ candidate: await reindexCandidatePackBuildResultWithEmbedder(materialization.candidate, embedder)
10
+ };
11
+ }
@@ -2,6 +2,8 @@ import { type OpenClawBrainInstallLayout } from "./openclaw-plugin-install.js";
2
2
  export type OpenClawBrainHookInstallState = "installed" | "not_installed" | "blocked_by_allowlist" | "unverified";
3
3
  export type OpenClawBrainHookLoadability = "loadable" | "blocked" | "not_installed" | "unverified";
4
4
  export type OpenClawBrainHookLoadProof = "status_probe_ready" | "not_ready";
5
+ export type OpenClawBrainHookGuardSeverity = "none" | "degraded" | "blocking";
6
+ export type OpenClawBrainHookGuardActionability = "none" | "pin_openclaw_home" | "repair_install";
5
7
  export type OpenClawBrainPluginAllowlistState = "unrestricted" | "allowed" | "blocked" | "invalid" | "unverified";
6
8
  export interface OpenClawBrainHookInspection {
7
9
  scope: "exact_openclaw_home" | "activation_root_only";
@@ -24,6 +26,10 @@ export interface OpenClawBrainHookInspection {
24
26
  }
25
27
  export interface OpenClawBrainHookLoadSummary extends OpenClawBrainHookInspection {
26
28
  loadProof: OpenClawBrainHookLoadProof;
29
+ guardSeverity: OpenClawBrainHookGuardSeverity;
30
+ guardActionability: OpenClawBrainHookGuardActionability;
31
+ guardSummary: string;
32
+ guardAction: string;
27
33
  }
28
34
  export declare function inspectOpenClawBrainPluginAllowlist(openclawHome: string): {
29
35
  state: Exclude<OpenClawBrainPluginAllowlistState, "unverified">;
@@ -74,6 +74,32 @@ function describeAdditionalInstallDetail(additionalInstalls) {
74
74
  .map((install) => `${shortenPath(install.extensionDir)} (${describeOpenClawBrainInstallLayout(install.installLayout)}, ${describeOpenClawBrainInstallIdentity(install)})`)
75
75
  .join(", ")}`;
76
76
  }
77
+ function summarizeOpenClawBrainHookGuard(inspection) {
78
+ if (inspection.scope === "activation_root_only") {
79
+ return {
80
+ guardSeverity: "degraded",
81
+ guardActionability: "pin_openclaw_home",
82
+ guardSummary: "current-profile hook state is not self-proven from this status scope",
83
+ guardAction: "Rerun status with --openclaw-home <path> to prove the current profile hook."
84
+ };
85
+ }
86
+ if (inspection.installState === "installed" && inspection.loadability === "loadable") {
87
+ return {
88
+ guardSeverity: "none",
89
+ guardActionability: "none",
90
+ guardSummary: "profile hook is installed and loadable",
91
+ guardAction: "none"
92
+ };
93
+ }
94
+ return {
95
+ guardSeverity: "blocking",
96
+ guardActionability: "repair_install",
97
+ guardSummary: inspection.installState === "not_installed"
98
+ ? "profile hook is missing or incomplete"
99
+ : "profile hook is present but OpenClaw will not load it",
100
+ guardAction: "Run openclawbrain install --openclaw-home <path> to repair the installed hook."
101
+ };
102
+ }
77
103
  export function inspectOpenClawBrainPluginAllowlist(openclawHome) {
78
104
  const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
79
105
  const installedPlugin = findInstalledOpenClawBrainPlugin(openclawHome);
@@ -252,6 +278,7 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
252
278
  export function summarizeOpenClawBrainHookLoad(inspection, statusProbeReady) {
253
279
  return {
254
280
  ...inspection,
281
+ ...summarizeOpenClawBrainHookGuard(inspection),
255
282
  loadProof: inspection.loadability === "loadable" && statusProbeReady
256
283
  ? "status_probe_ready"
257
284
  : "not_ready"
@@ -19,6 +19,22 @@ function normalizeOptionalCliString(value) {
19
19
  return trimmed.length > 0 ? trimmed : null;
20
20
  }
21
21
 
22
+ function normalizeReportedProofPath(filePath) {
23
+ const normalizedPath = normalizeOptionalCliString(filePath);
24
+ if (normalizedPath === null) {
25
+ return null;
26
+ }
27
+ if (normalizedPath === "~") {
28
+ return homedir();
29
+ }
30
+ if (normalizedPath.startsWith("~/")) {
31
+ return path.join(homedir(), normalizedPath.slice(2));
32
+ }
33
+ return path.isAbsolute(normalizedPath)
34
+ ? normalizedPath
35
+ : path.resolve(normalizedPath);
36
+ }
37
+
22
38
  function canonicalizeExistingProofPath(filePath) {
23
39
  const resolvedPath = path.resolve(filePath);
24
40
  try {
@@ -191,6 +207,12 @@ function readJsonSnapshot(filePath) {
191
207
  }
192
208
  }
193
209
 
210
+ function describeStepWarning(step) {
211
+ return step.captureState === "partial"
212
+ ? `${step.stepId} ended as ${step.resultClass} with partial capture`
213
+ : `${step.stepId} ended as ${step.resultClass}`;
214
+ }
215
+
194
216
  function extractStartupBreadcrumbs(logText, bundleStartedAtIso) {
195
217
  if (!logText) {
196
218
  return { all: [], afterBundleStart: [] };
@@ -230,6 +252,135 @@ function extractStatusSignals(statusText) {
230
252
  serveActivePack: /serve\s+state=serving_active_pack/.test(statusText),
231
253
  routeFnAvailable: /routeFn\s+available=yes/.test(statusText),
232
254
  proofPath: statusText.match(/proofPath=([^\s]+)/)?.[1] ?? null,
255
+ proofError: statusText.match(/proofError=([^\s]+)/)?.[1] ?? null,
256
+ };
257
+ }
258
+ function extractDetailedStatusLine(statusText, prefix) {
259
+ const normalizedPrefix = `${prefix} `;
260
+ return statusText.split(/\r?\n/).find((line) => line.startsWith(normalizedPrefix)) ?? null;
261
+ }
262
+ function extractKeyValuePairs(line) {
263
+ if (typeof line !== "string") {
264
+ return {};
265
+ }
266
+ const pairs = {};
267
+ for (const match of line.matchAll(/([A-Za-z][A-Za-z0-9]*)=([^\s]+)/g)) {
268
+ pairs[match[1]] = match[2];
269
+ }
270
+ return pairs;
271
+ }
272
+ function extractAttachedProfileCoverageEntries(line) {
273
+ if (typeof line !== "string") {
274
+ return [];
275
+ }
276
+ const normalized = line.replace(/^attachedSet\s+/, "");
277
+ const proofPathIndex = normalized.indexOf(" proofPath=");
278
+ const entriesText = (proofPathIndex === -1 ? normalized : normalized.slice(0, proofPathIndex)).trim();
279
+ if (entriesText.length === 0 || entriesText === "none") {
280
+ return [];
281
+ }
282
+ const entries = [];
283
+ let index = 0;
284
+ while (index < entriesText.length) {
285
+ while (index < entriesText.length && entriesText[index] === " ") {
286
+ index += 1;
287
+ }
288
+ if (index >= entriesText.length) {
289
+ break;
290
+ }
291
+ const bracketStart = entriesText.indexOf("[", index);
292
+ if (bracketStart === -1) {
293
+ break;
294
+ }
295
+ const bracketEnd = entriesText.indexOf("]", bracketStart + 1);
296
+ if (bracketEnd === -1) {
297
+ break;
298
+ }
299
+ const rawLabel = entriesText.slice(index, bracketStart).trim();
300
+ const fields = extractKeyValuePairs(entriesText.slice(bracketStart + 1, bracketEnd));
301
+ entries.push({
302
+ label: rawLabel.replace(/^\*/, "").trim(),
303
+ current: rawLabel.startsWith("*"),
304
+ hookFiles: fields.hook ?? "unknown",
305
+ configLoad: fields.config ?? "unknown",
306
+ runtimeLoad: fields.runtime ?? "unknown",
307
+ loadedAt: fields.loadedAt ?? null,
308
+ coverageState: fields.hook === "present" && fields.config === "allows_load" && fields.runtime === "proven"
309
+ ? "covered"
310
+ : "attention"
311
+ });
312
+ index = bracketEnd + 1;
313
+ }
314
+ return entries;
315
+ }
316
+ function buildCoverageSnapshot({ attachedSetLine, runtimeLoadProofSnapshot, openclawHome }) {
317
+ const parsedEntries = extractAttachedProfileCoverageEntries(attachedSetLine);
318
+ const proofProfiles = Array.isArray(runtimeLoadProofSnapshot?.value?.profiles)
319
+ ? runtimeLoadProofSnapshot.value.profiles
320
+ : [];
321
+ const profiles = parsedEntries.length > 0
322
+ ? parsedEntries
323
+ : proofProfiles.map((profile) => ({
324
+ label: `${profile?.profileId ?? "current_profile"}@${canonicalizeExistingProofPath(profile?.openclawHome ?? "")}`,
325
+ current: canonicalizeExistingProofPath(profile?.openclawHome ?? "") === canonicalizeExistingProofPath(openclawHome),
326
+ hookFiles: "unknown",
327
+ configLoad: "unknown",
328
+ runtimeLoad: "proven",
329
+ loadedAt: profile?.loadedAt ?? null,
330
+ coverageState: "covered"
331
+ }));
332
+ const runtimeProvenCount = profiles.filter((entry) => entry.runtimeLoad === "proven").length;
333
+ return {
334
+ contract: "openclaw_operator_profile_coverage_snapshot.v1",
335
+ generatedAt: new Date().toISOString(),
336
+ openclawHome: canonicalizeExistingProofPath(openclawHome),
337
+ attachedProfileCount: profiles.length,
338
+ runtimeProofProfileCount: proofProfiles.length,
339
+ hookReadyCount: profiles.filter((entry) => entry.hookFiles === "present").length,
340
+ configReadyCount: profiles.filter((entry) => entry.configLoad === "allows_load").length,
341
+ runtimeProvenCount,
342
+ coverageRate: profiles.length === 0 ? null : runtimeProvenCount / profiles.length,
343
+ profiles
344
+ };
345
+ }
346
+ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdict, statusSignals }) {
347
+ const attachTruth = extractKeyValuePairs(attachTruthLine);
348
+ const serve = extractKeyValuePairs(serveLine);
349
+ const routeFn = extractKeyValuePairs(routeFnLine);
350
+ return {
351
+ contract: "openclaw_operator_hardening_snapshot.v1",
352
+ generatedAt: new Date().toISOString(),
353
+ statusSignals: {
354
+ statusOk: statusSignals.statusOk,
355
+ loadProofReady: statusSignals.loadProofReady,
356
+ runtimeProven: statusSignals.runtimeProven,
357
+ serveActivePack: statusSignals.serveActivePack,
358
+ routeFnAvailable: statusSignals.routeFnAvailable,
359
+ },
360
+ attachTruth: {
361
+ current: attachTruth.current ?? null,
362
+ hook: attachTruth.hook ?? null,
363
+ config: attachTruth.config ?? null,
364
+ runtime: attachTruth.runtime ?? null,
365
+ watcher: attachTruth.watcher ?? null,
366
+ },
367
+ serve: {
368
+ state: serve.state ?? null,
369
+ failOpen: serve.failOpen ?? null,
370
+ hardFail: serve.hardFail ?? null,
371
+ usedRouteFn: serve.usedRouteFn ?? null,
372
+ awaitingFirstExport: serve.awaitingFirstExport ?? null,
373
+ },
374
+ routeFn: {
375
+ available: routeFn.available ?? null,
376
+ freshness: routeFn.freshness ?? null,
377
+ },
378
+ verdict: {
379
+ verdict: verdict.verdict,
380
+ severity: verdict.severity,
381
+ missingProofCount: Array.isArray(verdict.missingProofs) ? verdict.missingProofs.length : 0,
382
+ warningCount: Array.isArray(verdict.warnings) ? verdict.warnings.length : 0,
383
+ }
233
384
  };
234
385
  }
235
386
 
@@ -238,69 +389,106 @@ function hasPackagedHookSource(pluginInspectText) {
238
389
  }
239
390
 
240
391
  function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, openclawHome }) {
241
- const failedStep = steps.find((step) => step.resultClass !== "success" && step.skipped !== true);
242
- if (failedStep) {
243
- return {
244
- verdict: "command_failed",
245
- severity: "blocking",
246
- why: `${failedStep.stepId} exited as ${failedStep.resultClass}`,
247
- };
248
- }
392
+ const failedSteps = steps.filter((step) => step.resultClass !== "success" && step.skipped !== true);
393
+ const failedDetailedStatusStep = failedSteps.find((step) => step.stepId === "05-detailed-status");
249
394
  const gatewayHealthy = /Runtime:\s+running/m.test(gatewayStatus) && /RPC probe:\s+ok/m.test(gatewayStatus);
250
395
  const pluginLoaded = /Status:\s+loaded/m.test(pluginInspect);
251
396
  const packagedHookPath = hasPackagedHookSource(pluginInspect);
252
397
  const breadcrumbLoaded = breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded");
253
398
  const runtimeProofMatched = Array.isArray(runtimeLoadProofSnapshot?.value?.profiles)
254
399
  && runtimeLoadProofSnapshot.value.profiles.some((profile) => canonicalizeExistingProofPath(profile?.openclawHome ?? "") === canonicalizeExistingProofPath(openclawHome));
255
- const missingProofs = [];
256
- if (!gatewayHealthy)
257
- missingProofs.push("gateway_health");
258
- if (!pluginLoaded)
259
- missingProofs.push("plugin_loaded");
260
- if (!packagedHookPath)
261
- missingProofs.push("packaged_hook_path");
400
+ const runtimeTruthGaps = [];
262
401
  if (!statusSignals.statusOk)
263
- missingProofs.push("status_ok");
402
+ runtimeTruthGaps.push("status_ok");
264
403
  if (!statusSignals.loadProofReady)
265
- missingProofs.push("load_proof");
404
+ runtimeTruthGaps.push("load_proof");
266
405
  if (!statusSignals.runtimeProven)
267
- missingProofs.push("runtime_proven");
406
+ runtimeTruthGaps.push("runtime_proven");
268
407
  if (!statusSignals.serveActivePack)
269
- missingProofs.push("serve_active_pack");
408
+ runtimeTruthGaps.push("serve_active_pack");
270
409
  if (!statusSignals.routeFnAvailable)
271
- missingProofs.push("route_fn");
272
- if (!breadcrumbLoaded)
273
- missingProofs.push("startup_breadcrumb");
274
- if (!runtimeProofMatched)
275
- missingProofs.push("runtime_load_proof_record");
276
- if (missingProofs.length === 0) {
410
+ runtimeTruthGaps.push("route_fn");
411
+ const warningCodes = [];
412
+ const warnings = [];
413
+ if (!gatewayHealthy) {
414
+ warningCodes.push("gateway_health");
415
+ warnings.push("gateway status did not confirm runtime running and RPC probe ok");
416
+ }
417
+ if (!pluginLoaded) {
418
+ warningCodes.push("plugin_loaded");
419
+ warnings.push("plugin inspect did not report Status: loaded");
420
+ }
421
+ if (!packagedHookPath) {
422
+ warningCodes.push("packaged_hook_path");
423
+ warnings.push("plugin inspect did not confirm the packaged hook source");
424
+ }
425
+ if (!breadcrumbLoaded) {
426
+ warningCodes.push("startup_breadcrumb");
427
+ warnings.push("startup log did not contain a post-bundle [openclawbrain] BRAIN LOADED breadcrumb");
428
+ }
429
+ if (!runtimeProofMatched) {
430
+ warningCodes.push("runtime_load_proof_record");
431
+ warnings.push(runtimeLoadProofSnapshot.error !== null
432
+ ? `runtime-load-proof snapshot was unreadable: ${runtimeLoadProofSnapshot.error}`
433
+ : runtimeLoadProofSnapshot.exists
434
+ ? "runtime-load-proof snapshot did not include the current openclaw home"
435
+ : "runtime-load-proof snapshot was missing");
436
+ }
437
+ if (statusSignals.proofError !== null && statusSignals.proofError !== "none") {
438
+ warningCodes.push(`proof_error:${statusSignals.proofError}`);
439
+ warnings.push(`detailed status reported proofError=${statusSignals.proofError}`);
440
+ }
441
+ for (const step of failedSteps) {
442
+ warningCodes.push(`step:${step.stepId}:${step.resultClass}:${step.captureState}`);
443
+ warnings.push(describeStepWarning(step));
444
+ }
445
+ const uniqueWarningCodes = [...new Set(warningCodes)];
446
+ const uniqueWarnings = [...new Set(warnings)];
447
+ if (runtimeTruthGaps.length === 0 && uniqueWarningCodes.length === 0) {
277
448
  return {
278
449
  verdict: "success_and_proven",
279
450
  severity: "none",
280
451
  why: "install, restart, gateway health, plugin load, startup breadcrumb, runtime-load-proof record, and detailed status all aligned",
452
+ missingProofs: [],
453
+ warnings: [],
454
+ };
455
+ }
456
+ if (runtimeTruthGaps.length === 0) {
457
+ return {
458
+ verdict: "success_but_proof_incomplete",
459
+ severity: "degraded",
460
+ why: `status/runtime evidence stayed healthy, but proof warnings remained: ${uniqueWarningCodes.join(", ")}`,
461
+ missingProofs: uniqueWarningCodes,
462
+ warnings: uniqueWarnings,
463
+ };
464
+ }
465
+ const hasUsableStatusTruth = statusSignals.statusOk
466
+ || statusSignals.loadProofReady
467
+ || statusSignals.runtimeProven
468
+ || statusSignals.serveActivePack
469
+ || statusSignals.routeFnAvailable;
470
+ if (failedDetailedStatusStep && !hasUsableStatusTruth) {
471
+ return {
472
+ verdict: "command_failed",
473
+ severity: "blocking",
474
+ why: `${failedDetailedStatusStep.stepId} ended as ${failedDetailedStatusStep.resultClass} before runtime truth could be established`,
475
+ missingProofs: runtimeTruthGaps,
476
+ warnings: uniqueWarnings,
281
477
  };
282
478
  }
283
- const blocking = missingProofs.some((item) => [
284
- "gateway_health",
285
- "plugin_loaded",
286
- "packaged_hook_path",
287
- "status_ok",
288
- "load_proof",
289
- "runtime_proven",
290
- "serve_active_pack",
291
- "route_fn",
292
- ].includes(item));
293
479
  return {
294
- verdict: blocking ? "degraded_or_failed_proof" : "success_but_proof_incomplete",
295
- severity: blocking ? "blocking" : "degraded",
296
- why: `missing or conflicting proofs: ${missingProofs.join(", ")}`,
297
- missingProofs,
480
+ verdict: "degraded_or_failed_proof",
481
+ severity: "blocking",
482
+ why: `missing or conflicting runtime truths: ${runtimeTruthGaps.join(", ")}`,
483
+ missingProofs: [...new Set([...runtimeTruthGaps, ...uniqueWarningCodes])],
484
+ warnings: uniqueWarnings,
298
485
  };
299
486
  }
300
487
 
301
- function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot }) {
488
+ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, guardLine, attributionLine, learningPathLine, coverageSnapshot, hardeningSnapshot }) {
302
489
  const passed = [];
303
490
  const missing = [];
491
+ const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
304
492
  if (steps.find((step) => step.stepId === "01-install")?.resultClass === "success") {
305
493
  passed.push("install command succeeded");
306
494
  }
@@ -350,6 +538,36 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
350
538
  "## Missing / incomplete",
351
539
  ...(missing.length === 0 ? ["- none"] : missing.map((item) => `- ${item}`)),
352
540
  "",
541
+ "## Warnings",
542
+ ...(warnings.length === 0 ? ["- none"] : warnings.map((item) => `- ${item}`)),
543
+ "",
544
+ "## Runtime Guard",
545
+ ...(guardLine === null
546
+ ? ["- runtime guard line not reported by detailed status"]
547
+ : [`- ${guardLine}`]),
548
+ "",
549
+ "## Learning Attribution",
550
+ ...(attributionLine === null
551
+ ? ["- attribution line not reported by detailed status"]
552
+ : [`- ${attributionLine}`]),
553
+ ...(learningPathLine === null
554
+ ? []
555
+ : [`- ${learningPathLine}`]),
556
+ "",
557
+ "## Coverage snapshot",
558
+ `- attached profiles: ${coverageSnapshot.attachedProfileCount}`,
559
+ `- runtime-proven profiles: ${coverageSnapshot.runtimeProvenCount}/${coverageSnapshot.attachedProfileCount}`,
560
+ `- coverage rate: ${coverageSnapshot.coverageRate === null ? "none" : coverageSnapshot.coverageRate.toFixed(3)}`,
561
+ ...(coverageSnapshot.profiles.length === 0
562
+ ? ["- per-profile: none"]
563
+ : coverageSnapshot.profiles.map((entry) => `- ${entry.current ? "*" : ""}${entry.label} coverage=${entry.coverageState} hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad} loadedAt=${entry.loadedAt ?? "none"}`)),
564
+ "",
565
+ "## Hardening snapshot",
566
+ `- status signals: statusOk=${hardeningSnapshot.statusSignals.statusOk} loadProofReady=${hardeningSnapshot.statusSignals.loadProofReady} runtimeProven=${hardeningSnapshot.statusSignals.runtimeProven} serveActivePack=${hardeningSnapshot.statusSignals.serveActivePack} routeFnAvailable=${hardeningSnapshot.statusSignals.routeFnAvailable}`,
567
+ `- serve: state=${hardeningSnapshot.serve.state ?? "none"} failOpen=${hardeningSnapshot.serve.failOpen ?? "none"} hardFail=${hardeningSnapshot.serve.hardFail ?? "none"} usedRouteFn=${hardeningSnapshot.serve.usedRouteFn ?? "none"}`,
568
+ `- attachTruth: current=${hardeningSnapshot.attachTruth.current ?? "none"} hook=${hardeningSnapshot.attachTruth.hook ?? "none"} config=${hardeningSnapshot.attachTruth.config ?? "none"} runtime=${hardeningSnapshot.attachTruth.runtime ?? "none"}`,
569
+ `- proof verdict: ${hardeningSnapshot.verdict.verdict} severity=${hardeningSnapshot.verdict.severity} warnings=${hardeningSnapshot.verdict.warningCount}`,
570
+ "",
353
571
  "## Step ledger",
354
572
  ...steps.map((step) => `- ${step.stepId}: ${step.skipped ? "skipped" : `${step.resultClass} (${step.captureState})`} - ${step.summary}`),
355
573
  ];
@@ -570,15 +788,29 @@ export function captureOperatorProofBundle(options) {
570
788
  const statusCapture = addStep("05-detailed-status", "detailed status", cliInvocation.command, [...cliInvocation.args, "status", "--openclaw-home", options.openclawHome, "--detailed"]);
571
789
  const gatewayLogPath = extractGatewayLogPath(gatewayStatusCapture.stdout);
572
790
  const activationRoot = extractActivationRoot(statusCapture.stdout, options.activationRoot ?? null);
573
- const runtimeLoadProofPath = path.join(activationRoot, "attachment-truth", "runtime-load-proofs.json");
791
+ const statusSignals = extractStatusSignals(statusCapture.stdout);
792
+ const attachTruthLine = extractDetailedStatusLine(statusCapture.stdout, "attachTruth");
793
+ const attachedSetLine = extractDetailedStatusLine(statusCapture.stdout, "attachedSet");
794
+ const serveLine = extractDetailedStatusLine(statusCapture.stdout, "serve");
795
+ const routeFnLine = extractDetailedStatusLine(statusCapture.stdout, "routeFn");
796
+ const guardLine = extractDetailedStatusLine(statusCapture.stdout, "guard");
797
+ const attributionLine = extractDetailedStatusLine(statusCapture.stdout, "attribution");
798
+ const learningPathLine = extractDetailedStatusLine(statusCapture.stdout, "path");
799
+ const runtimeLoadProofPath = normalizeReportedProofPath(statusSignals.proofPath)
800
+ ?? path.join(activationRoot, "attachment-truth", "runtime-load-proofs.json");
574
801
  const runtimeLoadProofSnapshot = readJsonSnapshot(runtimeLoadProofPath);
575
802
  const gatewayLogText = readTextIfExists(gatewayLogPath);
576
803
  const breadcrumbs = extractStartupBreadcrumbs(gatewayLogText, bundleStartedAt);
577
- const statusSignals = extractStatusSignals(statusCapture.stdout);
804
+ const coverageSnapshot = buildCoverageSnapshot({
805
+ attachedSetLine,
806
+ runtimeLoadProofSnapshot,
807
+ openclawHome: options.openclawHome,
808
+ });
578
809
  writeText(path.join(bundleDir, "extracted-startup-breadcrumbs.log"), breadcrumbs.all.length === 0
579
810
  ? "<no matching breadcrumbs found>\n"
580
811
  : `${breadcrumbs.all.map((entry) => entry.line).join("\n")}\n`);
581
812
  writeJson(path.join(bundleDir, "runtime-load-proof.json"), runtimeLoadProofSnapshot);
813
+ writeJson(path.join(bundleDir, "coverage-snapshot.json"), coverageSnapshot);
582
814
  const verdict = buildVerdict({
583
815
  steps,
584
816
  gatewayStatus: gatewayStatusCapture.stdout,
@@ -588,6 +820,13 @@ export function captureOperatorProofBundle(options) {
588
820
  runtimeLoadProofSnapshot,
589
821
  openclawHome: options.openclawHome,
590
822
  });
823
+ const hardeningSnapshot = buildHardeningSnapshot({
824
+ attachTruthLine,
825
+ serveLine,
826
+ routeFnLine,
827
+ verdict,
828
+ statusSignals,
829
+ });
591
830
  writeJson(path.join(bundleDir, "steps.json"), {
592
831
  bundleStartedAt,
593
832
  openclawHome: canonicalizeExistingProofPath(options.openclawHome),
@@ -600,6 +839,8 @@ export function captureOperatorProofBundle(options) {
600
839
  bundleStartedAt,
601
840
  verdict,
602
841
  statusSignals,
842
+ coverageSnapshot,
843
+ hardeningSnapshot,
603
844
  breadcrumbs: {
604
845
  allCount: breadcrumbs.all.length,
605
846
  postBundleCount: breadcrumbs.afterBundleStart.length,
@@ -607,7 +848,11 @@ export function captureOperatorProofBundle(options) {
607
848
  },
608
849
  runtimeLoadProofPath,
609
850
  runtimeLoadProofError: runtimeLoadProofSnapshot.error,
851
+ guardLine,
852
+ attributionLine,
853
+ learningPathLine,
610
854
  });
855
+ writeJson(path.join(bundleDir, "hardening-snapshot.json"), hardeningSnapshot);
611
856
  writeText(path.join(bundleDir, "summary.md"), buildSummary({
612
857
  options,
613
858
  steps,
@@ -617,6 +862,11 @@ export function captureOperatorProofBundle(options) {
617
862
  statusSignals,
618
863
  breadcrumbs,
619
864
  runtimeLoadProofSnapshot,
865
+ guardLine,
866
+ attributionLine,
867
+ learningPathLine,
868
+ coverageSnapshot,
869
+ hardeningSnapshot,
620
870
  }));
621
871
  return {
622
872
  ok: true,
@@ -627,14 +877,21 @@ export function captureOperatorProofBundle(options) {
627
877
  gatewayLogPath,
628
878
  runtimeLoadProofPath,
629
879
  runtimeLoadProofSnapshot,
880
+ coverageSnapshot,
881
+ hardeningSnapshot,
630
882
  verdict,
631
883
  statusSignals,
884
+ guardLine,
885
+ attributionLine,
886
+ learningPathLine,
632
887
  steps,
633
888
  summaryPath: path.join(bundleDir, "summary.md"),
634
889
  stepsPath: path.join(bundleDir, "steps.json"),
635
890
  verdictPath: path.join(bundleDir, "verdict.json"),
636
891
  breadcrumbPath: path.join(bundleDir, "extracted-startup-breadcrumbs.log"),
637
892
  runtimeLoadProofSnapshotPath: path.join(bundleDir, "runtime-load-proof.json"),
893
+ coverageSnapshotPath: path.join(bundleDir, "coverage-snapshot.json"),
894
+ hardeningSnapshotPath: path.join(bundleDir, "hardening-snapshot.json"),
638
895
  };
639
896
  }
640
897
 
@@ -649,6 +906,8 @@ export function formatOperatorProofResult(result) {
649
906
  ` Verdict: ${result.verdictPath}`,
650
907
  ` Breadcrumbs: ${result.breadcrumbPath}`,
651
908
  ` Runtime proof: ${result.runtimeLoadProofSnapshotPath}`,
909
+ ` Coverage snapshot: ${result.coverageSnapshotPath}`,
910
+ ` Hardening snapshot: ${result.hardeningSnapshotPath}`,
652
911
  ];
653
912
  return lines.join("\n");
654
913
  }