@openclawbrain/openclaw 0.1.2 → 0.1.4
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 +62 -72
- package/dist/src/cli.d.ts +3 -3
- package/dist/src/cli.js +55 -17
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +113 -56
- package/dist/src/index.js +486 -108
- package/dist/src/index.js.map +1 -1
- package/dist/src/learning-spine.d.ts +46 -0
- package/dist/src/learning-spine.js +445 -0
- package/dist/src/learning-spine.js.map +1 -0
- package/package.json +6 -6
package/dist/src/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
|
|
6
6
|
import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, sortNormalizedEvents, validateKernelSurface, validateNormalizedEventExport } from "@openclawbrain/contracts";
|
|
7
7
|
import { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
|
|
8
8
|
import { DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS, advanceAlwaysOnLearningRuntime, buildTeacherSupervisionArtifactsFromNormalizedEventExport, createAlwaysOnLearningRuntimeState, describeAlwaysOnLearningRuntimeState, materializeAlwaysOnLearningCandidatePack, materializeCandidatePackFromNormalizedEventExport } from "@openclawbrain/learner";
|
|
9
|
-
import { activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, promoteCandidatePack, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
9
|
+
import { LEARNING_SPINE_LOG_LAYOUT, activatePack, describeActivationObservability, describeActivationTarget, describePackCompileTarget, inspectActivationState, loadPackFromActivation, promoteCandidatePack, readLearningSpineLogEntries, rollbackActivePack, stageCandidatePack } from "@openclawbrain/pack-format";
|
|
10
|
+
import { appendLearningUpdateLogs, appendServeTimeRouteDecisionLog } from "./learning-spine.js";
|
|
10
11
|
const DEFAULT_AGENT_ID = "openclaw-runtime";
|
|
11
12
|
const FEEDBACK_KINDS = new Set(["correction", "teaching", "approval", "suppression"]);
|
|
12
13
|
export const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
|
|
@@ -20,6 +21,35 @@ export const RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT = {
|
|
|
20
21
|
manifest: "manifest.json",
|
|
21
22
|
payload: "normalized-event-export.json"
|
|
22
23
|
};
|
|
24
|
+
function normalizeBrainAttachmentPolicy(value) {
|
|
25
|
+
if (value === "dedicated" || value === "shared") {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return "undeclared";
|
|
29
|
+
}
|
|
30
|
+
function buildCurrentProfileAttachmentPolicy(policyMode) {
|
|
31
|
+
if (policyMode === "dedicated") {
|
|
32
|
+
return {
|
|
33
|
+
mode: "dedicated",
|
|
34
|
+
readScope: "current_profile_only",
|
|
35
|
+
writeScope: "current_profile_only",
|
|
36
|
+
currentProfileExclusive: true,
|
|
37
|
+
requiresProfileAttribution: true,
|
|
38
|
+
detail: "dedicated brains are exclusive to the current profile and must keep profile attribution explicit on every served turn"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (policyMode === "shared") {
|
|
42
|
+
return {
|
|
43
|
+
mode: "shared",
|
|
44
|
+
readScope: "attached_profiles",
|
|
45
|
+
writeScope: "attached_profiles",
|
|
46
|
+
currentProfileExclusive: false,
|
|
47
|
+
requiresProfileAttribution: true,
|
|
48
|
+
detail: "shared brains may serve multiple attached profiles, so status and per-turn attribution must stay profile-explicit"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
23
53
|
const OPENCLAW_LANDING_BOUNDARIES_V1 = {
|
|
24
54
|
compileBoundary: {
|
|
25
55
|
contract: CONTRACT_IDS.runtimeCompile,
|
|
@@ -994,6 +1024,9 @@ function buildAttachSuccessSignals(input) {
|
|
|
994
1024
|
}
|
|
995
1025
|
if (activeTarget !== null) {
|
|
996
1026
|
signals.push(`active_workspace_snapshot:${activeTarget.workspaceSnapshot}`);
|
|
1027
|
+
if (activeTarget.eventRange.count === 0) {
|
|
1028
|
+
signals.push("awaiting_first_export");
|
|
1029
|
+
}
|
|
997
1030
|
}
|
|
998
1031
|
if (input.compile?.ok) {
|
|
999
1032
|
signals.push(`compile_ok:${input.compile.activePackId ?? "unknown"}`);
|
|
@@ -1079,7 +1112,10 @@ export function rollbackRuntimeAttach(input) {
|
|
|
1079
1112
|
}
|
|
1080
1113
|
const after = dryRun
|
|
1081
1114
|
? before.rollback.nextPointers
|
|
1082
|
-
: rollbackActivePack(activationRoot,
|
|
1115
|
+
: rollbackActivePack(activationRoot, {
|
|
1116
|
+
updatedAt,
|
|
1117
|
+
reason: "runtime_attach_rollback"
|
|
1118
|
+
}).pointers;
|
|
1083
1119
|
return {
|
|
1084
1120
|
runtimeOwner: "openclaw",
|
|
1085
1121
|
activationRoot,
|
|
@@ -1108,17 +1144,54 @@ function resolveBootstrapNormalizedEventExport(input) {
|
|
|
1108
1144
|
throw new Error("Provide normalizedEventExport or interactionEvents/feedbackEvents, not both");
|
|
1109
1145
|
}
|
|
1110
1146
|
const normalizedEventExport = input.normalizedEventExport !== undefined
|
|
1111
|
-
?
|
|
1147
|
+
? canonicalizeBootstrapNormalizedEventExport(input.normalizedEventExport)
|
|
1112
1148
|
: buildNormalizedEventExport({
|
|
1113
1149
|
interactionEvents,
|
|
1114
1150
|
feedbackEvents
|
|
1115
1151
|
});
|
|
1116
1152
|
const validationErrors = validateNormalizedEventExport(normalizedEventExport);
|
|
1117
1153
|
if (validationErrors.length > 0) {
|
|
1118
|
-
throw new Error(
|
|
1154
|
+
throw new Error(formatBootstrapNormalizedEventExportValidationError(normalizedEventExport, validationErrors));
|
|
1119
1155
|
}
|
|
1120
1156
|
return normalizedEventExport;
|
|
1121
1157
|
}
|
|
1158
|
+
function canonicalizeBootstrapNormalizedEventExport(normalizedEventExport) {
|
|
1159
|
+
const interactionEvents = cloneBootstrapNormalizedEventArray(normalizedEventExport.interactionEvents, "interactionEvents");
|
|
1160
|
+
const feedbackEvents = cloneBootstrapNormalizedEventArray(normalizedEventExport.feedbackEvents, "feedbackEvents");
|
|
1161
|
+
try {
|
|
1162
|
+
return buildNormalizedEventExport({
|
|
1163
|
+
interactionEvents,
|
|
1164
|
+
feedbackEvents
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1169
|
+
throw new Error("bootstrapRuntimeAttach could not reconstruct a safe normalized event export from the provided event arrays. " +
|
|
1170
|
+
"Repair the event payload or, for a first attach with no live events yet, pass empty arrays so bootstrap can self-boot. " +
|
|
1171
|
+
`Details: ${detail}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
function cloneBootstrapNormalizedEventArray(value, fieldName) {
|
|
1175
|
+
if (!Array.isArray(value)) {
|
|
1176
|
+
throw new Error(`bootstrapRuntimeAttach expected normalizedEventExport.${fieldName} to be an array. ` +
|
|
1177
|
+
"For a first attach with no live events yet, pass empty arrays or omit normalizedEventExport and use interactionEvents: [] / feedbackEvents: [].");
|
|
1178
|
+
}
|
|
1179
|
+
return structuredClone(value);
|
|
1180
|
+
}
|
|
1181
|
+
function formatBootstrapNormalizedEventExportValidationError(normalizedEventExport, validationErrors) {
|
|
1182
|
+
const details = validationErrors.join("; ");
|
|
1183
|
+
const zeroEventBootstrap = normalizedEventExport.interactionEvents.length === 0 &&
|
|
1184
|
+
normalizedEventExport.feedbackEvents.length === 0 &&
|
|
1185
|
+
normalizedEventExport.range.count === 0;
|
|
1186
|
+
if (zeroEventBootstrap) {
|
|
1187
|
+
return ("bootstrapRuntimeAttach could not derive a safe zero-event bootstrap export: " +
|
|
1188
|
+
`${details}. ` +
|
|
1189
|
+
"For a first attach with no live events yet, pass empty interaction/feedback arrays or an empty normalized export payload and bootstrap will self-boot.");
|
|
1190
|
+
}
|
|
1191
|
+
return ("bootstrapRuntimeAttach could not use the provided normalized event export: " +
|
|
1192
|
+
`${details}. ` +
|
|
1193
|
+
"Repair the event payload or pass raw interactionEvents/feedbackEvents so bootstrap can derive range and provenance itself.");
|
|
1194
|
+
}
|
|
1122
1195
|
export function bootstrapRuntimeAttach(input) {
|
|
1123
1196
|
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
1124
1197
|
const packRoot = path.resolve(normalizeNonEmptyString(input.packRoot, "packRoot"));
|
|
@@ -1141,7 +1214,10 @@ export function bootstrapRuntimeAttach(input) {
|
|
|
1141
1214
|
...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
|
|
1142
1215
|
...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
|
|
1143
1216
|
});
|
|
1144
|
-
activatePack(activationRoot, packRoot,
|
|
1217
|
+
activatePack(activationRoot, packRoot, {
|
|
1218
|
+
updatedAt: activatedAt,
|
|
1219
|
+
reason: "attach_bootstrap"
|
|
1220
|
+
});
|
|
1145
1221
|
return {
|
|
1146
1222
|
runtimeOwner: "openclaw",
|
|
1147
1223
|
activationRoot,
|
|
@@ -1154,6 +1230,48 @@ export function bootstrapRuntimeAttach(input) {
|
|
|
1154
1230
|
})
|
|
1155
1231
|
};
|
|
1156
1232
|
}
|
|
1233
|
+
function buildRuntimeTurnAttribution(input) {
|
|
1234
|
+
const brainAttachmentPolicy = normalizeBrainAttachmentPolicy(input.turn.brainAttachmentPolicy);
|
|
1235
|
+
if (input.compileResult.ok) {
|
|
1236
|
+
const notes = input.compileResult.compileResponse.diagnostics.notes;
|
|
1237
|
+
const contextAttribution = buildContextAttributionSummary({
|
|
1238
|
+
fallbackToStaticContext: false,
|
|
1239
|
+
hardRequirementViolated: false,
|
|
1240
|
+
usedLearnedRouteFn: input.compileResult.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
1241
|
+
selectedContext: input.compileResult.compileResponse.selectedContext,
|
|
1242
|
+
notes
|
|
1243
|
+
});
|
|
1244
|
+
return {
|
|
1245
|
+
hostRuntimeOwner: "openclaw",
|
|
1246
|
+
profileSelector: "current_profile",
|
|
1247
|
+
brainAttachmentPolicy,
|
|
1248
|
+
brainStatus: "serving_active_pack",
|
|
1249
|
+
activePackId: input.compileResult.activePackId,
|
|
1250
|
+
usedLearnedRouteFn: input.compileResult.compileResponse.diagnostics.usedLearnedRouteFn,
|
|
1251
|
+
routerIdentity: input.compileResult.compileResponse.diagnostics.routerIdentity,
|
|
1252
|
+
selectionDigest: input.compileResult.compileResponse.diagnostics.selectionDigest,
|
|
1253
|
+
selectionTiers: readDiagnosticNoteValue(notes, "selection_tiers="),
|
|
1254
|
+
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const contextAttribution = buildContextAttributionSummary({
|
|
1258
|
+
fallbackToStaticContext: input.compileResult.fallbackToStaticContext,
|
|
1259
|
+
hardRequirementViolated: input.compileResult.hardRequirementViolated,
|
|
1260
|
+
usedLearnedRouteFn: null
|
|
1261
|
+
});
|
|
1262
|
+
return {
|
|
1263
|
+
hostRuntimeOwner: "openclaw",
|
|
1264
|
+
profileSelector: "current_profile",
|
|
1265
|
+
brainAttachmentPolicy,
|
|
1266
|
+
brainStatus: input.compileResult.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
|
|
1267
|
+
activePackId: null,
|
|
1268
|
+
usedLearnedRouteFn: null,
|
|
1269
|
+
routerIdentity: null,
|
|
1270
|
+
selectionDigest: null,
|
|
1271
|
+
selectionTiers: null,
|
|
1272
|
+
contextEvidence: contextAttribution.evidence === "unprobed" ? null : contextAttribution.evidence
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1157
1275
|
function buildCompileInteractionEvent(input) {
|
|
1158
1276
|
if (!input.compileResult.ok) {
|
|
1159
1277
|
return null;
|
|
@@ -1168,6 +1286,10 @@ function buildCompileInteractionEvent(input) {
|
|
|
1168
1286
|
sessionId: input.turn.sessionId,
|
|
1169
1287
|
source: input.sourceStream
|
|
1170
1288
|
});
|
|
1289
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
1290
|
+
turn: input.turn,
|
|
1291
|
+
compileResult: input.compileResult
|
|
1292
|
+
});
|
|
1171
1293
|
return createInteractionEvent({
|
|
1172
1294
|
eventId,
|
|
1173
1295
|
agentId: input.agentId,
|
|
@@ -1180,7 +1302,8 @@ function buildCompileInteractionEvent(input) {
|
|
|
1180
1302
|
runtimeOwner: "openclaw",
|
|
1181
1303
|
stream: input.sourceStream
|
|
1182
1304
|
},
|
|
1183
|
-
packId: input.compileResult.compileResponse.packId
|
|
1305
|
+
packId: input.compileResult.compileResponse.packId,
|
|
1306
|
+
attribution
|
|
1184
1307
|
});
|
|
1185
1308
|
}
|
|
1186
1309
|
function buildDeliveryInteractionEvent(input) {
|
|
@@ -1201,6 +1324,10 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
1201
1324
|
sessionId: input.turn.sessionId,
|
|
1202
1325
|
source: input.sourceStream
|
|
1203
1326
|
});
|
|
1327
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
1328
|
+
turn: input.turn,
|
|
1329
|
+
compileResult: input.compileResult
|
|
1330
|
+
});
|
|
1204
1331
|
return createInteractionEvent({
|
|
1205
1332
|
eventId,
|
|
1206
1333
|
agentId: input.agentId,
|
|
@@ -1213,12 +1340,17 @@ function buildDeliveryInteractionEvent(input) {
|
|
|
1213
1340
|
runtimeOwner: "openclaw",
|
|
1214
1341
|
stream: input.sourceStream
|
|
1215
1342
|
},
|
|
1343
|
+
attribution,
|
|
1216
1344
|
...(input.compileResult.ok ? { packId: input.compileResult.compileResponse.packId } : {}),
|
|
1217
1345
|
...(messageId !== undefined ? { messageId } : {})
|
|
1218
1346
|
});
|
|
1219
1347
|
}
|
|
1220
1348
|
function buildFeedbackEvents(input) {
|
|
1221
1349
|
const feedbackItems = input.turn.feedback ?? [];
|
|
1350
|
+
const attribution = buildRuntimeTurnAttribution({
|
|
1351
|
+
turn: input.turn,
|
|
1352
|
+
compileResult: input.compileResult
|
|
1353
|
+
});
|
|
1222
1354
|
return feedbackItems.map((item, index) => {
|
|
1223
1355
|
if (item === null) {
|
|
1224
1356
|
throw new Error(`feedback[${index}] must be an object`);
|
|
@@ -1255,6 +1387,7 @@ function buildFeedbackEvents(input) {
|
|
|
1255
1387
|
stream: input.sourceStream
|
|
1256
1388
|
},
|
|
1257
1389
|
content,
|
|
1390
|
+
attribution,
|
|
1258
1391
|
...(messageId !== undefined ? { messageId } : {}),
|
|
1259
1392
|
...(relatedInteractionId !== undefined ? { relatedInteractionId } : {})
|
|
1260
1393
|
});
|
|
@@ -1287,6 +1420,7 @@ export function buildNormalizedRuntimeEventExport(turn, compileResult) {
|
|
|
1287
1420
|
nextSequence,
|
|
1288
1421
|
defaultCreatedAt: compileCreatedAt,
|
|
1289
1422
|
compileInteraction,
|
|
1423
|
+
compileResult,
|
|
1290
1424
|
agentId
|
|
1291
1425
|
});
|
|
1292
1426
|
const deliveryInteraction = buildDeliveryInteractionEvent({
|
|
@@ -1357,12 +1491,36 @@ export function runRuntimeTurn(turn, options = {}) {
|
|
|
1357
1491
|
...(turn.runtimeHints !== undefined ? { runtimeHints: turn.runtimeHints } : {})
|
|
1358
1492
|
};
|
|
1359
1493
|
const compileResult = compileRuntimeContext(compileInput);
|
|
1494
|
+
const warnings = [];
|
|
1495
|
+
const serveLoggedAt = turn.compile?.createdAt ?? turn.createdAt ?? new Date().toISOString();
|
|
1360
1496
|
if (!compileResult.ok && compileResult.hardRequirementViolated) {
|
|
1497
|
+
try {
|
|
1498
|
+
appendServeTimeRouteDecisionLog({
|
|
1499
|
+
activationRoot: compileInput.activationRoot,
|
|
1500
|
+
turn,
|
|
1501
|
+
compileResult,
|
|
1502
|
+
recordedAt: serveLoggedAt
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
}
|
|
1361
1507
|
throw new Error(compileResult.error);
|
|
1362
1508
|
}
|
|
1363
|
-
const warnings = [];
|
|
1364
1509
|
try {
|
|
1365
1510
|
const normalizedEventExport = buildNormalizedRuntimeEventExport(turn, compileResult);
|
|
1511
|
+
try {
|
|
1512
|
+
const compileEvent = normalizedEventExport.interactionEvents.find((event) => event.kind === "memory_compiled");
|
|
1513
|
+
appendServeTimeRouteDecisionLog({
|
|
1514
|
+
activationRoot: compileInput.activationRoot,
|
|
1515
|
+
turn,
|
|
1516
|
+
compileResult,
|
|
1517
|
+
normalizedEventExport,
|
|
1518
|
+
recordedAt: compileEvent?.createdAt ?? serveLoggedAt
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
catch (error) {
|
|
1522
|
+
warnings.push(`learning spine serve log failed: ${toErrorMessage(error)}`);
|
|
1523
|
+
}
|
|
1366
1524
|
const eventExport = writeRuntimeEventExportBundle(turn, normalizedEventExport);
|
|
1367
1525
|
return {
|
|
1368
1526
|
...compileResult,
|
|
@@ -1442,6 +1600,13 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
1442
1600
|
currentState.interactionEvents = mergedHistory.interactionEvents;
|
|
1443
1601
|
currentState.feedbackEvents = mergedHistory.feedbackEvents;
|
|
1444
1602
|
try {
|
|
1603
|
+
let activeBeforePack = null;
|
|
1604
|
+
try {
|
|
1605
|
+
activeBeforePack = loadPackFromActivation(activationRoot, "active", { requireActivationReady: true });
|
|
1606
|
+
}
|
|
1607
|
+
catch {
|
|
1608
|
+
activeBeforePack = null;
|
|
1609
|
+
}
|
|
1445
1610
|
const learnerResult = advanceAlwaysOnLearningRuntime({
|
|
1446
1611
|
packLabel: input.packLabel,
|
|
1447
1612
|
workspace: input.workspace,
|
|
@@ -1474,13 +1639,30 @@ export function runContinuousProductLoopTurn(input) {
|
|
|
1474
1639
|
learning.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
|
|
1475
1640
|
currentState.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
|
|
1476
1641
|
const stagedAt = normalizeIsoTimestamp(input.stageUpdatedAt, "stageUpdatedAt", descriptor.manifest.provenance.builtAt);
|
|
1477
|
-
|
|
1642
|
+
try {
|
|
1643
|
+
appendLearningUpdateLogs({
|
|
1644
|
+
activationRoot,
|
|
1645
|
+
materialization: learnerResult.materialization,
|
|
1646
|
+
activeBeforePack,
|
|
1647
|
+
candidateDescriptor: descriptor
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
catch (error) {
|
|
1651
|
+
learningWarnings.push(`learning spine update logs failed: ${toErrorMessage(error)}`);
|
|
1652
|
+
}
|
|
1653
|
+
stageCandidatePack(activationRoot, candidateRootDir, {
|
|
1654
|
+
updatedAt: stagedAt,
|
|
1655
|
+
reason: `stage_candidate:${learnerResult.materialization.reason}:${learnerResult.materialization.lane}`
|
|
1656
|
+
});
|
|
1478
1657
|
const stagedInspection = inspectActivationState(activationRoot, stagedAt);
|
|
1479
1658
|
learning.promotionAllowed = stagedInspection.promotion.allowed;
|
|
1480
1659
|
learning.promotionFindings = [...stagedInspection.promotion.findings];
|
|
1481
1660
|
if ((input.autoPromote ?? true) && stagedInspection.promotion.allowed) {
|
|
1482
1661
|
const promotedAt = normalizeIsoTimestamp(input.promoteUpdatedAt, "promoteUpdatedAt", stagedAt);
|
|
1483
|
-
promoteCandidatePack(activationRoot,
|
|
1662
|
+
promoteCandidatePack(activationRoot, {
|
|
1663
|
+
updatedAt: promotedAt,
|
|
1664
|
+
reason: `auto_promote:${learnerResult.materialization.reason}:${learnerResult.materialization.lane}`
|
|
1665
|
+
});
|
|
1484
1666
|
currentState.promotionCount += 1;
|
|
1485
1667
|
currentState.candidatePack = null;
|
|
1486
1668
|
learning.promoted = true;
|
|
@@ -1896,7 +2078,10 @@ function prepareSeedActivation(rootDir, fixture) {
|
|
|
1896
2078
|
connect: 1
|
|
1897
2079
|
}
|
|
1898
2080
|
});
|
|
1899
|
-
activatePack(activationRoot, seedPackRoot,
|
|
2081
|
+
activatePack(activationRoot, seedPackRoot, {
|
|
2082
|
+
updatedAt: fixture.seedActivatedAt,
|
|
2083
|
+
reason: "recorded_session_seed_activate"
|
|
2084
|
+
});
|
|
1900
2085
|
return {
|
|
1901
2086
|
activationRoot,
|
|
1902
2087
|
seedPackId: seedPack.manifest.packId
|
|
@@ -2056,6 +2241,146 @@ export function verifyRecordedSessionReplayBundleHashes(bundle) {
|
|
|
2056
2241
|
scoreHashMatches: rescored.scoreHash === bundle.scoreHash
|
|
2057
2242
|
};
|
|
2058
2243
|
}
|
|
2244
|
+
export const OPERATOR_API_CONTRACT_ID = "openclaw_operator_api.v1";
|
|
2245
|
+
export const SUPPORTED_OPERATOR_API_FAMILIES = [
|
|
2246
|
+
"bootstrap_attach",
|
|
2247
|
+
"status",
|
|
2248
|
+
"export",
|
|
2249
|
+
"refresh",
|
|
2250
|
+
"promote",
|
|
2251
|
+
"rollback",
|
|
2252
|
+
"proof_observability"
|
|
2253
|
+
];
|
|
2254
|
+
export const OPERATOR_API_CONTRACT_V1 = {
|
|
2255
|
+
contract: OPERATOR_API_CONTRACT_ID,
|
|
2256
|
+
runtimeOwner: "openclaw",
|
|
2257
|
+
scope: "narrow_supported_operator_surface",
|
|
2258
|
+
families: SUPPORTED_OPERATOR_API_FAMILIES,
|
|
2259
|
+
routes: [
|
|
2260
|
+
{
|
|
2261
|
+
family: "bootstrap_attach",
|
|
2262
|
+
scope: "programmatic",
|
|
2263
|
+
packageName: "@openclawbrain/openclaw",
|
|
2264
|
+
entrypoints: ["bootstrapRuntimeAttach", "describeAttachStatus"],
|
|
2265
|
+
summary: "Bootstrap the first attach pack and prove the initial handoff state without pretending live learning has already run.",
|
|
2266
|
+
notes: [
|
|
2267
|
+
"Zero-event bootstrap is supported and stays explicit through awaiting_first_export.",
|
|
2268
|
+
"Attach serves only from activation's active slot after bootstrap completes."
|
|
2269
|
+
]
|
|
2270
|
+
},
|
|
2271
|
+
{
|
|
2272
|
+
family: "status",
|
|
2273
|
+
scope: "cli",
|
|
2274
|
+
packageName: "@openclawbrain/openclaw",
|
|
2275
|
+
entrypoints: ["openclawbrain-ops status", "describeCurrentProfileBrainStatus"],
|
|
2276
|
+
summary: "Read the canonical current-profile brain-status object for the active Host/Profile/Brain/Attachment boundary.",
|
|
2277
|
+
notes: [
|
|
2278
|
+
"Status is the first operator read path.",
|
|
2279
|
+
"describeCurrentProfileBrainStatus() freezes the supported Host/Profile/Brain/Attachment answer shape for the current profile.",
|
|
2280
|
+
"Use activation and export observability proof helpers when you need candidate/previous or export-freshness detail."
|
|
2281
|
+
]
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
family: "export",
|
|
2285
|
+
scope: "programmatic",
|
|
2286
|
+
packageName: "@openclawbrain/openclaw",
|
|
2287
|
+
entrypoints: ["buildNormalizedRuntimeEventExport", "writeRuntimeEventExportBundle", "loadRuntimeEventExportBundle"],
|
|
2288
|
+
summary: "Emit the deterministic learner handoff artifact explicitly instead of folding export into a larger implicit runtime loop.",
|
|
2289
|
+
notes: [
|
|
2290
|
+
"Export is an off-hot-path operator handoff artifact, not proof of immediate active-pack mutation.",
|
|
2291
|
+
"Bundle roots and normalized payloads are both accepted downstream by observability surfaces."
|
|
2292
|
+
]
|
|
2293
|
+
},
|
|
2294
|
+
{
|
|
2295
|
+
family: "refresh",
|
|
2296
|
+
scope: "programmatic",
|
|
2297
|
+
packageName: "@openclawbrain/learner",
|
|
2298
|
+
entrypoints: [
|
|
2299
|
+
"createAlwaysOnLearningRuntimeState",
|
|
2300
|
+
"advanceAlwaysOnLearningRuntime",
|
|
2301
|
+
"materializeAlwaysOnLearningCandidatePack"
|
|
2302
|
+
],
|
|
2303
|
+
summary: "Refresh candidate learning state explicitly through the learner boundary before any activation-pointer move happens.",
|
|
2304
|
+
notes: [
|
|
2305
|
+
"Refresh is PG-only candidate-pack materialization in this repo.",
|
|
2306
|
+
"Refresh does not mutate the currently served active pack in place."
|
|
2307
|
+
]
|
|
2308
|
+
},
|
|
2309
|
+
{
|
|
2310
|
+
family: "promote",
|
|
2311
|
+
scope: "programmatic",
|
|
2312
|
+
packageName: "@openclawbrain/pack-format",
|
|
2313
|
+
entrypoints: ["stageCandidatePack", "promoteCandidatePack"],
|
|
2314
|
+
summary: "Stage and promote activation-ready candidate packs through explicit pointer changes.",
|
|
2315
|
+
notes: [
|
|
2316
|
+
"Promotion is the only path that changes which pack is served.",
|
|
2317
|
+
"Candidate and previous remain inspectable around the pointer move."
|
|
2318
|
+
]
|
|
2319
|
+
},
|
|
2320
|
+
{
|
|
2321
|
+
family: "rollback",
|
|
2322
|
+
scope: "cli",
|
|
2323
|
+
packageName: "@openclawbrain/openclaw",
|
|
2324
|
+
entrypoints: ["openclawbrain-ops rollback", "rollbackRuntimeAttach", "formatOperatorRollbackReport"],
|
|
2325
|
+
summary: "Preview and apply the explicit active<-previous / active->candidate rollback move.",
|
|
2326
|
+
notes: [
|
|
2327
|
+
"Rollback is blocked when the previous pointer is unavailable.",
|
|
2328
|
+
"Dry-run is the required first read path for safe operator rollback."
|
|
2329
|
+
]
|
|
2330
|
+
},
|
|
2331
|
+
{
|
|
2332
|
+
family: "proof_observability",
|
|
2333
|
+
scope: "programmatic",
|
|
2334
|
+
packageName: "@openclawbrain/openclaw",
|
|
2335
|
+
entrypoints: ["describeAttachStatus", "describeKernelBrainBoundary"],
|
|
2336
|
+
summary: "Prove the local attach and kernel-vs-brain boundary from the shipped bridge surface.",
|
|
2337
|
+
notes: [
|
|
2338
|
+
"Use these for repo-local or installed-package operator proof reads.",
|
|
2339
|
+
"These surfaces report the promoted artifact boundary, not full live runtime plasticity."
|
|
2340
|
+
]
|
|
2341
|
+
},
|
|
2342
|
+
{
|
|
2343
|
+
family: "proof_observability",
|
|
2344
|
+
scope: "programmatic",
|
|
2345
|
+
packageName: "@openclawbrain/pack-format",
|
|
2346
|
+
entrypoints: ["describeActivationObservability"],
|
|
2347
|
+
summary: "Inspect activation health, freshness, route artifacts, rollback lineage, and slot readiness.",
|
|
2348
|
+
notes: ["Activation observability is the ground truth for active/candidate/previous slot inspection."]
|
|
2349
|
+
},
|
|
2350
|
+
{
|
|
2351
|
+
family: "proof_observability",
|
|
2352
|
+
scope: "programmatic",
|
|
2353
|
+
packageName: "@openclawbrain/event-export",
|
|
2354
|
+
entrypoints: ["describeNormalizedEventExportObservability"],
|
|
2355
|
+
summary: "Inspect supervision freshness and teacher freshness from the exported learner handoff artifact.",
|
|
2356
|
+
notes: ["Export observability is local-to-export proof only."]
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
family: "proof_observability",
|
|
2360
|
+
scope: "proof_lane",
|
|
2361
|
+
packageName: "workspace",
|
|
2362
|
+
entrypoints: ["pnpm current-profile-lifecycle:smoke", "pnpm observability:smoke"],
|
|
2363
|
+
summary: "Run the repo-local proof lanes that derive operator truth from the canonical current-profile status object plus activation observability.",
|
|
2364
|
+
notes: ["These lanes are proof machinery, not a second semver-stable API."]
|
|
2365
|
+
}
|
|
2366
|
+
],
|
|
2367
|
+
quarantinedSurface: [
|
|
2368
|
+
"openclawbrain-ops doctor was deleted; use the canonical current-profile status object plus proof helpers instead of a parallel troubleshooting surface.",
|
|
2369
|
+
"buildOperatorSurfaceReport / formatOperatorStatusReport / formatOperatorDoctorReport were historical parallel status surfaces and are not the supported operator API.",
|
|
2370
|
+
"runContinuousProductLoopTurn collapses export/refresh/promote into one proof helper and is not the supported operator API.",
|
|
2371
|
+
"runRecordedSessionReplay and recorded-session fixtures are proof helpers, not operator API.",
|
|
2372
|
+
"release scripts, root smoke plumbing, and workspace layout are proof-and-build machinery, not operator API.",
|
|
2373
|
+
"runRuntimeTurn is a runtime convenience wrapper and not the narrow operator export contract.",
|
|
2374
|
+
"createAsyncTeacherLiveLoop is supporting internals for refresh/teacher snapshots, not the narrow operator contract."
|
|
2375
|
+
]
|
|
2376
|
+
};
|
|
2377
|
+
export const OPENCLAW_OPERATOR_NOUNS_V1 = ["Host", "Profile", "Brain", "Attachment"];
|
|
2378
|
+
export const CURRENT_PROFILE_BRAIN_STATUS_CONTRACT = CONTRACT_IDS.currentProfileBrainStatus;
|
|
2379
|
+
export const BRAIN_ATTACHMENT_POLICY_SEMANTICS_V1 = {
|
|
2380
|
+
undeclared: "The Host has not declared whether the current Profile's Brain attachment policy is shared or dedicated; do not infer profile exclusivity from activation state alone.",
|
|
2381
|
+
dedicated: "The Host declares a dedicated Brain attachment policy: one Profile is intentionally attached to one Brain activation root, and operators may treat the served Brain state as profile-specific until the attachment changes.",
|
|
2382
|
+
shared: "The Host declares a shared Brain attachment policy: multiple Profiles may intentionally attach to the same Brain activation root, attribution must stay current-profile explicit, and operators must not treat later served context as profile-exclusive."
|
|
2383
|
+
};
|
|
2059
2384
|
function summarizeOperatorSlot(slot, updatedAt) {
|
|
2060
2385
|
if (slot === null) {
|
|
2061
2386
|
return null;
|
|
@@ -2108,6 +2433,9 @@ function summarizeCandidateAheadBy(candidateAheadBy) {
|
|
|
2108
2433
|
.map(([field]) => field)
|
|
2109
2434
|
.sort();
|
|
2110
2435
|
}
|
|
2436
|
+
function isAwaitingFirstExportSlot(slot) {
|
|
2437
|
+
return slot !== null && slot.eventRange.count === 0;
|
|
2438
|
+
}
|
|
2111
2439
|
function summarizeBrainState(active, observability) {
|
|
2112
2440
|
if (active === null) {
|
|
2113
2441
|
return {
|
|
@@ -2202,7 +2530,8 @@ function loadOperatorEventExport(input) {
|
|
|
2202
2530
|
return {
|
|
2203
2531
|
sourcePath: resolvedPath,
|
|
2204
2532
|
sourceKind: "bundle_root",
|
|
2205
|
-
normalizedEventExport: bundle.normalizedEventExport
|
|
2533
|
+
normalizedEventExport: bundle.normalizedEventExport,
|
|
2534
|
+
exportedAt: bundle.manifest.exportedAt
|
|
2206
2535
|
};
|
|
2207
2536
|
}
|
|
2208
2537
|
const normalizedEventExport = readJsonFile(resolvedPath);
|
|
@@ -2213,7 +2542,8 @@ function loadOperatorEventExport(input) {
|
|
|
2213
2542
|
return {
|
|
2214
2543
|
sourcePath: resolvedPath,
|
|
2215
2544
|
sourceKind: "payload",
|
|
2216
|
-
normalizedEventExport
|
|
2545
|
+
normalizedEventExport,
|
|
2546
|
+
exportedAt: null
|
|
2217
2547
|
};
|
|
2218
2548
|
}
|
|
2219
2549
|
function summarizeSupervision(input) {
|
|
@@ -2224,6 +2554,7 @@ function summarizeSupervision(input) {
|
|
|
2224
2554
|
sourcePath: null,
|
|
2225
2555
|
sourceKind: "missing",
|
|
2226
2556
|
exportDigest: null,
|
|
2557
|
+
exportedAt: null,
|
|
2227
2558
|
flowing: null,
|
|
2228
2559
|
sourceCount: 0,
|
|
2229
2560
|
freshestSourceStream: null,
|
|
@@ -2242,6 +2573,7 @@ function summarizeSupervision(input) {
|
|
|
2242
2573
|
sourcePath: loaded.sourcePath,
|
|
2243
2574
|
sourceKind: loaded.sourceKind,
|
|
2244
2575
|
exportDigest: observability.exportDigest,
|
|
2576
|
+
exportedAt: loaded.exportedAt,
|
|
2245
2577
|
flowing,
|
|
2246
2578
|
sourceCount: observability.supervisionFreshnessBySource.length,
|
|
2247
2579
|
freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
|
|
@@ -2390,13 +2722,16 @@ function buildOperatorFindings(report) {
|
|
|
2390
2722
|
findings.push({ severity, code, summary, detail });
|
|
2391
2723
|
};
|
|
2392
2724
|
if (report.active === null) {
|
|
2393
|
-
push("fail", "active_missing", "active slot is empty", "activate
|
|
2725
|
+
push("fail", "active_missing", "active slot is empty", "no active pack found; this is the pre-bootstrap state — call `bootstrapRuntimeAttach()` to activate an initial pack before compiling or serving");
|
|
2394
2726
|
}
|
|
2395
2727
|
else if (!report.active.activationReady) {
|
|
2396
2728
|
push("fail", "active_unhealthy", `active slot is not activation-ready: ${report.active.packId}`, report.active.findings.join("; ") || "inspect the active pack payloads and activation pointers");
|
|
2397
2729
|
}
|
|
2398
2730
|
else {
|
|
2399
2731
|
push("pass", "active_ready", `active slot is ready: ${report.active.packId}`, "serving can inspect the active pack without activation drift");
|
|
2732
|
+
if (isAwaitingFirstExportSlot(report.active)) {
|
|
2733
|
+
push("warn", "bootstrap_waiting_for_first_export", "active pack bootstrapped without live exports yet", "serving is up from seed-state defaults; this is expected on first install — learning activates after the first turn is captured via `runRuntimeTurn()`");
|
|
2734
|
+
}
|
|
2400
2735
|
}
|
|
2401
2736
|
if (report.learnedRouting.required) {
|
|
2402
2737
|
if (report.learnedRouting.available) {
|
|
@@ -2506,7 +2841,139 @@ function formatCompactValue(value, empty = "none", maxLength = 24) {
|
|
|
2506
2841
|
}
|
|
2507
2842
|
return value.length <= maxLength ? value : `${value.slice(0, maxLength)}…`;
|
|
2508
2843
|
}
|
|
2509
|
-
|
|
2844
|
+
function summarizeCurrentProfileLogRoot(activationRoot) {
|
|
2845
|
+
const logRoot = path.join(path.resolve(activationRoot), LEARNING_SPINE_LOG_LAYOUT.dir);
|
|
2846
|
+
return existsSync(logRoot) ? logRoot : null;
|
|
2847
|
+
}
|
|
2848
|
+
function summarizeCurrentProfileLastLearningUpdateAt(activationRoot, learning) {
|
|
2849
|
+
const updates = readLearningSpineLogEntries(activationRoot, "pgRouteUpdates");
|
|
2850
|
+
return updates.at(-1)?.recordedAt ?? learning.lastMaterializedAt ?? null;
|
|
2851
|
+
}
|
|
2852
|
+
function summarizeCurrentProfileBrainSummary(input) {
|
|
2853
|
+
if (!input.attached) {
|
|
2854
|
+
return "Brain is not attached to the current Profile.";
|
|
2855
|
+
}
|
|
2856
|
+
if (input.serveState === "fail_open_static_context") {
|
|
2857
|
+
return "Brain is attached but would fail open to static context.";
|
|
2858
|
+
}
|
|
2859
|
+
if (input.serveState === "hard_fail") {
|
|
2860
|
+
return "Brain is attached but currently hard-fails learned routing.";
|
|
2861
|
+
}
|
|
2862
|
+
if (input.awaitingFirstExport) {
|
|
2863
|
+
return "Brain is serving the seed-state pack and awaiting the first export.";
|
|
2864
|
+
}
|
|
2865
|
+
if (input.brainState === "pg_promoted_pack_authoritative") {
|
|
2866
|
+
return "Brain is serving the promoted pack.";
|
|
2867
|
+
}
|
|
2868
|
+
if (input.serveState === "serving_active_pack") {
|
|
2869
|
+
return "Brain is serving the active pack with learned routing.";
|
|
2870
|
+
}
|
|
2871
|
+
return "Brain is attached but has not been compile-probed yet.";
|
|
2872
|
+
}
|
|
2873
|
+
function summarizeCurrentProfileBrainStatusLevel(input) {
|
|
2874
|
+
if (!input.attached) {
|
|
2875
|
+
return "fail";
|
|
2876
|
+
}
|
|
2877
|
+
if (input.serveState === "fail_open_static_context" || input.serveState === "hard_fail") {
|
|
2878
|
+
return "fail";
|
|
2879
|
+
}
|
|
2880
|
+
if (input.awaitingFirstExport || input.serveState === "unprobed") {
|
|
2881
|
+
return "warn";
|
|
2882
|
+
}
|
|
2883
|
+
return input.routeFreshness === "updated" ? "ok" : "warn";
|
|
2884
|
+
}
|
|
2885
|
+
function buildCurrentProfileBrainStatusFromReport(report, policyMode) {
|
|
2886
|
+
const attached = report.active !== null;
|
|
2887
|
+
const awaitingFirstExport = isAwaitingFirstExportSlot(report.active);
|
|
2888
|
+
const routerIdentity = report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? report.active?.routerIdentity ?? null;
|
|
2889
|
+
const routeFreshness = report.servePath.refreshStatus === "updated" || report.servePath.refreshStatus === "no_supervision"
|
|
2890
|
+
? report.servePath.refreshStatus
|
|
2891
|
+
: "unknown";
|
|
2892
|
+
const activePackId = report.brain.activePackId ?? report.servePath.activePackId ?? report.active?.packId ?? null;
|
|
2893
|
+
const status = summarizeCurrentProfileBrainStatusLevel({
|
|
2894
|
+
attached,
|
|
2895
|
+
serveState: report.servePath.state,
|
|
2896
|
+
routeFreshness,
|
|
2897
|
+
awaitingFirstExport
|
|
2898
|
+
});
|
|
2899
|
+
return {
|
|
2900
|
+
contract: CURRENT_PROFILE_BRAIN_STATUS_CONTRACT,
|
|
2901
|
+
generatedAt: report.generatedAt,
|
|
2902
|
+
host: {
|
|
2903
|
+
noun: "Host",
|
|
2904
|
+
runtimeOwner: "openclaw",
|
|
2905
|
+
activationRoot: report.activationRoot
|
|
2906
|
+
},
|
|
2907
|
+
profile: {
|
|
2908
|
+
noun: "Profile",
|
|
2909
|
+
selector: "current_profile",
|
|
2910
|
+
detail: attached
|
|
2911
|
+
? "The Host resolves the current Profile through the active Attachment boundary only."
|
|
2912
|
+
: "The current Profile has no active Brain attachment visible at the Host boundary."
|
|
2913
|
+
},
|
|
2914
|
+
brain: {
|
|
2915
|
+
noun: "Brain",
|
|
2916
|
+
activationRoot: attached ? report.activationRoot : null,
|
|
2917
|
+
logRoot: summarizeCurrentProfileLogRoot(report.activationRoot),
|
|
2918
|
+
activePackId,
|
|
2919
|
+
initMode: report.learnedRouting.initMode,
|
|
2920
|
+
state: report.brain.state,
|
|
2921
|
+
routeFreshness,
|
|
2922
|
+
routerIdentity,
|
|
2923
|
+
routerChecksum: report.learnedRouting.routerChecksum,
|
|
2924
|
+
lastExportAt: report.supervision.exportedAt,
|
|
2925
|
+
lastLearningUpdateAt: summarizeCurrentProfileLastLearningUpdateAt(report.activationRoot, report.learning),
|
|
2926
|
+
lastPromotionAt: report.promotion.lastPromotion.at,
|
|
2927
|
+
summary: summarizeCurrentProfileBrainSummary({
|
|
2928
|
+
attached,
|
|
2929
|
+
serveState: report.servePath.state,
|
|
2930
|
+
brainState: report.brain.state,
|
|
2931
|
+
awaitingFirstExport
|
|
2932
|
+
}),
|
|
2933
|
+
detail: report.brain.detail
|
|
2934
|
+
},
|
|
2935
|
+
attachment: attached
|
|
2936
|
+
? {
|
|
2937
|
+
noun: "Attachment",
|
|
2938
|
+
state: "attached",
|
|
2939
|
+
activationRoot: report.activationRoot,
|
|
2940
|
+
servingSlot: "active",
|
|
2941
|
+
policyMode,
|
|
2942
|
+
policy: buildCurrentProfileAttachmentPolicy(policyMode),
|
|
2943
|
+
detail: policyMode === "shared"
|
|
2944
|
+
? "current profile is attached to a shared OpenClawBrain activation boundary"
|
|
2945
|
+
: policyMode === "dedicated"
|
|
2946
|
+
? "current profile is attached to a dedicated OpenClawBrain activation boundary"
|
|
2947
|
+
: "current profile is attached to an OpenClawBrain activation boundary, but shared-vs-dedicated policy has not been declared"
|
|
2948
|
+
}
|
|
2949
|
+
: {
|
|
2950
|
+
noun: "Attachment",
|
|
2951
|
+
state: "not_attached",
|
|
2952
|
+
activationRoot: null,
|
|
2953
|
+
servingSlot: "none",
|
|
2954
|
+
policyMode,
|
|
2955
|
+
policy: buildCurrentProfileAttachmentPolicy(policyMode),
|
|
2956
|
+
detail: "current profile is not attached to an OpenClawBrain activation boundary"
|
|
2957
|
+
},
|
|
2958
|
+
brainStatus: {
|
|
2959
|
+
status,
|
|
2960
|
+
brainState: report.brain.state,
|
|
2961
|
+
serveState: report.servePath.state,
|
|
2962
|
+
usedLearnedRouteFn: report.servePath.usedLearnedRouteFn,
|
|
2963
|
+
failOpen: report.servePath.fallbackToStaticContext,
|
|
2964
|
+
awaitingFirstExport,
|
|
2965
|
+
detail: report.servePath.state === "serving_active_pack"
|
|
2966
|
+
? "current profile is serving compiled context from the active promoted pack"
|
|
2967
|
+
: report.servePath.state === "fail_open_static_context"
|
|
2968
|
+
? "current profile would fail open to static context because no serving pack is available"
|
|
2969
|
+
: report.servePath.state === "hard_fail"
|
|
2970
|
+
? "current profile cannot serve because the learned-route or activation requirement hard-failed"
|
|
2971
|
+
: "current profile serve state has not been compile-probed yet"
|
|
2972
|
+
},
|
|
2973
|
+
currentTurnAttribution: null
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
function buildOperatorSurfaceReport(input) {
|
|
2510
2977
|
const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
|
|
2511
2978
|
const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
|
|
2512
2979
|
const inspection = inspectActivationState(activationRoot, updatedAt);
|
|
@@ -2535,6 +3002,7 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
2535
3002
|
routerTrainedAt: observability.learnedRouteFn.routerTrainedAt,
|
|
2536
3003
|
objective: observability.learnedRouteFn.objective,
|
|
2537
3004
|
pgProfile: observability.learnedRouteFn.pgProfile,
|
|
3005
|
+
routerChecksum: observability.learnedRouteFn.routerChecksum,
|
|
2538
3006
|
objectiveChecksum: observability.learnedRouteFn.objectiveChecksum,
|
|
2539
3007
|
updateMechanism: observability.learnedRouteFn.updateMechanism,
|
|
2540
3008
|
updateVersion: observability.learnedRouteFn.updateVersion,
|
|
@@ -2572,11 +3040,9 @@ export function buildOperatorSurfaceReport(input) {
|
|
|
2572
3040
|
findings
|
|
2573
3041
|
};
|
|
2574
3042
|
}
|
|
2575
|
-
function
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
}
|
|
2579
|
-
return `${label.padEnd(11)}pack=${slot.packId} ready=${yesNo(slot.activationReady)} route=${slot.routePolicy} snapshot=${slot.workspaceSnapshot} export=${formatCompactValue(slot.eventExportDigest)} range=${slot.eventRange.start}-${slot.eventRange.end}/${slot.eventRange.count} built=${slot.builtAt} updated=${slot.updatedAt ?? "unknown"}`;
|
|
3043
|
+
export function describeCurrentProfileBrainStatus(input) {
|
|
3044
|
+
const report = buildOperatorSurfaceReport(input);
|
|
3045
|
+
return buildCurrentProfileBrainStatusFromReport(report, normalizeBrainAttachmentPolicy(input.brainAttachmentPolicy));
|
|
2580
3046
|
}
|
|
2581
3047
|
export function formatOperatorRollbackReport(result) {
|
|
2582
3048
|
const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
|
|
@@ -2589,94 +3055,6 @@ export function formatOperatorRollbackReport(result) {
|
|
|
2589
3055
|
`findings ${formatList(result.findings)}`
|
|
2590
3056
|
].join("\n");
|
|
2591
3057
|
}
|
|
2592
|
-
export function formatOperatorStatusReport(report) {
|
|
2593
|
-
return [
|
|
2594
|
-
`STATUS ${report.status}`,
|
|
2595
|
-
formatSlot("active", report.active),
|
|
2596
|
-
formatSlot("candidate", report.candidate),
|
|
2597
|
-
`freshness candidateAhead=${yesNo(report.freshness.activeBehindPromotionReadyCandidate)} delta=${formatList(report.freshness.candidateAheadBy)} lastPromotion=${report.promotion.lastPromotion.known ? report.promotion.lastPromotion.at : report.promotion.lastPromotion.confidence}`,
|
|
2598
|
-
`brain state=${report.brain.state} init=${report.brain.initMode ?? "unknown"} plasticity=${report.brain.runtimePlasticitySource ?? "unknown"} seedVisible=${yesNo(report.brain.seedStateVisible)} seedBlocks=${report.brain.seedBlockCount}`,
|
|
2599
|
-
`serve pack=${report.servePath.activePackId ?? report.active?.packId ?? "none"} state=${report.servePath.state} failOpen=${yesNo(report.servePath.fallbackToStaticContext)} hardFail=${yesNo(report.servePath.hardRequirementViolated)} usedRouteFn=${yesNo(report.servePath.usedLearnedRouteFn)} selection=${report.servePath.selectionMode ?? "unknown"} tiers=${report.servePath.contextAttribution.selectionTiers ?? "unknown"}`,
|
|
2600
|
-
`context kernel=${report.servePath.contextAttribution.stableKernelBlockCount} brain=${report.servePath.contextAttribution.brainCompiledBlockCount} evidence=${report.servePath.contextAttribution.evidence} kernelSrc=${formatCompactList(report.servePath.contextAttribution.stableKernelSources)} brainSrc=${formatCompactList(report.servePath.contextAttribution.brainCompiledSources)}`,
|
|
2601
|
-
`route router=${report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? "none"} refresh=${report.servePath.refreshStatus ?? "unknown"} freshness=${formatCompactValue(report.servePath.freshnessChecksum ?? report.learnedRouting.freshnessChecksum)} objective=${formatCompactValue(report.learnedRouting.objectiveChecksum)} labels=${report.learnedRouting.collectedLabelsTotal ?? 0} updates=${report.learnedRouting.updateCount ?? 0}`,
|
|
2602
|
-
`supervision flowing=${yesNo(report.supervision.flowing)} source=${report.supervision.freshestSourceStream ?? "none"} freshest=${report.supervision.freshestCreatedAt ?? "unknown"} humanLabels=${report.supervision.humanLabelCount ?? 0} export=${report.supervision.exportDigest ?? "none"}`,
|
|
2603
|
-
`learning mode=${report.learning.mode} next=${report.learning.nextPriorityLane} pendingLive=${report.learning.pendingLive ?? 0} pendingBackfill=${report.learning.pendingBackfill ?? 0} activeBuilt=${report.active?.builtAt ?? "unknown"} supervisionFreshest=${report.supervision.freshestCreatedAt ?? "unknown"} teacher=${report.teacherLoop.latestFreshness} lastNoOp=${report.teacherLoop.lastNoOpReason} lastProcessed=${report.teacherLoop.lastProcessedAt ?? "unknown"} materialized=${report.teacherLoop.lastMaterializedPackId ?? "none"}`,
|
|
2604
|
-
`rollback ready=${yesNo(report.rollback.allowed)} previous=${report.rollback.previousPackId ?? "none"} findings=${formatList(report.rollback.findings)}`
|
|
2605
|
-
].join("\n");
|
|
2606
|
-
}
|
|
2607
|
-
function orderedDoctorFindings(findings) {
|
|
2608
|
-
const severityOrder = {
|
|
2609
|
-
fail: 0,
|
|
2610
|
-
warn: 1,
|
|
2611
|
-
pass: 2
|
|
2612
|
-
};
|
|
2613
|
-
return [...findings].sort((left, right) => {
|
|
2614
|
-
if (severityOrder[left.severity] !== severityOrder[right.severity]) {
|
|
2615
|
-
return severityOrder[left.severity] - severityOrder[right.severity];
|
|
2616
|
-
}
|
|
2617
|
-
return left.code.localeCompare(right.code);
|
|
2618
|
-
});
|
|
2619
|
-
}
|
|
2620
|
-
function buildDoctorNextSteps(findings) {
|
|
2621
|
-
const steps = new Map();
|
|
2622
|
-
for (const finding of findings) {
|
|
2623
|
-
switch (finding.code) {
|
|
2624
|
-
case "active_missing":
|
|
2625
|
-
steps.set(finding.code, "activate a healthy pack before serving or promotion checks");
|
|
2626
|
-
break;
|
|
2627
|
-
case "active_unhealthy":
|
|
2628
|
-
case "learned_route_missing":
|
|
2629
|
-
steps.set(finding.code, "repair the active pack or promote a healthy learned-routing candidate");
|
|
2630
|
-
break;
|
|
2631
|
-
case "candidate_missing":
|
|
2632
|
-
case "promotion_blocked":
|
|
2633
|
-
case "candidate_unhealthy":
|
|
2634
|
-
steps.set(finding.code, "stage or repair a fresher candidate pack before attempting promotion");
|
|
2635
|
-
break;
|
|
2636
|
-
case "rollback_blocked":
|
|
2637
|
-
steps.set(finding.code, "capture a promotion before expecting rollback readiness; previous pointer is the key guardrail");
|
|
2638
|
-
break;
|
|
2639
|
-
case "last_promotion_unknown":
|
|
2640
|
-
steps.set(finding.code, "treat `active.updatedAt` as the last pointer move only; do not claim exact last-promotion time without previous-pointer lineage");
|
|
2641
|
-
break;
|
|
2642
|
-
case "supervision_unavailable":
|
|
2643
|
-
case "supervision_not_flowing":
|
|
2644
|
-
steps.set(finding.code, "pass `--event-export <bundle-root-or-payload>` to inspect local supervision freshness and teacher signals");
|
|
2645
|
-
break;
|
|
2646
|
-
case "serve_path_fail_open":
|
|
2647
|
-
steps.set(finding.code, "decide whether fail-open static context is acceptable for this runtime and repair activation if it is not");
|
|
2648
|
-
break;
|
|
2649
|
-
case "serve_path_hard_fail":
|
|
2650
|
-
case "serve_path_route_evidence_missing":
|
|
2651
|
-
steps.set(finding.code, "repair the active learned-routing pack or promote a healthy candidate before serving again");
|
|
2652
|
-
break;
|
|
2653
|
-
case "brain_context_kernel_only":
|
|
2654
|
-
steps.set(finding.code, "compare stable-kernel versus brain-compiled sources; if you expected live context, refresh exports or promote a fresher candidate pack");
|
|
2655
|
-
break;
|
|
2656
|
-
case "teacher_snapshot_unavailable":
|
|
2657
|
-
steps.set(finding.code, "serialize `teacherLoop.snapshot()` or `await teacherLoop.flush()` and pass `--teacher-snapshot <snapshot.json>` when you need the last no-op reason");
|
|
2658
|
-
break;
|
|
2659
|
-
default:
|
|
2660
|
-
break;
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
return [...steps.values()];
|
|
2664
|
-
}
|
|
2665
|
-
export function formatOperatorDoctorReport(report) {
|
|
2666
|
-
const lines = [`DOCTOR ${report.status}`];
|
|
2667
|
-
for (const finding of orderedDoctorFindings(report.findings)) {
|
|
2668
|
-
lines.push(`${finding.severity.toUpperCase()} ${finding.summary}`);
|
|
2669
|
-
lines.push(` ${finding.detail}`);
|
|
2670
|
-
}
|
|
2671
|
-
const nextSteps = buildDoctorNextSteps(report.findings.filter((finding) => finding.severity !== "pass"));
|
|
2672
|
-
if (nextSteps.length > 0) {
|
|
2673
|
-
lines.push("NEXT");
|
|
2674
|
-
for (const step of nextSteps) {
|
|
2675
|
-
lines.push(`- ${step}`);
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
return lines.join("\n");
|
|
2679
|
-
}
|
|
2680
3058
|
/**
|
|
2681
3059
|
* Describes the kernel/brain boundary for a single compile response.
|
|
2682
3060
|
*
|