@openclawbrain/cli 0.4.28 → 0.4.30
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/dist/src/cli.js +137 -12
- package/dist/src/install-converge.js +74 -3
- package/dist/src/openclaw-hook-truth.js +138 -62
- package/dist/src/proof-command.js +52 -3
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -33,14 +33,19 @@ const OPENCLAWBRAIN_EMBEDDER_MODEL_ENV = "OPENCLAWBRAIN_EMBEDDER_MODEL";
|
|
|
33
33
|
const OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV = "OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION";
|
|
34
34
|
const LEGACY_COMPAT_PACKAGE_NAME = "@jonathangu/openclawbrain";
|
|
35
35
|
const INSTALL_COMPATIBLE_LOCAL_TEACHER_MODEL_PREFIXES = [
|
|
36
|
+
"gemma4:31b",
|
|
37
|
+
"gemma4:27b",
|
|
38
|
+
"gemma4:12b",
|
|
36
39
|
"unsloth-qwen3.5-27b:q4_k_m",
|
|
37
40
|
"unsloth-qwen3.5-27b",
|
|
41
|
+
"qwen3.5:35b-a3b",
|
|
38
42
|
"qwen3.5:32b",
|
|
39
43
|
"qwen3.5:27b",
|
|
40
44
|
"qwen3.5:14b",
|
|
41
45
|
"qwen3.5:9b",
|
|
42
46
|
"qwen3.5:8b",
|
|
43
47
|
"qwen3:8b",
|
|
48
|
+
"qwen2.5:32b-instruct",
|
|
44
49
|
"qwen2.5:7b"
|
|
45
50
|
];
|
|
46
51
|
const DEFAULT_BOUNDED_JSONL_TAIL_BYTES = 4 * 1024 * 1024;
|
|
@@ -557,7 +562,7 @@ function operatorCliHelp() {
|
|
|
557
562
|
" openclawbrain-ops scan --session <trace.json> --root <path> [options] # compatibility alias",
|
|
558
563
|
"",
|
|
559
564
|
"Options:",
|
|
560
|
-
" --openclaw-home <path> OpenClaw home dir for install/attach/detach/uninstall (e.g. ~/.openclaw-Tern or ~/.openclaw). Also pins status/rollback/context/history/learn to that installed target when applicable.",
|
|
565
|
+
" --openclaw-home <path> OpenClaw home dir for install/attach/detach/uninstall (e.g. ./openclaw-cormorantai, ~/.openclaw-Tern, or ~/.openclaw). Also pins status/rollback/context/history/learn to that installed target when applicable.",
|
|
561
566
|
" --shared Set brain-attachment-policy to shared instead of dedicated (install/attach only).",
|
|
562
567
|
` --skip-embedder-provision Skip the default Ollama ${DEFAULT_OLLAMA_EMBEDDING_MODEL} pull before install/attach bootstrap. Use only when intentionally deferring embedder setup. Also supports ${OPENCLAWBRAIN_INSTALL_SKIP_EMBEDDER_PROVISION_ENV}=1.`,
|
|
563
568
|
" --activation-root <path> Explicit activation root for attach/watch/daemon and other stateful commands; install/attach default to sibling .openclawbrain/activation next to the selected OpenClaw home.",
|
|
@@ -597,6 +602,11 @@ function operatorCliHelp() {
|
|
|
597
602
|
" 9. detach openclawbrain detach --openclaw-home <path> — remove the profile hookup only and keep brain data",
|
|
598
603
|
" 10. uninstall openclawbrain uninstall --openclaw-home <path> --keep-data|--purge-data — remove the hookup and choose the data outcome explicitly",
|
|
599
604
|
"",
|
|
605
|
+
"Examples:",
|
|
606
|
+
" openclawbrain install --openclaw-home ./openclaw-cormorantai",
|
|
607
|
+
" openclawbrain status --openclaw-home ./openclaw-cormorantai --detailed",
|
|
608
|
+
" openclawbrain proof --openclaw-home ./openclaw-cormorantai",
|
|
609
|
+
"",
|
|
600
610
|
"Advanced/operator surfaces:",
|
|
601
611
|
" context preview the brain context that would be injected for a message",
|
|
602
612
|
" rollback preview or apply active <- previous, active -> candidate pointer movement",
|
|
@@ -950,16 +960,69 @@ function summarizeStatusAttachmentTruth(input) {
|
|
|
950
960
|
})
|
|
951
961
|
};
|
|
952
962
|
}
|
|
963
|
+
let cachedCliPackageMetadata = null;
|
|
964
|
+
function resolveCliPackageManifestPath() {
|
|
965
|
+
const candidates = [
|
|
966
|
+
path.resolve(__dirname, "..", "..", "package.json"),
|
|
967
|
+
path.resolve(__dirname, "..", "package.json"),
|
|
968
|
+
];
|
|
969
|
+
for (const candidate of candidates) {
|
|
970
|
+
if (existsSync(candidate)) {
|
|
971
|
+
return candidate;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
throw new Error("OpenClawBrain CLI package manifest not found. Searched:\n" +
|
|
975
|
+
candidates.map((candidate) => ` - ${candidate}`).join("\n"));
|
|
976
|
+
}
|
|
977
|
+
function readCliPackageMetadata() {
|
|
978
|
+
if (cachedCliPackageMetadata !== null) {
|
|
979
|
+
return cachedCliPackageMetadata;
|
|
980
|
+
}
|
|
981
|
+
const manifest = JSON.parse(readFileSync(resolveCliPackageManifestPath(), "utf8"));
|
|
982
|
+
const name = typeof manifest.name === "string" && manifest.name.trim().length > 0
|
|
983
|
+
? manifest.name.trim()
|
|
984
|
+
: "@openclawbrain/cli";
|
|
985
|
+
const version = typeof manifest.version === "string" && manifest.version.trim().length > 0
|
|
986
|
+
? manifest.version.trim()
|
|
987
|
+
: "0.0.0";
|
|
988
|
+
cachedCliPackageMetadata = { name, version };
|
|
989
|
+
return cachedCliPackageMetadata;
|
|
990
|
+
}
|
|
991
|
+
function inspectDaemonRuntimeSurface(activationRoot) {
|
|
992
|
+
const managedInspection = inspectManagedLearnerService(activationRoot);
|
|
993
|
+
if (managedInspection.installed === true ||
|
|
994
|
+
managedInspection.configuredRuntimePath !== null ||
|
|
995
|
+
managedInspection.configuredRuntimePackageSpec !== null ||
|
|
996
|
+
managedInspection.configuredRuntimePackageName !== null ||
|
|
997
|
+
managedInspection.configuredRuntimePackageVersion !== null) {
|
|
998
|
+
return {
|
|
999
|
+
...managedInspection,
|
|
1000
|
+
surfaceIdentitySource: "managed_service"
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
const cliPackageMetadata = readCliPackageMetadata();
|
|
1004
|
+
return {
|
|
1005
|
+
...managedInspection,
|
|
1006
|
+
configuredRuntimePath: canonicalizeExistingCliPath(__filename),
|
|
1007
|
+
configuredRuntimePackageName: cliPackageMetadata.name,
|
|
1008
|
+
configuredRuntimePackageVersion: cliPackageMetadata.version,
|
|
1009
|
+
configuredRuntimePackageSpec: null,
|
|
1010
|
+
surfaceIdentitySource: "current_cli"
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
953
1013
|
function summarizeStatusHotfixBoundary(status) {
|
|
954
1014
|
return describeOpenClawBrainHotfixBoundary({
|
|
955
1015
|
hookInspection: status.hook,
|
|
956
|
-
daemonInspection:
|
|
1016
|
+
daemonInspection: inspectDaemonRuntimeSurface(status.host.activationRoot)
|
|
957
1017
|
});
|
|
958
1018
|
}
|
|
959
1019
|
function formatStatusHotfixBoundarySummary(boundary) {
|
|
960
1020
|
return [
|
|
961
1021
|
`boundary=${boundary.boundary}`,
|
|
962
1022
|
`skew=${boundary.skew}`,
|
|
1023
|
+
`converge=${boundary.convergeState ?? "unverified"}`,
|
|
1024
|
+
`daemonSource=${boundary.daemonSource ?? "unverified"}`,
|
|
1025
|
+
`selectedHome=${boundary.selectedOpenClawHome === null ? "unverified" : shortenPath(boundary.selectedOpenClawHome)}`,
|
|
963
1026
|
`daemon=${boundary.daemonPackage ?? "unverified"}`,
|
|
964
1027
|
`hook=${boundary.hookPackage ?? "unverified"}`
|
|
965
1028
|
].join(" ");
|
|
@@ -1644,6 +1707,8 @@ function readInstallRuntimeFingerprint(openclawHome) {
|
|
|
1644
1707
|
openclawHome,
|
|
1645
1708
|
quiet: true
|
|
1646
1709
|
});
|
|
1710
|
+
const activationRoot = resolvedActivationRoot.trim().length === 0 ? null : path.resolve(resolvedActivationRoot);
|
|
1711
|
+
const daemonSurface = activationRoot === null ? null : inspectDaemonRuntimeSurface(activationRoot);
|
|
1647
1712
|
const { config } = readOpenClawJsonConfig(openclawHome);
|
|
1648
1713
|
return {
|
|
1649
1714
|
selectedInstall: selectedInstall === null
|
|
@@ -1659,10 +1724,15 @@ function readInstallRuntimeFingerprint(openclawHome) {
|
|
|
1659
1724
|
hookPath: hook.hookPath,
|
|
1660
1725
|
hookState: hook.installState,
|
|
1661
1726
|
loadability: hook.loadability,
|
|
1662
|
-
activationRoot
|
|
1727
|
+
activationRoot,
|
|
1663
1728
|
loaderSource: readTextFileIfExists(selectedInstall?.loaderEntryPath ?? null),
|
|
1664
1729
|
runtimeGuardSource: readTextFileIfExists(selectedInstall?.runtimeGuardPath ?? null),
|
|
1665
|
-
pluginsConfig: JSON.stringify(config.plugins ?? null)
|
|
1730
|
+
pluginsConfig: JSON.stringify(config.plugins ?? null),
|
|
1731
|
+
daemonRuntimePath: daemonSurface?.configuredRuntimePath ?? null,
|
|
1732
|
+
daemonRuntimePackageName: daemonSurface?.configuredRuntimePackageName ?? null,
|
|
1733
|
+
daemonRuntimePackageVersion: daemonSurface?.configuredRuntimePackageVersion ?? null,
|
|
1734
|
+
daemonRuntimePackageSpec: daemonSurface?.configuredRuntimePackageSpec ?? null,
|
|
1735
|
+
daemonRuntimeSource: daemonSurface?.surfaceIdentitySource ?? null
|
|
1666
1736
|
};
|
|
1667
1737
|
}
|
|
1668
1738
|
function runOpenClawBrainConvergePluginStep(openclawHome) {
|
|
@@ -1733,7 +1803,22 @@ function inspectInstallConvergeVerification(parsed) {
|
|
|
1733
1803
|
status: normalizedStatusAndReport.status
|
|
1734
1804
|
});
|
|
1735
1805
|
const displayedStatus = summarizeDisplayedStatus(normalizedStatusAndReport.status, installHook);
|
|
1806
|
+
const hotfixBoundary = summarizeStatusHotfixBoundary(normalizedStatusAndReport.status);
|
|
1807
|
+
const surfaceSummary = hotfixBoundary.convergeState === "half_converged"
|
|
1808
|
+
? `daemon runtime and installed hook/runtime-guard are half-converged: ${hotfixBoundary.detail}`
|
|
1809
|
+
: hotfixBoundary.convergeState === "warning"
|
|
1810
|
+
? `daemon runtime and installed hook/runtime-guard identity is not fully proven: ${hotfixBoundary.detail}`
|
|
1811
|
+
: hotfixBoundary.convergeState === "unverified"
|
|
1812
|
+
? `daemon runtime and installed hook/runtime-guard convergence is unverified: ${hotfixBoundary.detail}`
|
|
1813
|
+
: hotfixBoundary.detail;
|
|
1736
1814
|
const routeFn = summarizeStatusRouteFn(normalizedStatusAndReport.status, normalizedStatusAndReport.report);
|
|
1815
|
+
const defaultNextStep = buildStatusNextStep(normalizedStatusAndReport.status, normalizedStatusAndReport.report, {
|
|
1816
|
+
openclawHome: parsed.openclawHome,
|
|
1817
|
+
installHook
|
|
1818
|
+
});
|
|
1819
|
+
const nextStep = hotfixBoundary.convergeState === "half_converged"
|
|
1820
|
+
? `Daemon runtime and installed hook/runtime-guard are half-converged for ${shortenPath(parsed.openclawHome)} (${hotfixBoundary.skew}); refresh the daemon-side CLI/runtime surface, then rerun ${buildInstallCommand(parsed.openclawHome)}.`
|
|
1821
|
+
: defaultNextStep;
|
|
1737
1822
|
return {
|
|
1738
1823
|
targetInspection,
|
|
1739
1824
|
status: normalizedStatusAndReport.status,
|
|
@@ -1742,11 +1827,9 @@ function inspectInstallConvergeVerification(parsed) {
|
|
|
1742
1827
|
attachmentTruth,
|
|
1743
1828
|
displayedStatus,
|
|
1744
1829
|
routeFn,
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
}),
|
|
1749
|
-
summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; guard=${normalizedStatusAndReport.status.hook.guardSeverity}/${normalizedStatusAndReport.status.hook.guardActionability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
|
|
1830
|
+
hotfixBoundary,
|
|
1831
|
+
nextStep,
|
|
1832
|
+
summaryLine: `STATUS ${displayedStatus}; hook=${installHook.state}/${installHook.loadability}; surface=${hotfixBoundary.convergeState}/${hotfixBoundary.skew}; guard=${normalizedStatusAndReport.status.hook.guardSeverity}/${normalizedStatusAndReport.status.hook.guardActionability}; runtime=${attachmentTruth.runtimeLoad}; loadProof=${normalizedStatusAndReport.status.hook.loadProof}; serve=${normalizedStatusAndReport.status.brainStatus.serveState}`,
|
|
1750
1833
|
facts: {
|
|
1751
1834
|
installLayout: normalizedStatusAndReport.status.hook.installLayout ?? installHook.installLayout ?? null,
|
|
1752
1835
|
installState: installHook.state,
|
|
@@ -1758,7 +1841,17 @@ function inspectInstallConvergeVerification(parsed) {
|
|
|
1758
1841
|
loadProof: normalizedStatusAndReport.status.hook.loadProof,
|
|
1759
1842
|
serveState: normalizedStatusAndReport.status.brainStatus.serveState,
|
|
1760
1843
|
routeFnAvailable: routeFn.available,
|
|
1761
|
-
awaitingFirstExport: normalizedStatusAndReport.status.brainStatus.awaitingFirstExport
|
|
1844
|
+
awaitingFirstExport: normalizedStatusAndReport.status.brainStatus.awaitingFirstExport,
|
|
1845
|
+
surfaceBoundary: hotfixBoundary.boundary,
|
|
1846
|
+
surfaceSkew: hotfixBoundary.skew,
|
|
1847
|
+
surfaceConvergeState: hotfixBoundary.convergeState,
|
|
1848
|
+
surfaceSummary,
|
|
1849
|
+
daemonPackage: hotfixBoundary.daemonPackage,
|
|
1850
|
+
hookPackage: hotfixBoundary.hookPackage,
|
|
1851
|
+
daemonPath: hotfixBoundary.daemonPath,
|
|
1852
|
+
hookPath: hotfixBoundary.hookPath,
|
|
1853
|
+
runtimeGuardPath: hotfixBoundary.runtimeGuardPath,
|
|
1854
|
+
selectedOpenClawHome: hotfixBoundary.selectedOpenClawHome
|
|
1762
1855
|
}
|
|
1763
1856
|
};
|
|
1764
1857
|
}
|
|
@@ -4060,6 +4153,8 @@ function emitInstallConvergeResult(result, parsed) {
|
|
|
4060
4153
|
console.log(`Attach: ${result.attach.detail}`);
|
|
4061
4154
|
console.log(`Restart: ${result.restart.detail}`);
|
|
4062
4155
|
console.log(`Verify: ${result.verification.summaryLine}`);
|
|
4156
|
+
console.log(`Surface: ${result.verification.surface.summaryLine}`);
|
|
4157
|
+
console.log(`Paths: ${result.verification.surface.pathsLine}`);
|
|
4063
4158
|
console.log(`Verdict: ${result.verdict.verdict}`);
|
|
4064
4159
|
console.log(`Why: ${result.verdict.why}`);
|
|
4065
4160
|
if (result.verdict.warnings.length > 0) {
|
|
@@ -4121,7 +4216,22 @@ function runInstallCommand(parsed) {
|
|
|
4121
4216
|
loadProof: "unknown",
|
|
4122
4217
|
serveState: "unknown",
|
|
4123
4218
|
routeFnAvailable: false,
|
|
4124
|
-
awaitingFirstExport: false
|
|
4219
|
+
awaitingFirstExport: false,
|
|
4220
|
+
surface: {
|
|
4221
|
+
summaryLine: "daemon/runtime hook surface identity was not verified because install failed earlier",
|
|
4222
|
+
pathsLine: "daemonPath=none hookPath=unverified runtimeGuard=unverified",
|
|
4223
|
+
boundary: "unknown",
|
|
4224
|
+
skew: "unknown",
|
|
4225
|
+
convergeState: "unknown",
|
|
4226
|
+
daemonSource: "unknown",
|
|
4227
|
+
daemonPackage: null,
|
|
4228
|
+
hookPackage: null,
|
|
4229
|
+
detail: "install failed before daemon/runtime hook surface identity could be verified",
|
|
4230
|
+
daemonPath: null,
|
|
4231
|
+
hookPath: null,
|
|
4232
|
+
runtimeGuardPath: null,
|
|
4233
|
+
selectedOpenClawHome: parsed.openclawHome
|
|
4234
|
+
}
|
|
4125
4235
|
},
|
|
4126
4236
|
verdict
|
|
4127
4237
|
};
|
|
@@ -4206,7 +4316,22 @@ function runInstallCommand(parsed) {
|
|
|
4206
4316
|
loadProof: verificationSnapshot.facts.loadProof,
|
|
4207
4317
|
serveState: verificationSnapshot.facts.serveState,
|
|
4208
4318
|
routeFnAvailable: verificationSnapshot.facts.routeFnAvailable,
|
|
4209
|
-
awaitingFirstExport: verificationSnapshot.facts.awaitingFirstExport
|
|
4319
|
+
awaitingFirstExport: verificationSnapshot.facts.awaitingFirstExport,
|
|
4320
|
+
surface: {
|
|
4321
|
+
summaryLine: `boundary=${verificationSnapshot.hotfixBoundary.boundary}; skew=${verificationSnapshot.hotfixBoundary.skew}; converge=${verificationSnapshot.hotfixBoundary.convergeState}; daemonSource=${verificationSnapshot.hotfixBoundary.daemonSource}; selectedHome=${verificationSnapshot.hotfixBoundary.selectedOpenClawHome === null ? "unverified" : shortenPath(verificationSnapshot.hotfixBoundary.selectedOpenClawHome)}; daemon=${verificationSnapshot.hotfixBoundary.daemonPackage ?? "unverified"}; hook=${verificationSnapshot.hotfixBoundary.hookPackage ?? "unverified"}; detail=${verificationSnapshot.hotfixBoundary.detail}`,
|
|
4322
|
+
pathsLine: `daemonPath=${verificationSnapshot.hotfixBoundary.daemonPath === null ? "none" : shortenPath(verificationSnapshot.hotfixBoundary.daemonPath)} hookPath=${verificationSnapshot.hotfixBoundary.hookPath === null ? "unverified" : shortenPath(verificationSnapshot.hotfixBoundary.hookPath)} runtimeGuard=${verificationSnapshot.hotfixBoundary.runtimeGuardPath === null ? "unverified" : shortenPath(verificationSnapshot.hotfixBoundary.runtimeGuardPath)}`,
|
|
4323
|
+
boundary: verificationSnapshot.hotfixBoundary.boundary,
|
|
4324
|
+
skew: verificationSnapshot.hotfixBoundary.skew,
|
|
4325
|
+
convergeState: verificationSnapshot.hotfixBoundary.convergeState,
|
|
4326
|
+
daemonSource: verificationSnapshot.hotfixBoundary.daemonSource,
|
|
4327
|
+
daemonPackage: verificationSnapshot.hotfixBoundary.daemonPackage,
|
|
4328
|
+
hookPackage: verificationSnapshot.hotfixBoundary.hookPackage,
|
|
4329
|
+
detail: verificationSnapshot.hotfixBoundary.detail,
|
|
4330
|
+
daemonPath: verificationSnapshot.hotfixBoundary.daemonPath,
|
|
4331
|
+
hookPath: verificationSnapshot.hotfixBoundary.hookPath,
|
|
4332
|
+
runtimeGuardPath: verificationSnapshot.hotfixBoundary.runtimeGuardPath,
|
|
4333
|
+
selectedOpenClawHome: verificationSnapshot.hotfixBoundary.selectedOpenClawHome
|
|
4334
|
+
}
|
|
4210
4335
|
},
|
|
4211
4336
|
verdict,
|
|
4212
4337
|
underlyingInstall: attachResult
|
|
@@ -10,8 +10,22 @@ const CHANGE_REASON_LABELS = {
|
|
|
10
10
|
loader_source: "loader source changed",
|
|
11
11
|
runtime_guard_source: "runtime-guard source changed",
|
|
12
12
|
plugins_config: "OpenClaw plugins config changed",
|
|
13
|
+
daemon_runtime_path: "daemon runtime path changed",
|
|
14
|
+
daemon_runtime_identity: "daemon runtime identity changed",
|
|
13
15
|
};
|
|
14
16
|
|
|
17
|
+
const GATEWAY_RESTART_CHANGE_REASONS = new Set([
|
|
18
|
+
"install_identity",
|
|
19
|
+
"install_layout",
|
|
20
|
+
"hook_path",
|
|
21
|
+
"hook_state",
|
|
22
|
+
"loadability",
|
|
23
|
+
"activation_root",
|
|
24
|
+
"loader_source",
|
|
25
|
+
"runtime_guard_source",
|
|
26
|
+
"plugins_config",
|
|
27
|
+
]);
|
|
28
|
+
|
|
15
29
|
function sameValue(left, right) {
|
|
16
30
|
return left === right;
|
|
17
31
|
}
|
|
@@ -28,6 +42,32 @@ function installIdentityOf(fingerprint) {
|
|
|
28
42
|
});
|
|
29
43
|
}
|
|
30
44
|
|
|
45
|
+
function daemonRuntimeIdentityOf(fingerprint) {
|
|
46
|
+
return JSON.stringify({
|
|
47
|
+
runtimePackageName: fingerprint?.daemonRuntimePackageName ?? null,
|
|
48
|
+
runtimePackageVersion: fingerprint?.daemonRuntimePackageVersion ?? null,
|
|
49
|
+
runtimePackageSpec: fingerprint?.daemonRuntimePackageSpec ?? null,
|
|
50
|
+
runtimeSource: fingerprint?.daemonRuntimeSource ?? null,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function describeSurfaceHalfConvergedReason(input) {
|
|
55
|
+
const boundary = input.surfaceBoundary ?? null;
|
|
56
|
+
const skew = input.surfaceSkew ?? null;
|
|
57
|
+
const selectedOpenClawHome = input.selectedOpenClawHome ?? null;
|
|
58
|
+
const daemonPackage = input.daemonPackage ?? null;
|
|
59
|
+
const hookPackage = input.hookPackage ?? null;
|
|
60
|
+
const boundarySuffix = boundary === null
|
|
61
|
+
? ""
|
|
62
|
+
: skew === null || skew === "unverified"
|
|
63
|
+
? ` (${boundary})`
|
|
64
|
+
: ` (${boundary}/${skew})`;
|
|
65
|
+
const packageSuffix = daemonPackage === null && hookPackage === null
|
|
66
|
+
? ""
|
|
67
|
+
: `; daemon=${daemonPackage ?? "unverified"} hook=${hookPackage ?? "unverified"}`;
|
|
68
|
+
return `daemon runtime and installed hook/runtime-guard are half-converged${selectedOpenClawHome === null ? "" : ` for ${selectedOpenClawHome}`}${boundarySuffix}${packageSuffix}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
31
71
|
export function shouldReplaceOpenClawBrainInstallBeforeConverge(fingerprint) {
|
|
32
72
|
const selectedInstall = fingerprint?.selectedInstall ?? null;
|
|
33
73
|
if (selectedInstall === null) {
|
|
@@ -99,6 +139,12 @@ export function diffOpenClawBrainConvergeRuntimeFingerprint(before, after) {
|
|
|
99
139
|
if (!sameValue(before?.pluginsConfig ?? null, after?.pluginsConfig ?? null)) {
|
|
100
140
|
reasons.push("plugins_config");
|
|
101
141
|
}
|
|
142
|
+
if (!sameValue(before?.daemonRuntimePath ?? null, after?.daemonRuntimePath ?? null)) {
|
|
143
|
+
reasons.push("daemon_runtime_path");
|
|
144
|
+
}
|
|
145
|
+
if (!sameValue(daemonRuntimeIdentityOf(before), daemonRuntimeIdentityOf(after))) {
|
|
146
|
+
reasons.push("daemon_runtime_identity");
|
|
147
|
+
}
|
|
102
148
|
return {
|
|
103
149
|
changed: reasons.length > 0,
|
|
104
150
|
reasons,
|
|
@@ -114,12 +160,15 @@ export function describeOpenClawBrainConvergeChangeReasons(reasons) {
|
|
|
114
160
|
|
|
115
161
|
export function buildOpenClawBrainConvergeRestartPlan(input) {
|
|
116
162
|
const changeReasons = input.changeReasons ?? [];
|
|
117
|
-
|
|
163
|
+
const gatewayRestartReasons = changeReasons.filter((reason) => GATEWAY_RESTART_CHANGE_REASONS.has(reason));
|
|
164
|
+
if (gatewayRestartReasons.length === 0) {
|
|
118
165
|
return {
|
|
119
166
|
required: false,
|
|
120
167
|
automatic: false,
|
|
121
|
-
reason: "no_runtime_affecting_changes",
|
|
122
|
-
detail:
|
|
168
|
+
reason: changeReasons.length === 0 ? "no_runtime_affecting_changes" : "daemon_only_runtime_changes",
|
|
169
|
+
detail: changeReasons.length === 0
|
|
170
|
+
? "Skipped gateway restart because converge did not change plugin files, hook wiring, or the pinned activation root."
|
|
171
|
+
: "Skipped gateway restart because converge only changed the daemon runtime surface; the selected OpenClaw hook/runtime-guard wiring did not change.",
|
|
123
172
|
};
|
|
124
173
|
}
|
|
125
174
|
if (input.profileName === null) {
|
|
@@ -149,6 +198,13 @@ export function classifyOpenClawBrainConvergeVerification(input) {
|
|
|
149
198
|
const loadProof = input.loadProof ?? "unverified";
|
|
150
199
|
const serveState = input.serveState ?? "unknown";
|
|
151
200
|
const installedPackageName = input.installedPackageName ?? null;
|
|
201
|
+
const surfaceConvergeState = input.surfaceConvergeState ?? null;
|
|
202
|
+
const surfaceBoundary = input.surfaceBoundary ?? null;
|
|
203
|
+
const surfaceSkew = input.surfaceSkew ?? null;
|
|
204
|
+
const selectedOpenClawHome = input.selectedOpenClawHome ?? null;
|
|
205
|
+
const daemonPackage = input.daemonPackage ?? null;
|
|
206
|
+
const hookPackage = input.hookPackage ?? null;
|
|
207
|
+
const surfaceSummary = input.surfaceSummary ?? null;
|
|
152
208
|
if (installedPackageName === LEGACY_COMPAT_PACKAGE_NAME) {
|
|
153
209
|
blockingReasons.push(`installed plugin still references the retired compatibility package ${LEGACY_COMPAT_PACKAGE_NAME}; converge must replace it with @openclawbrain/openclaw`);
|
|
154
210
|
}
|
|
@@ -164,6 +220,15 @@ export function classifyOpenClawBrainConvergeVerification(input) {
|
|
|
164
220
|
if (displayedStatus === "fail") {
|
|
165
221
|
blockingReasons.push("status still reports fail");
|
|
166
222
|
}
|
|
223
|
+
if (surfaceConvergeState === "half_converged") {
|
|
224
|
+
blockingReasons.push(surfaceSummary ?? describeSurfaceHalfConvergedReason({
|
|
225
|
+
surfaceBoundary,
|
|
226
|
+
surfaceSkew,
|
|
227
|
+
selectedOpenClawHome,
|
|
228
|
+
daemonPackage,
|
|
229
|
+
hookPackage,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
167
232
|
const runtimeTruthAlreadyProven = runtimeLoad === "proven"
|
|
168
233
|
&& loadProof === "status_probe_ready"
|
|
169
234
|
&& installState === "installed"
|
|
@@ -182,6 +247,12 @@ export function classifyOpenClawBrainConvergeVerification(input) {
|
|
|
182
247
|
if (input.restartRequired === true && input.restartPerformed !== true && runtimeTruthAlreadyProven) {
|
|
183
248
|
warnings.push("automatic restart was not performed because install could not infer an exact OpenClaw profile token, but current status already proves runtime load");
|
|
184
249
|
}
|
|
250
|
+
if (surfaceConvergeState === "warning") {
|
|
251
|
+
warnings.push(surfaceSummary ?? "daemon runtime vs installed hook/runtime-guard identity is not fully proven from this verification snapshot");
|
|
252
|
+
}
|
|
253
|
+
if (surfaceConvergeState === "unverified") {
|
|
254
|
+
warnings.push(surfaceSummary ?? "daemon runtime vs installed hook/runtime-guard convergence is not fully proven from this verification snapshot");
|
|
255
|
+
}
|
|
185
256
|
// When runtime proof is green for the repaired profile, promote the proof
|
|
186
257
|
// surface to healthy rather than leaving stale degraded warnings that
|
|
187
258
|
// contradict the proven truth.
|
|
@@ -66,6 +66,58 @@ function normalizeSurfacePath(filePath) {
|
|
|
66
66
|
? path.resolve(filePath)
|
|
67
67
|
: null;
|
|
68
68
|
}
|
|
69
|
+
function classifyRuntimeSurfaceConvergeState(input) {
|
|
70
|
+
const reasons = [];
|
|
71
|
+
if (input.scope === "activation_root_only") {
|
|
72
|
+
reasons.push("selected_openclaw_home_unverified");
|
|
73
|
+
return {
|
|
74
|
+
state: "unverified",
|
|
75
|
+
reasons
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (input.daemonPath === null) {
|
|
79
|
+
reasons.push("daemon_surface_unverified");
|
|
80
|
+
}
|
|
81
|
+
if (input.hookPath === null && input.runtimeGuardPath === null) {
|
|
82
|
+
reasons.push("installed_surface_unverified");
|
|
83
|
+
return {
|
|
84
|
+
state: input.daemonPath === null ? "unverified" : "half_converged",
|
|
85
|
+
reasons
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (input.hookLoadability !== "loadable") {
|
|
89
|
+
reasons.push("installed_surface_not_loadable");
|
|
90
|
+
return {
|
|
91
|
+
state: input.daemonPath === null ? "unverified" : "half_converged",
|
|
92
|
+
reasons
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (input.daemonPath === null) {
|
|
96
|
+
return {
|
|
97
|
+
state: "unverified",
|
|
98
|
+
reasons
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (input.samePath) {
|
|
102
|
+
reasons.push("same_path");
|
|
103
|
+
return {
|
|
104
|
+
state: "converged",
|
|
105
|
+
reasons
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (input.daemonVersion !== null && input.hookVersion !== null) {
|
|
109
|
+
reasons.push(input.daemonVersion === input.hookVersion ? "split_path_same_version" : "version_skew");
|
|
110
|
+
return {
|
|
111
|
+
state: input.daemonVersion === input.hookVersion ? "converged" : "half_converged",
|
|
112
|
+
reasons
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
reasons.push("version_unverified");
|
|
116
|
+
return {
|
|
117
|
+
state: "unverified",
|
|
118
|
+
reasons
|
|
119
|
+
};
|
|
120
|
+
}
|
|
69
121
|
function inspectInstalledHookActivationRoot(loaderEntryPath) {
|
|
70
122
|
let content;
|
|
71
123
|
try {
|
|
@@ -328,77 +380,101 @@ export function describeOpenClawBrainHotfixBoundary(input) {
|
|
|
328
380
|
const daemonPath = normalizeSurfacePath(daemonInspection?.configuredRuntimePath ?? null);
|
|
329
381
|
const hookPath = normalizeSurfacePath(hookInspection.hookPath);
|
|
330
382
|
const runtimeGuardPath = normalizeSurfacePath(hookInspection.runtimeGuardPath);
|
|
383
|
+
const selectedOpenClawHome = typeof hookInspection.openclawHome === "string" && hookInspection.openclawHome.trim().length > 0
|
|
384
|
+
? path.resolve(hookInspection.openclawHome)
|
|
385
|
+
: null;
|
|
386
|
+
const daemonSource = daemonInspection?.surfaceIdentitySource ?? (daemonPath === null ? "unverified" : "managed_service");
|
|
387
|
+
const daemonPackageName = daemonInspection?.configuredRuntimePackageName ?? null;
|
|
388
|
+
const daemonPackageVersion = daemonInspection?.configuredRuntimePackageVersion ?? null;
|
|
389
|
+
const hookPackageName = hookInspection.packageName ?? null;
|
|
390
|
+
const hookPackageVersion = hookInspection.packageVersion ?? null;
|
|
331
391
|
const daemonPackage = formatPackageIdentity(daemonInspection?.configuredRuntimePackageName ?? null, daemonInspection?.configuredRuntimePackageVersion ?? null, daemonInspection?.configuredRuntimePackageSpec ?? null);
|
|
332
392
|
const hookPackage = formatPackageIdentity(hookInspection.packageName, hookInspection.packageVersion);
|
|
393
|
+
const samePath = daemonPath === hookPath || daemonPath === runtimeGuardPath;
|
|
394
|
+
const daemonVersion = daemonPackageVersion;
|
|
395
|
+
const hookVersion = hookPackageVersion;
|
|
396
|
+
let boundary;
|
|
397
|
+
let skew;
|
|
398
|
+
let displayedHookPath = hookPath;
|
|
399
|
+
let displayedRuntimeGuardPath = runtimeGuardPath;
|
|
400
|
+
let displayedDaemonPackage = daemonPackage;
|
|
401
|
+
let displayedHookPackage = hookPackage;
|
|
402
|
+
let guidance;
|
|
403
|
+
let detail;
|
|
333
404
|
if (hookInspection.scope === "activation_root_only") {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
detail: daemonPath === null
|
|
344
|
-
? "activation-root-only status does not expose the installed hook/runtime-guard surface."
|
|
345
|
-
: `daemon runtime path ${shortenPath(daemonPath)} is visible, but activation-root-only status still does not expose the installed hook/runtime-guard surface.`
|
|
346
|
-
};
|
|
405
|
+
boundary = "hook_surface_unverified";
|
|
406
|
+
skew = "unverified";
|
|
407
|
+
displayedHookPath = null;
|
|
408
|
+
displayedRuntimeGuardPath = null;
|
|
409
|
+
displayedHookPackage = null;
|
|
410
|
+
guidance = "Pin --openclaw-home before patching the installed hook/runtime-guard surface; activation-root-only status does not prove which OpenClaw install you would be changing.";
|
|
411
|
+
detail = daemonPath === null
|
|
412
|
+
? "activation-root-only status does not expose the installed hook/runtime-guard surface."
|
|
413
|
+
: `daemon runtime path ${shortenPath(daemonPath)} is visible, but activation-root-only status still does not expose the installed hook/runtime-guard surface.`;
|
|
347
414
|
}
|
|
348
|
-
if (hookPath === null && runtimeGuardPath === null) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
: "Patch the daemon runtime path for background watch fixes, but repair or reinstall the installed hook/runtime-guard surface before patching OpenClaw load behavior.",
|
|
360
|
-
detail: daemonPath === null
|
|
361
|
-
? "no verified daemon runtime path or installed hook/runtime-guard path is visible from this status snapshot."
|
|
362
|
-
: `daemon runtime path ${shortenPath(daemonPath)} is visible, but the installed hook/runtime-guard surface is not yet loadable.`
|
|
363
|
-
};
|
|
415
|
+
else if (hookPath === null && runtimeGuardPath === null) {
|
|
416
|
+
boundary = "hook_surface_unverified";
|
|
417
|
+
skew = "unverified";
|
|
418
|
+
displayedHookPath = null;
|
|
419
|
+
displayedRuntimeGuardPath = null;
|
|
420
|
+
guidance = daemonPath === null
|
|
421
|
+
? "Repair or reinstall the installed hook surface before patching OpenClaw load behavior."
|
|
422
|
+
: "Patch the daemon runtime path for background watch fixes, but repair or reinstall the installed hook/runtime-guard surface before patching OpenClaw load behavior.";
|
|
423
|
+
detail = daemonPath === null
|
|
424
|
+
? "no verified daemon runtime path or installed hook/runtime-guard path is visible from this status snapshot."
|
|
425
|
+
: `daemon runtime path ${shortenPath(daemonPath)} is visible, but the installed hook/runtime-guard surface is not yet loadable.`;
|
|
364
426
|
}
|
|
365
|
-
if (daemonPath === null) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
runtimeGuardPath,
|
|
372
|
-
daemonPackage: null,
|
|
373
|
-
hookPackage,
|
|
374
|
-
guidance: "Patch the installed hook/runtime-guard surface for OpenClaw load fixes. No configured daemon runtime path is visible from status.",
|
|
375
|
-
detail: `installed hook loads from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${runtimeGuardPath === null ? "" : ` with runtime-guard ${shortenPath(runtimeGuardPath)}`}, but no configured daemon runtime path is visible.`
|
|
376
|
-
};
|
|
427
|
+
else if (daemonPath === null) {
|
|
428
|
+
boundary = "daemon_surface_only";
|
|
429
|
+
skew = "unverified";
|
|
430
|
+
displayedDaemonPackage = null;
|
|
431
|
+
guidance = "Patch the installed hook/runtime-guard surface for OpenClaw load fixes. No configured daemon runtime path is visible from status.";
|
|
432
|
+
detail = `installed hook loads from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${runtimeGuardPath === null ? "" : ` with runtime-guard ${shortenPath(runtimeGuardPath)}`}, but no configured daemon runtime path is visible.`;
|
|
377
433
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
434
|
+
else {
|
|
435
|
+
skew = samePath
|
|
436
|
+
? "same_path"
|
|
437
|
+
: daemonVersion !== null && hookVersion !== null
|
|
438
|
+
? daemonVersion === hookVersion
|
|
439
|
+
? "split_path_same_version"
|
|
440
|
+
: "split_path_version_skew"
|
|
441
|
+
: "split_path_version_unverified";
|
|
442
|
+
boundary = samePath ? "same_surface" : "split_surfaces";
|
|
443
|
+
guidance = samePath
|
|
444
|
+
? "Daemon and installed hook paths currently collapse to the same file; verify both surfaces before patching anyway."
|
|
445
|
+
: "Patch the daemon runtime path for background watch/learner fixes. Patch the installed hook/runtime-guard paths for OpenClaw load fixes.";
|
|
446
|
+
detail = samePath
|
|
447
|
+
? `daemon runtime path ${shortenPath(daemonPath)} currently resolves to the same file as the installed OpenClaw hook surface.`
|
|
448
|
+
: `daemon background watch runs from ${shortenPath(daemonPath)}${daemonPackage === null ? "" : ` (${daemonPackage})`}; OpenClaw loads the installed hook from ${hookPath === null ? "unverified" : shortenPath(hookPath)}${hookPackage === null ? "" : ` (${hookPackage})`}${runtimeGuardPath === null ? "" : ` and runtime-guard ${shortenPath(runtimeGuardPath)}`}.`;
|
|
449
|
+
}
|
|
450
|
+
const converge = classifyRuntimeSurfaceConvergeState({
|
|
451
|
+
scope: hookInspection.scope,
|
|
452
|
+
daemonPath,
|
|
453
|
+
hookPath: displayedHookPath,
|
|
454
|
+
runtimeGuardPath: displayedRuntimeGuardPath,
|
|
455
|
+
hookLoadability: hookInspection.loadability,
|
|
456
|
+
daemonVersion,
|
|
457
|
+
hookVersion,
|
|
458
|
+
samePath
|
|
459
|
+
});
|
|
388
460
|
return {
|
|
389
|
-
boundary
|
|
461
|
+
boundary,
|
|
390
462
|
skew,
|
|
391
463
|
daemonPath,
|
|
392
|
-
hookPath,
|
|
393
|
-
runtimeGuardPath,
|
|
394
|
-
daemonPackage,
|
|
395
|
-
hookPackage,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
464
|
+
hookPath: displayedHookPath,
|
|
465
|
+
runtimeGuardPath: displayedRuntimeGuardPath,
|
|
466
|
+
daemonPackage: displayedDaemonPackage,
|
|
467
|
+
hookPackage: displayedHookPackage,
|
|
468
|
+
daemonPackageName,
|
|
469
|
+
daemonPackageVersion,
|
|
470
|
+
hookPackageName,
|
|
471
|
+
hookPackageVersion,
|
|
472
|
+
daemonSource,
|
|
473
|
+
selectedOpenClawHome,
|
|
474
|
+
convergeState: converge.state,
|
|
475
|
+
convergeReasons: converge.reasons,
|
|
476
|
+
guidance,
|
|
477
|
+
detail
|
|
402
478
|
};
|
|
403
479
|
}
|
|
404
480
|
//# sourceMappingURL=openclaw-hook-truth.js.map
|
|
@@ -279,6 +279,7 @@ function extractStartupBreadcrumbs(logText, bundleStartedAtIso) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
function extractStatusSignals(statusText) {
|
|
282
|
+
const surface = extractKeyValuePairs(extractDetailedStatusLine(statusText, "surface"));
|
|
282
283
|
return {
|
|
283
284
|
statusOk: /^STATUS ok$/m.test(statusText),
|
|
284
285
|
loadProofReady: /loadProof=status_probe_ready/.test(statusText),
|
|
@@ -288,6 +289,11 @@ function extractStatusSignals(statusText) {
|
|
|
288
289
|
routeFnAvailable: /routeFn\s+available=yes/.test(statusText),
|
|
289
290
|
proofPath: statusText.match(/proofPath=([^\s]+)/)?.[1] ?? null,
|
|
290
291
|
proofError: statusText.match(/proofError=([^\s]+)/)?.[1] ?? null,
|
|
292
|
+
surfaceBoundary: surface.boundary ?? null,
|
|
293
|
+
surfaceSkew: surface.skew ?? null,
|
|
294
|
+
surfaceConvergeState: surface.converge ?? null,
|
|
295
|
+
surfaceDaemonSource: surface.daemonSource ?? null,
|
|
296
|
+
surfaceSelectedHome: surface.selectedHome ?? null,
|
|
291
297
|
};
|
|
292
298
|
}
|
|
293
299
|
function extractDetailedStatusLine(statusText, prefix) {
|
|
@@ -378,10 +384,11 @@ function buildCoverageSnapshot({ attachedSetLine, runtimeLoadProofSnapshot, open
|
|
|
378
384
|
profiles
|
|
379
385
|
};
|
|
380
386
|
}
|
|
381
|
-
function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdict, statusSignals }) {
|
|
387
|
+
function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, surfaceLine, verdict, statusSignals }) {
|
|
382
388
|
const attachTruth = extractKeyValuePairs(attachTruthLine);
|
|
383
389
|
const serve = extractKeyValuePairs(serveLine);
|
|
384
390
|
const routeFn = extractKeyValuePairs(routeFnLine);
|
|
391
|
+
const surface = extractKeyValuePairs(surfaceLine);
|
|
385
392
|
return {
|
|
386
393
|
contract: "openclaw_operator_hardening_snapshot.v1",
|
|
387
394
|
generatedAt: new Date().toISOString(),
|
|
@@ -391,6 +398,7 @@ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdi
|
|
|
391
398
|
runtimeProven: statusSignals.runtimeProven,
|
|
392
399
|
serveActivePack: statusSignals.serveActivePack,
|
|
393
400
|
routeFnAvailable: statusSignals.routeFnAvailable,
|
|
401
|
+
surfaceConverged: statusSignals.surfaceConvergeState === "converged",
|
|
394
402
|
},
|
|
395
403
|
attachTruth: {
|
|
396
404
|
current: attachTruth.current ?? null,
|
|
@@ -399,6 +407,15 @@ function buildHardeningSnapshot({ attachTruthLine, serveLine, routeFnLine, verdi
|
|
|
399
407
|
runtime: attachTruth.runtime ?? null,
|
|
400
408
|
watcher: attachTruth.watcher ?? null,
|
|
401
409
|
},
|
|
410
|
+
surface: {
|
|
411
|
+
boundary: surface.boundary ?? null,
|
|
412
|
+
skew: surface.skew ?? null,
|
|
413
|
+
converge: surface.converge ?? null,
|
|
414
|
+
daemonSource: surface.daemonSource ?? null,
|
|
415
|
+
selectedHome: surface.selectedHome ?? null,
|
|
416
|
+
daemon: surface.daemon ?? null,
|
|
417
|
+
hook: surface.hook ?? null,
|
|
418
|
+
},
|
|
402
419
|
serve: {
|
|
403
420
|
state: serve.state ?? null,
|
|
404
421
|
failOpen: serve.failOpen ?? null,
|
|
@@ -450,6 +467,8 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
|
|
|
450
467
|
&& statusSignals.runtimeProven
|
|
451
468
|
&& statusSignals.serveActivePack
|
|
452
469
|
&& statusSignals.routeFnAvailable;
|
|
470
|
+
const surfaceConverged = statusSignals.surfaceConvergeState === "converged";
|
|
471
|
+
const surfaceHalfConverged = statusSignals.surfaceConvergeState === "half_converged";
|
|
453
472
|
if (!statusSignals.loadProofReady)
|
|
454
473
|
runtimeTruthGaps.push("load_proof");
|
|
455
474
|
if (!statusSignals.runtimeProven)
|
|
@@ -458,6 +477,9 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
|
|
|
458
477
|
runtimeTruthGaps.push("serve_active_pack");
|
|
459
478
|
if (!statusSignals.routeFnAvailable)
|
|
460
479
|
runtimeTruthGaps.push("route_fn");
|
|
480
|
+
if (surfaceHalfConverged) {
|
|
481
|
+
runtimeTruthGaps.push("surface_converged");
|
|
482
|
+
}
|
|
461
483
|
const warningCodes = [];
|
|
462
484
|
const warnings = [];
|
|
463
485
|
if (!statusSignals.statusOk) {
|
|
@@ -495,6 +517,14 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
|
|
|
495
517
|
? "runtime-load-proof snapshot did not include the current openclaw home"
|
|
496
518
|
: "runtime-load-proof snapshot was missing");
|
|
497
519
|
}
|
|
520
|
+
if (surfaceHalfConverged) {
|
|
521
|
+
warningCodes.push(`surface_half_converged:${statusSignals.surfaceSkew ?? "unknown"}`);
|
|
522
|
+
warnings.push(`detailed status reported daemon-vs-installed-surface half-converged (${statusSignals.surfaceSkew ?? "unknown"})`);
|
|
523
|
+
}
|
|
524
|
+
else if (!surfaceConverged) {
|
|
525
|
+
warningCodes.push(`surface_unverified:${statusSignals.surfaceConvergeState ?? "unknown"}`);
|
|
526
|
+
warnings.push("detailed status did not fully prove daemon-vs-installed-surface convergence");
|
|
527
|
+
}
|
|
498
528
|
if (statusSignals.proofError !== null && statusSignals.proofError !== "none") {
|
|
499
529
|
warningCodes.push(`proof_error:${statusSignals.proofError}`);
|
|
500
530
|
warnings.push(`detailed status reported proofError=${statusSignals.proofError}`);
|
|
@@ -546,7 +576,7 @@ function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, brea
|
|
|
546
576
|
};
|
|
547
577
|
}
|
|
548
578
|
|
|
549
|
-
function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, surfaceLine, surfacesLine, hotfixLine, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, learningFlowLine, learningHealthLine, coverageSnapshot, hardeningSnapshot }) {
|
|
579
|
+
function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, surfaceLine, surfacesLine, surfaceNoteLine, hotfixLine, guardLine, feedbackLine, attributionLine, attributionCoverageLine, learningPathLine, learningFlowLine, learningHealthLine, coverageSnapshot, hardeningSnapshot }) {
|
|
550
580
|
const passed = [];
|
|
551
581
|
const missing = [];
|
|
552
582
|
const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
|
|
@@ -574,11 +604,20 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
|
|
|
574
604
|
if (statusSignals.routeFnAvailable) {
|
|
575
605
|
passed.push("detailed status reported routeFn available=yes");
|
|
576
606
|
}
|
|
607
|
+
if (statusSignals.surfaceConvergeState === "converged") {
|
|
608
|
+
passed.push("detailed status reported daemon-vs-installed-surface convergence");
|
|
609
|
+
}
|
|
577
610
|
if (breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded")) {
|
|
578
611
|
passed.push("startup log contained a post-bundle [openclawbrain] BRAIN LOADED breadcrumb");
|
|
579
612
|
}
|
|
580
613
|
if (!statusSignals.loadProofReady)
|
|
581
614
|
missing.push("detailed status did not prove hook load");
|
|
615
|
+
if (statusSignals.surfaceConvergeState === "half_converged") {
|
|
616
|
+
missing.push(`detailed status reported daemon-vs-installed-surface half-converged (${statusSignals.surfaceSkew ?? "unknown"})`);
|
|
617
|
+
}
|
|
618
|
+
else if (statusSignals.surfaceConvergeState !== "converged") {
|
|
619
|
+
missing.push("detailed status did not fully prove daemon-vs-installed-surface convergence");
|
|
620
|
+
}
|
|
582
621
|
if (!breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded"))
|
|
583
622
|
missing.push("no post-bundle startup breadcrumb was found");
|
|
584
623
|
if (runtimeLoadProofSnapshot.path === null)
|
|
@@ -609,6 +648,9 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
|
|
|
609
648
|
...(surfacesLine === null
|
|
610
649
|
? ["- hotfix paths line not reported by detailed status"]
|
|
611
650
|
: [`- ${surfacesLine}`]),
|
|
651
|
+
...(surfaceNoteLine === null
|
|
652
|
+
? ["- hotfix detail line not reported by detailed status"]
|
|
653
|
+
: [`- ${surfaceNoteLine}`]),
|
|
612
654
|
...(hotfixLine === null
|
|
613
655
|
? ["- hotfix guidance line not reported by detailed status"]
|
|
614
656
|
: [`- ${hotfixLine}`]),
|
|
@@ -649,7 +691,8 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
|
|
|
649
691
|
: coverageSnapshot.profiles.map((entry) => `- ${entry.current ? "*" : ""}${entry.label} coverage=${entry.coverageState} hook=${entry.hookFiles} config=${entry.configLoad} runtime=${entry.runtimeLoad} loadedAt=${entry.loadedAt ?? "none"}`)),
|
|
650
692
|
"",
|
|
651
693
|
"## Hardening snapshot",
|
|
652
|
-
`- status signals: statusOk=${hardeningSnapshot.statusSignals.statusOk} loadProofReady=${hardeningSnapshot.statusSignals.loadProofReady} runtimeProven=${hardeningSnapshot.statusSignals.runtimeProven} serveActivePack=${hardeningSnapshot.statusSignals.serveActivePack} routeFnAvailable=${hardeningSnapshot.statusSignals.routeFnAvailable}`,
|
|
694
|
+
`- status signals: statusOk=${hardeningSnapshot.statusSignals.statusOk} loadProofReady=${hardeningSnapshot.statusSignals.loadProofReady} runtimeProven=${hardeningSnapshot.statusSignals.runtimeProven} serveActivePack=${hardeningSnapshot.statusSignals.serveActivePack} routeFnAvailable=${hardeningSnapshot.statusSignals.routeFnAvailable} surfaceConverged=${hardeningSnapshot.statusSignals.surfaceConverged}`,
|
|
695
|
+
`- surface: boundary=${hardeningSnapshot.surface.boundary ?? "none"} skew=${hardeningSnapshot.surface.skew ?? "none"} converge=${hardeningSnapshot.surface.converge ?? "none"} daemonSource=${hardeningSnapshot.surface.daemonSource ?? "none"} daemon=${hardeningSnapshot.surface.daemon ?? "none"} hook=${hardeningSnapshot.surface.hook ?? "none"} selectedHome=${hardeningSnapshot.surface.selectedHome ?? "none"}`,
|
|
653
696
|
`- serve: state=${hardeningSnapshot.serve.state ?? "none"} failOpen=${hardeningSnapshot.serve.failOpen ?? "none"} hardFail=${hardeningSnapshot.serve.hardFail ?? "none"} usedRouteFn=${hardeningSnapshot.serve.usedRouteFn ?? "none"}`,
|
|
654
697
|
`- attachTruth: current=${hardeningSnapshot.attachTruth.current ?? "none"} hook=${hardeningSnapshot.attachTruth.hook ?? "none"} config=${hardeningSnapshot.attachTruth.config ?? "none"} runtime=${hardeningSnapshot.attachTruth.runtime ?? "none"}`,
|
|
655
698
|
`- proof verdict: ${hardeningSnapshot.verdict.verdict} severity=${hardeningSnapshot.verdict.severity} warnings=${hardeningSnapshot.verdict.warningCount}`,
|
|
@@ -702,6 +745,7 @@ export function buildProofCommandHelpSection() {
|
|
|
702
745
|
return {
|
|
703
746
|
usage: " openclawbrain proof --openclaw-home <path> [options]",
|
|
704
747
|
optionLines: [
|
|
748
|
+
" --openclaw-home <path> Target one explicit OpenClaw home (for example ./openclaw-cormorantai or ~/.openclaw).",
|
|
705
749
|
" --output-dir <path> Bundle directory for proof artifacts (proof only). Defaults to ./artifacts/operator-proof-<timestamp>.",
|
|
706
750
|
" --skip-install Capture proof without rerunning install first (proof only).",
|
|
707
751
|
" --skip-restart Capture proof without restarting OpenClaw first (proof only).",
|
|
@@ -937,6 +981,7 @@ export function captureOperatorProofBundle(options) {
|
|
|
937
981
|
const statusSignals = extractStatusSignals(statusCapture.stdout);
|
|
938
982
|
const surfaceLine = extractDetailedStatusLine(statusCapture.stdout, "surface");
|
|
939
983
|
const surfacesLine = extractDetailedStatusLine(statusCapture.stdout, "surfaces");
|
|
984
|
+
const surfaceNoteLine = extractDetailedStatusLine(statusCapture.stdout, "surfaceNote");
|
|
940
985
|
const hotfixLine = extractDetailedStatusLine(statusCapture.stdout, "hotfix");
|
|
941
986
|
const attachTruthLine = extractDetailedStatusLine(statusCapture.stdout, "attachTruth");
|
|
942
987
|
const attachedSetLine = extractDetailedStatusLine(statusCapture.stdout, "attachedSet");
|
|
@@ -978,6 +1023,7 @@ export function captureOperatorProofBundle(options) {
|
|
|
978
1023
|
attachTruthLine,
|
|
979
1024
|
serveLine,
|
|
980
1025
|
routeFnLine,
|
|
1026
|
+
surfaceLine,
|
|
981
1027
|
verdict,
|
|
982
1028
|
statusSignals,
|
|
983
1029
|
});
|
|
@@ -1004,6 +1050,7 @@ export function captureOperatorProofBundle(options) {
|
|
|
1004
1050
|
runtimeLoadProofError: runtimeLoadProofSnapshot.error,
|
|
1005
1051
|
surfaceLine,
|
|
1006
1052
|
surfacesLine,
|
|
1053
|
+
surfaceNoteLine,
|
|
1007
1054
|
hotfixLine,
|
|
1008
1055
|
guardLine,
|
|
1009
1056
|
learningFlowLine,
|
|
@@ -1025,6 +1072,7 @@ export function captureOperatorProofBundle(options) {
|
|
|
1025
1072
|
runtimeLoadProofSnapshot,
|
|
1026
1073
|
surfaceLine,
|
|
1027
1074
|
surfacesLine,
|
|
1075
|
+
surfaceNoteLine,
|
|
1028
1076
|
hotfixLine,
|
|
1029
1077
|
guardLine,
|
|
1030
1078
|
learningFlowLine,
|
|
@@ -1051,6 +1099,7 @@ export function captureOperatorProofBundle(options) {
|
|
|
1051
1099
|
statusSignals,
|
|
1052
1100
|
surfaceLine,
|
|
1053
1101
|
surfacesLine,
|
|
1102
|
+
surfaceNoteLine,
|
|
1054
1103
|
hotfixLine,
|
|
1055
1104
|
guardLine,
|
|
1056
1105
|
learningFlowLine,
|
package/package.json
CHANGED