@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.
- package/README.md +17 -11
- package/dist/extension/index.js +29 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.d.ts +8 -0
- package/dist/extension/runtime-guard.js +100 -12
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +32 -22
- package/dist/src/attachment-truth.js +338 -186
- package/dist/src/cli.d.ts +13 -1
- package/dist/src/cli.js +595 -113
- package/dist/src/index.d.ts +242 -3
- package/dist/src/index.js +1029 -38
- package/dist/src/install-converge.js +217 -0
- package/dist/src/learning-spine.d.ts +2 -1
- package/dist/src/learning-spine.js +49 -19
- package/dist/src/local-learner.d.ts +30 -0
- package/dist/src/local-learner.js +298 -179
- package/dist/src/local-session-passive-learning.js +28 -2
- package/dist/src/materialization-embedder.js +11 -0
- package/dist/src/openclaw-hook-truth.d.ts +6 -0
- package/dist/src/openclaw-hook-truth.js +27 -0
- package/dist/src/proof-command.js +301 -42
- package/dist/src/runtime-core.js +658 -0
- package/dist/src/status-learning-path.js +32 -2
- package/dist/src/teacher-decision-match.js +277 -0
- package/dist/src/teacher-labeler.js +4 -30
- package/dist/src/traced-learning-bridge.js +17 -1
- package/extension/index.ts +35 -4
- package/extension/runtime-guard.ts +92 -14
- package/package.json +4 -3
|
@@ -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
|
|
242
|
-
|
|
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
|
|
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
|
-
|
|
402
|
+
runtimeTruthGaps.push("status_ok");
|
|
264
403
|
if (!statusSignals.loadProofReady)
|
|
265
|
-
|
|
404
|
+
runtimeTruthGaps.push("load_proof");
|
|
266
405
|
if (!statusSignals.runtimeProven)
|
|
267
|
-
|
|
406
|
+
runtimeTruthGaps.push("runtime_proven");
|
|
268
407
|
if (!statusSignals.serveActivePack)
|
|
269
|
-
|
|
408
|
+
runtimeTruthGaps.push("serve_active_pack");
|
|
270
409
|
if (!statusSignals.routeFnAvailable)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!
|
|
275
|
-
|
|
276
|
-
|
|
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:
|
|
295
|
-
severity:
|
|
296
|
-
why: `missing or conflicting
|
|
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
|
|
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
|
|
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
|
}
|