@openclawbrain/cli 0.4.13 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- package/dist/extension/index.js +1 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/runtime-guard.d.ts +2 -0
- package/dist/extension/runtime-guard.js +33 -1
- package/dist/extension/runtime-guard.js.map +1 -1
- package/dist/src/attachment-truth.d.ts +32 -22
- package/dist/src/attachment-truth.js +338 -186
- package/dist/src/cli.d.ts +13 -1
- package/dist/src/cli.js +439 -95
- package/dist/src/install-converge.js +217 -0
- package/dist/src/learning-spine.d.ts +2 -1
- package/dist/src/learning-spine.js +7 -19
- package/dist/src/local-learner.d.ts +24 -0
- package/dist/src/local-learner.js +216 -29
- package/dist/src/proof-command.js +105 -41
- package/dist/src/runtime-core.js +578 -0
- package/dist/src/teacher-decision-match.js +240 -0
- package/dist/src/teacher-labeler.js +4 -30
- package/package.json +4 -3
|
@@ -4054,15 +4054,36 @@ function normalizeOutcomeMatchText(value) {
|
|
|
4054
4054
|
? value.replace(/\s+/g, " ").trim().toLowerCase()
|
|
4055
4055
|
: null;
|
|
4056
4056
|
}
|
|
4057
|
+
function normalizeObservationOptionalString(value) {
|
|
4058
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
4059
|
+
}
|
|
4060
|
+
function buildObservationSelectionDigestKey(selectionDigest, activePackGraphChecksum) {
|
|
4061
|
+
const normalizedSelectionDigest = normalizeObservationOptionalString(selectionDigest);
|
|
4062
|
+
const normalizedGraphChecksum = normalizeObservationOptionalString(activePackGraphChecksum);
|
|
4063
|
+
if (normalizedSelectionDigest === null || normalizedGraphChecksum === null) {
|
|
4064
|
+
return null;
|
|
4065
|
+
}
|
|
4066
|
+
return `${normalizedGraphChecksum}|${normalizedSelectionDigest}`;
|
|
4067
|
+
}
|
|
4057
4068
|
function parseObservationRouteMetadata(value) {
|
|
4058
4069
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4059
4070
|
return {
|
|
4071
|
+
serveDecisionRecordId: null,
|
|
4072
|
+
selectionDigest: null,
|
|
4073
|
+
turnCompileEventId: null,
|
|
4074
|
+
activePackId: null,
|
|
4075
|
+
activePackGraphChecksum: null,
|
|
4060
4076
|
selectedNodeIds: [],
|
|
4061
4077
|
selectedPathNodeIds: []
|
|
4062
4078
|
};
|
|
4063
4079
|
}
|
|
4064
4080
|
try {
|
|
4065
4081
|
const parsed = JSON.parse(value);
|
|
4082
|
+
const serveDecisionRecordId = normalizeObservationOptionalString(parsed?.serveDecisionRecordId);
|
|
4083
|
+
const selectionDigest = normalizeObservationOptionalString(parsed?.selectionDigest);
|
|
4084
|
+
const turnCompileEventId = normalizeObservationOptionalString(parsed?.turnCompileEventId);
|
|
4085
|
+
const activePackId = normalizeObservationOptionalString(parsed?.activePackId);
|
|
4086
|
+
const activePackGraphChecksum = normalizeObservationOptionalString(parsed?.activePackGraphChecksum);
|
|
4066
4087
|
const selectedNodeIds = Array.isArray(parsed?.selectedNodeIds)
|
|
4067
4088
|
? parsed.selectedNodeIds.filter((entry) => typeof entry === "string")
|
|
4068
4089
|
: [];
|
|
@@ -4070,12 +4091,22 @@ function parseObservationRouteMetadata(value) {
|
|
|
4070
4091
|
? parsed.selectedPathNodeIds.filter((entry) => typeof entry === "string")
|
|
4071
4092
|
: [];
|
|
4072
4093
|
return {
|
|
4094
|
+
serveDecisionRecordId,
|
|
4095
|
+
selectionDigest,
|
|
4096
|
+
turnCompileEventId,
|
|
4097
|
+
activePackId,
|
|
4098
|
+
activePackGraphChecksum,
|
|
4073
4099
|
selectedNodeIds,
|
|
4074
4100
|
selectedPathNodeIds
|
|
4075
4101
|
};
|
|
4076
4102
|
}
|
|
4077
4103
|
catch {
|
|
4078
4104
|
return {
|
|
4105
|
+
serveDecisionRecordId: null,
|
|
4106
|
+
selectionDigest: null,
|
|
4107
|
+
turnCompileEventId: null,
|
|
4108
|
+
activePackId: null,
|
|
4109
|
+
activePackGraphChecksum: null,
|
|
4079
4110
|
selectedNodeIds: [],
|
|
4080
4111
|
selectedPathNodeIds: []
|
|
4081
4112
|
};
|
|
@@ -4099,8 +4130,24 @@ function loadTeacherObservationOutcomesFromActivation(activationRoot) {
|
|
|
4099
4130
|
if (table === undefined) {
|
|
4100
4131
|
return [];
|
|
4101
4132
|
}
|
|
4133
|
+
const columns = new Set(db.prepare("PRAGMA table_info(brain_observations)").all().map((column) => String(column.name ?? "")));
|
|
4134
|
+
const selectableColumns = [
|
|
4135
|
+
"id",
|
|
4136
|
+
"query_text",
|
|
4137
|
+
"route_metadata_json",
|
|
4138
|
+
"final_score",
|
|
4139
|
+
"confidence",
|
|
4140
|
+
"created_at",
|
|
4141
|
+
"updated_at",
|
|
4142
|
+
"evaluated_at",
|
|
4143
|
+
columns.has("serve_decision_record_id") ? "serve_decision_record_id" : "NULL AS serve_decision_record_id",
|
|
4144
|
+
columns.has("selection_digest") ? "selection_digest" : "NULL AS selection_digest",
|
|
4145
|
+
columns.has("turn_compile_event_id") ? "turn_compile_event_id" : "NULL AS turn_compile_event_id",
|
|
4146
|
+
columns.has("active_pack_id") ? "active_pack_id" : "NULL AS active_pack_id",
|
|
4147
|
+
columns.has("active_pack_graph_checksum") ? "active_pack_graph_checksum" : "NULL AS active_pack_graph_checksum"
|
|
4148
|
+
];
|
|
4102
4149
|
const rows = db.prepare(`
|
|
4103
|
-
SELECT
|
|
4150
|
+
SELECT ${selectableColumns.join(", ")}
|
|
4104
4151
|
FROM brain_observations
|
|
4105
4152
|
WHERE final_score IS NOT NULL
|
|
4106
4153
|
ORDER BY created_at DESC
|
|
@@ -4116,6 +4163,11 @@ function loadTeacherObservationOutcomesFromActivation(activationRoot) {
|
|
|
4116
4163
|
createdAt: Number.isFinite(Number(row.created_at)) ? Number(row.created_at) : null,
|
|
4117
4164
|
updatedAt: Number.isFinite(Number(row.updated_at)) ? Number(row.updated_at) : null,
|
|
4118
4165
|
evaluatedAt: Number.isFinite(Number(row.evaluated_at)) ? Number(row.evaluated_at) : null,
|
|
4166
|
+
serveDecisionRecordId: normalizeObservationOptionalString(row.serve_decision_record_id) ?? routeMetadata.serveDecisionRecordId,
|
|
4167
|
+
selectionDigest: normalizeObservationOptionalString(row.selection_digest) ?? routeMetadata.selectionDigest,
|
|
4168
|
+
turnCompileEventId: normalizeObservationOptionalString(row.turn_compile_event_id) ?? routeMetadata.turnCompileEventId,
|
|
4169
|
+
activePackId: normalizeObservationOptionalString(row.active_pack_id) ?? routeMetadata.activePackId,
|
|
4170
|
+
activePackGraphChecksum: normalizeObservationOptionalString(row.active_pack_graph_checksum) ?? routeMetadata.activePackGraphChecksum,
|
|
4119
4171
|
selectedNodeIds: routeMetadata.selectedNodeIds,
|
|
4120
4172
|
selectedPathNodeIds: routeMetadata.selectedPathNodeIds
|
|
4121
4173
|
};
|
|
@@ -4197,45 +4249,172 @@ function compareDecisionObservationMatch(left, right) {
|
|
|
4197
4249
|
}
|
|
4198
4250
|
return 0;
|
|
4199
4251
|
}
|
|
4252
|
+
function emptyTeacherObservationBindingStats() {
|
|
4253
|
+
return {
|
|
4254
|
+
totalObservationCount: 0,
|
|
4255
|
+
nonZeroObservationCount: 0,
|
|
4256
|
+
skippedZeroRewardCount: 0,
|
|
4257
|
+
matched: {
|
|
4258
|
+
exactDecisionId: 0,
|
|
4259
|
+
exactSelectionDigest: 0,
|
|
4260
|
+
turnCompileEventId: 0,
|
|
4261
|
+
legacyHeuristic: 0
|
|
4262
|
+
},
|
|
4263
|
+
unmatched: {
|
|
4264
|
+
exactDecisionId: 0,
|
|
4265
|
+
exactSelectionDigest: 0,
|
|
4266
|
+
turnCompileEventId: 0,
|
|
4267
|
+
legacyHeuristic: 0
|
|
4268
|
+
},
|
|
4269
|
+
ambiguous: {
|
|
4270
|
+
exactDecisionId: 0,
|
|
4271
|
+
exactSelectionDigest: 0,
|
|
4272
|
+
turnCompileEventId: 0,
|
|
4273
|
+
legacyHeuristic: 0
|
|
4274
|
+
}
|
|
4275
|
+
};
|
|
4276
|
+
}
|
|
4277
|
+
function summarizeTeacherObservationBindingStats(stats) {
|
|
4278
|
+
return [
|
|
4279
|
+
`matched(exact_decision_id=${stats.matched.exactDecisionId},exact_selection_digest=${stats.matched.exactSelectionDigest},turn_compile_event_id=${stats.matched.turnCompileEventId},legacy_heuristic=${stats.matched.legacyHeuristic})`,
|
|
4280
|
+
`unmatched(exact_decision_id=${stats.unmatched.exactDecisionId},exact_selection_digest=${stats.unmatched.exactSelectionDigest},turn_compile_event_id=${stats.unmatched.turnCompileEventId},legacy_heuristic=${stats.unmatched.legacyHeuristic})`,
|
|
4281
|
+
`ambiguous(exact_decision_id=${stats.ambiguous.exactDecisionId},exact_selection_digest=${stats.ambiguous.exactSelectionDigest},turn_compile_event_id=${stats.ambiguous.turnCompileEventId},legacy_heuristic=${stats.ambiguous.legacyHeuristic})`,
|
|
4282
|
+
`non_zero=${stats.nonZeroObservationCount}`,
|
|
4283
|
+
`skipped_zero=${stats.skippedZeroRewardCount}`
|
|
4284
|
+
].join(" ");
|
|
4285
|
+
}
|
|
4286
|
+
function buildTeacherObservationDecisionIndexes(decisions) {
|
|
4287
|
+
const byRecordId = new Map();
|
|
4288
|
+
const bySelectionDigest = new Map();
|
|
4289
|
+
const ambiguousSelectionDigests = new Set();
|
|
4290
|
+
const byTurnCompileEventId = new Map();
|
|
4291
|
+
const ambiguousTurnCompileEventIds = new Set();
|
|
4292
|
+
for (const decision of decisions) {
|
|
4293
|
+
byRecordId.set(decision.recordId, decision);
|
|
4294
|
+
const selectionDigestKey = buildObservationSelectionDigestKey(decision.selectionDigest, decision.activePackGraphChecksum);
|
|
4295
|
+
if (selectionDigestKey !== null) {
|
|
4296
|
+
if (bySelectionDigest.has(selectionDigestKey)) {
|
|
4297
|
+
bySelectionDigest.delete(selectionDigestKey);
|
|
4298
|
+
ambiguousSelectionDigests.add(selectionDigestKey);
|
|
4299
|
+
}
|
|
4300
|
+
else if (!ambiguousSelectionDigests.has(selectionDigestKey)) {
|
|
4301
|
+
bySelectionDigest.set(selectionDigestKey, decision);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
const turnCompileEventId = normalizeObservationOptionalString(decision.turnCompileEventId);
|
|
4305
|
+
if (turnCompileEventId !== null) {
|
|
4306
|
+
if (byTurnCompileEventId.has(turnCompileEventId)) {
|
|
4307
|
+
byTurnCompileEventId.delete(turnCompileEventId);
|
|
4308
|
+
ambiguousTurnCompileEventIds.add(turnCompileEventId);
|
|
4309
|
+
}
|
|
4310
|
+
else if (!ambiguousTurnCompileEventIds.has(turnCompileEventId)) {
|
|
4311
|
+
byTurnCompileEventId.set(turnCompileEventId, decision);
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
return {
|
|
4316
|
+
byRecordId,
|
|
4317
|
+
bySelectionDigest,
|
|
4318
|
+
ambiguousSelectionDigests,
|
|
4319
|
+
byTurnCompileEventId,
|
|
4320
|
+
ambiguousTurnCompileEventIds
|
|
4321
|
+
};
|
|
4322
|
+
}
|
|
4323
|
+
function resolveLegacyTeacherObservationMatch(decisions, observation) {
|
|
4324
|
+
const matches = decisions
|
|
4325
|
+
.map((decision) => {
|
|
4326
|
+
const key = decisionObservationMatchKey(decision, observation);
|
|
4327
|
+
return key === null
|
|
4328
|
+
? null
|
|
4329
|
+
: {
|
|
4330
|
+
decision,
|
|
4331
|
+
key
|
|
4332
|
+
};
|
|
4333
|
+
})
|
|
4334
|
+
.filter((entry) => entry !== null)
|
|
4335
|
+
.sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
|
|
4336
|
+
const best = matches[0] ?? null;
|
|
4337
|
+
const runnerUp = matches[1] ?? null;
|
|
4338
|
+
if (best === null) {
|
|
4339
|
+
return { kind: "unmatched", mode: "legacyHeuristic" };
|
|
4340
|
+
}
|
|
4341
|
+
if (runnerUp !== null && compareDecisionObservationMatch(best.key, runnerUp.key) === 0) {
|
|
4342
|
+
return { kind: "ambiguous", mode: "legacyHeuristic" };
|
|
4343
|
+
}
|
|
4344
|
+
return {
|
|
4345
|
+
kind: "matched",
|
|
4346
|
+
mode: "legacyHeuristic",
|
|
4347
|
+
decision: best.decision
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
function resolveTeacherObservationDecisionMatch(decisions, observation, indexes) {
|
|
4351
|
+
const serveDecisionRecordId = normalizeObservationOptionalString(observation.serveDecisionRecordId);
|
|
4352
|
+
if (serveDecisionRecordId !== null) {
|
|
4353
|
+
const decision = indexes.byRecordId.get(serveDecisionRecordId) ?? null;
|
|
4354
|
+
return decision === null
|
|
4355
|
+
? { kind: "unmatched", mode: "exactDecisionId" }
|
|
4356
|
+
: { kind: "matched", mode: "exactDecisionId", decision };
|
|
4357
|
+
}
|
|
4358
|
+
const selectionDigestKey = buildObservationSelectionDigestKey(observation.selectionDigest, observation.activePackGraphChecksum);
|
|
4359
|
+
if (selectionDigestKey !== null) {
|
|
4360
|
+
if (indexes.ambiguousSelectionDigests.has(selectionDigestKey)) {
|
|
4361
|
+
return { kind: "ambiguous", mode: "exactSelectionDigest" };
|
|
4362
|
+
}
|
|
4363
|
+
const decision = indexes.bySelectionDigest.get(selectionDigestKey) ?? null;
|
|
4364
|
+
return decision === null
|
|
4365
|
+
? { kind: "unmatched", mode: "exactSelectionDigest" }
|
|
4366
|
+
: { kind: "matched", mode: "exactSelectionDigest", decision };
|
|
4367
|
+
}
|
|
4368
|
+
if (normalizeObservationOptionalString(observation.selectionDigest) !== null
|
|
4369
|
+
|| normalizeObservationOptionalString(observation.activePackGraphChecksum) !== null) {
|
|
4370
|
+
return { kind: "unmatched", mode: "exactSelectionDigest" };
|
|
4371
|
+
}
|
|
4372
|
+
const turnCompileEventId = normalizeObservationOptionalString(observation.turnCompileEventId);
|
|
4373
|
+
if (turnCompileEventId !== null) {
|
|
4374
|
+
if (indexes.ambiguousTurnCompileEventIds.has(turnCompileEventId)) {
|
|
4375
|
+
return { kind: "ambiguous", mode: "turnCompileEventId" };
|
|
4376
|
+
}
|
|
4377
|
+
const decision = indexes.byTurnCompileEventId.get(turnCompileEventId) ?? null;
|
|
4378
|
+
return decision === null
|
|
4379
|
+
? { kind: "unmatched", mode: "turnCompileEventId" }
|
|
4380
|
+
: { kind: "matched", mode: "turnCompileEventId", decision };
|
|
4381
|
+
}
|
|
4382
|
+
return resolveLegacyTeacherObservationMatch(decisions, observation);
|
|
4383
|
+
}
|
|
4200
4384
|
function joinDecisionsWithTeacherObservationOutcomes(decisions, observationOutcomes) {
|
|
4201
4385
|
const outcomes = new Map();
|
|
4386
|
+
const stats = emptyTeacherObservationBindingStats();
|
|
4202
4387
|
for (const decision of decisions) {
|
|
4203
4388
|
outcomes.set(decision.recordId, 0);
|
|
4204
4389
|
}
|
|
4205
4390
|
if (!Array.isArray(observationOutcomes) || observationOutcomes.length === 0) {
|
|
4206
|
-
return outcomes;
|
|
4391
|
+
return { outcomes, stats };
|
|
4207
4392
|
}
|
|
4393
|
+
const indexes = buildTeacherObservationDecisionIndexes(decisions);
|
|
4394
|
+
stats.totalObservationCount = observationOutcomes.length;
|
|
4208
4395
|
for (const observation of observationOutcomes) {
|
|
4209
|
-
const
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
return key === null
|
|
4213
|
-
? null
|
|
4214
|
-
: {
|
|
4215
|
-
decision,
|
|
4216
|
-
key
|
|
4217
|
-
};
|
|
4218
|
-
})
|
|
4219
|
-
.filter((entry) => entry !== null)
|
|
4220
|
-
.sort((left, right) => compareDecisionObservationMatch(left.key, right.key));
|
|
4221
|
-
const best = matches[0] ?? null;
|
|
4222
|
-
const runnerUp = matches[1] ?? null;
|
|
4223
|
-
if (best === null) {
|
|
4396
|
+
const reward = clampTeacherObservationOutcome(observation.finalScore);
|
|
4397
|
+
if (reward === 0) {
|
|
4398
|
+
stats.skippedZeroRewardCount += 1;
|
|
4224
4399
|
continue;
|
|
4225
4400
|
}
|
|
4226
|
-
|
|
4401
|
+
stats.nonZeroObservationCount += 1;
|
|
4402
|
+
const match = resolveTeacherObservationDecisionMatch(decisions, observation, indexes);
|
|
4403
|
+
if (match.kind === "unmatched") {
|
|
4404
|
+
stats.unmatched[match.mode] += 1;
|
|
4227
4405
|
continue;
|
|
4228
4406
|
}
|
|
4229
|
-
|
|
4230
|
-
|
|
4407
|
+
if (match.kind === "ambiguous") {
|
|
4408
|
+
stats.ambiguous[match.mode] += 1;
|
|
4231
4409
|
continue;
|
|
4232
4410
|
}
|
|
4233
|
-
|
|
4411
|
+
stats.matched[match.mode] += 1;
|
|
4412
|
+
const current = outcomes.get(match.decision.recordId) ?? 0;
|
|
4234
4413
|
if (current === 0 || Math.abs(reward) > Math.abs(current)) {
|
|
4235
|
-
outcomes.set(
|
|
4414
|
+
outcomes.set(match.decision.recordId, reward);
|
|
4236
4415
|
}
|
|
4237
4416
|
}
|
|
4238
|
-
return outcomes;
|
|
4417
|
+
return { outcomes, stats };
|
|
4239
4418
|
}
|
|
4240
4419
|
|
|
4241
4420
|
export function joinDecisionsWithFeedback(decisions, eventExport, maxDelayMs = 300_000) {
|
|
@@ -4627,8 +4806,8 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4627
4806
|
const remappedServeTimeDecisions = serveTimeDecisions.map((decision) => remapServeTimeDecisionToGraph(decision, resolveBlockId));
|
|
4628
4807
|
// 2. Join serve-time decisions with explicit feedback first, then let teacher-v2 observations override when available.
|
|
4629
4808
|
const outcomeMap = joinDecisionsWithFeedback(remappedServeTimeDecisions, eventExport);
|
|
4630
|
-
const
|
|
4631
|
-
for (const [decisionId, reward] of
|
|
4809
|
+
const teacherObservationBindings = joinDecisionsWithTeacherObservationOutcomes(remappedServeTimeDecisions, teacherObservationOutcomes);
|
|
4810
|
+
for (const [decisionId, reward] of teacherObservationBindings.outcomes.entries()) {
|
|
4632
4811
|
if (reward !== 0) {
|
|
4633
4812
|
outcomeMap.set(decisionId, reward);
|
|
4634
4813
|
}
|
|
@@ -4695,7 +4874,8 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4695
4874
|
if (fallback.policyUpdates.length > 0) {
|
|
4696
4875
|
return {
|
|
4697
4876
|
artifact: fallback,
|
|
4698
|
-
updatedBaseline: currentBaseline
|
|
4877
|
+
updatedBaseline: currentBaseline,
|
|
4878
|
+
observationBindingStats: teacherObservationBindings.stats
|
|
4699
4879
|
};
|
|
4700
4880
|
}
|
|
4701
4881
|
}
|
|
@@ -4717,7 +4897,7 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4717
4897
|
: remappedServeTimeDecisions.length === 0
|
|
4718
4898
|
? "no serve-time decisions supplied for V2 learned routing refresh"
|
|
4719
4899
|
: supervisedTrajectoryCount === 0
|
|
4720
|
-
?
|
|
4900
|
+
? `no outcomes found for serve-time decisions (${summarizeTeacherObservationBindingStats(teacherObservationBindings.stats)})`
|
|
4721
4901
|
: "trajectory updates produced no learned routing delta";
|
|
4722
4902
|
const artifact = {
|
|
4723
4903
|
routerIdentity: `${packId}:route_fn`,
|
|
@@ -4765,7 +4945,11 @@ function createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, se
|
|
|
4765
4945
|
traces,
|
|
4766
4946
|
policyUpdates
|
|
4767
4947
|
};
|
|
4768
|
-
return {
|
|
4948
|
+
return {
|
|
4949
|
+
artifact,
|
|
4950
|
+
updatedBaseline: currentBaseline,
|
|
4951
|
+
observationBindingStats: teacherObservationBindings.stats
|
|
4952
|
+
};
|
|
4769
4953
|
}
|
|
4770
4954
|
function resolveEventExport(input) {
|
|
4771
4955
|
if (input.eventExports === undefined) {
|
|
@@ -4922,6 +5106,7 @@ export function buildCandidatePack(input) {
|
|
|
4922
5106
|
const vectors = createVectorsPayload(graph);
|
|
4923
5107
|
let router = null;
|
|
4924
5108
|
let updatedBaseline = null;
|
|
5109
|
+
let observationBindingStats = null;
|
|
4925
5110
|
const teacherObservationOutcomes = input.activationRoot === undefined
|
|
4926
5111
|
? []
|
|
4927
5112
|
: loadTeacherObservationOutcomesFromActivation(input.activationRoot);
|
|
@@ -4930,6 +5115,7 @@ export function buildCandidatePack(input) {
|
|
|
4930
5115
|
const v2Result = createRouterArtifactV2(packId, builtAt, graph, vectors, eventExport, input.serveTimeDecisions, input.baselineState ?? initBaseline(), input.sparseFeedback, input.principalBacklog, teacherObservationOutcomes);
|
|
4931
5116
|
router = v2Result.artifact;
|
|
4932
5117
|
updatedBaseline = v2Result.updatedBaseline;
|
|
5118
|
+
observationBindingStats = v2Result.observationBindingStats;
|
|
4933
5119
|
}
|
|
4934
5120
|
else {
|
|
4935
5121
|
router = createRouterArtifact(packId, builtAt, graph, vectors, eventExport, input.sparseFeedback, input.principalBacklog);
|
|
@@ -5045,7 +5231,8 @@ export function buildCandidatePack(input) {
|
|
|
5045
5231
|
pgVersionUsed: input.learnedRouting ? (useV2 ? "v2" : "v1") : null,
|
|
5046
5232
|
decisionLogCount,
|
|
5047
5233
|
fallbackReason,
|
|
5048
|
-
updatedBaseline
|
|
5234
|
+
updatedBaseline,
|
|
5235
|
+
observationBindingStats
|
|
5049
5236
|
},
|
|
5050
5237
|
summary: {
|
|
5051
5238
|
packId,
|
|
@@ -19,6 +19,22 @@ function normalizeOptionalCliString(value) {
|
|
|
19
19
|
return trimmed.length > 0 ? trimmed : null;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function normalizeReportedProofPath(filePath) {
|
|
23
|
+
const normalizedPath = normalizeOptionalCliString(filePath);
|
|
24
|
+
if (normalizedPath === null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (normalizedPath === "~") {
|
|
28
|
+
return homedir();
|
|
29
|
+
}
|
|
30
|
+
if (normalizedPath.startsWith("~/")) {
|
|
31
|
+
return path.join(homedir(), normalizedPath.slice(2));
|
|
32
|
+
}
|
|
33
|
+
return path.isAbsolute(normalizedPath)
|
|
34
|
+
? normalizedPath
|
|
35
|
+
: path.resolve(normalizedPath);
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
function canonicalizeExistingProofPath(filePath) {
|
|
23
39
|
const resolvedPath = path.resolve(filePath);
|
|
24
40
|
try {
|
|
@@ -191,6 +207,12 @@ function readJsonSnapshot(filePath) {
|
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
210
|
+
function describeStepWarning(step) {
|
|
211
|
+
return step.captureState === "partial"
|
|
212
|
+
? `${step.stepId} ended as ${step.resultClass} with partial capture`
|
|
213
|
+
: `${step.stepId} ended as ${step.resultClass}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
194
216
|
function extractStartupBreadcrumbs(logText, bundleStartedAtIso) {
|
|
195
217
|
if (!logText) {
|
|
196
218
|
return { all: [], afterBundleStart: [] };
|
|
@@ -230,6 +252,7 @@ function extractStatusSignals(statusText) {
|
|
|
230
252
|
serveActivePack: /serve\s+state=serving_active_pack/.test(statusText),
|
|
231
253
|
routeFnAvailable: /routeFn\s+available=yes/.test(statusText),
|
|
232
254
|
proofPath: statusText.match(/proofPath=([^\s]+)/)?.[1] ?? null,
|
|
255
|
+
proofError: statusText.match(/proofError=([^\s]+)/)?.[1] ?? null,
|
|
233
256
|
};
|
|
234
257
|
}
|
|
235
258
|
|
|
@@ -238,69 +261,106 @@ function hasPackagedHookSource(pluginInspectText) {
|
|
|
238
261
|
}
|
|
239
262
|
|
|
240
263
|
function buildVerdict({ steps, gatewayStatus, pluginInspect, statusSignals, breadcrumbs, runtimeLoadProofSnapshot, openclawHome }) {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
verdict: "command_failed",
|
|
245
|
-
severity: "blocking",
|
|
246
|
-
why: `${failedStep.stepId} exited as ${failedStep.resultClass}`,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
264
|
+
const failedSteps = steps.filter((step) => step.resultClass !== "success" && step.skipped !== true);
|
|
265
|
+
const failedDetailedStatusStep = failedSteps.find((step) => step.stepId === "05-detailed-status");
|
|
249
266
|
const gatewayHealthy = /Runtime:\s+running/m.test(gatewayStatus) && /RPC probe:\s+ok/m.test(gatewayStatus);
|
|
250
267
|
const pluginLoaded = /Status:\s+loaded/m.test(pluginInspect);
|
|
251
268
|
const packagedHookPath = hasPackagedHookSource(pluginInspect);
|
|
252
269
|
const breadcrumbLoaded = breadcrumbs.afterBundleStart.some((entry) => entry.kind === "loaded");
|
|
253
270
|
const runtimeProofMatched = Array.isArray(runtimeLoadProofSnapshot?.value?.profiles)
|
|
254
271
|
&& runtimeLoadProofSnapshot.value.profiles.some((profile) => canonicalizeExistingProofPath(profile?.openclawHome ?? "") === canonicalizeExistingProofPath(openclawHome));
|
|
255
|
-
const
|
|
256
|
-
if (!gatewayHealthy)
|
|
257
|
-
missingProofs.push("gateway_health");
|
|
258
|
-
if (!pluginLoaded)
|
|
259
|
-
missingProofs.push("plugin_loaded");
|
|
260
|
-
if (!packagedHookPath)
|
|
261
|
-
missingProofs.push("packaged_hook_path");
|
|
272
|
+
const runtimeTruthGaps = [];
|
|
262
273
|
if (!statusSignals.statusOk)
|
|
263
|
-
|
|
274
|
+
runtimeTruthGaps.push("status_ok");
|
|
264
275
|
if (!statusSignals.loadProofReady)
|
|
265
|
-
|
|
276
|
+
runtimeTruthGaps.push("load_proof");
|
|
266
277
|
if (!statusSignals.runtimeProven)
|
|
267
|
-
|
|
278
|
+
runtimeTruthGaps.push("runtime_proven");
|
|
268
279
|
if (!statusSignals.serveActivePack)
|
|
269
|
-
|
|
280
|
+
runtimeTruthGaps.push("serve_active_pack");
|
|
270
281
|
if (!statusSignals.routeFnAvailable)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!
|
|
275
|
-
|
|
276
|
-
|
|
282
|
+
runtimeTruthGaps.push("route_fn");
|
|
283
|
+
const warningCodes = [];
|
|
284
|
+
const warnings = [];
|
|
285
|
+
if (!gatewayHealthy) {
|
|
286
|
+
warningCodes.push("gateway_health");
|
|
287
|
+
warnings.push("gateway status did not confirm runtime running and RPC probe ok");
|
|
288
|
+
}
|
|
289
|
+
if (!pluginLoaded) {
|
|
290
|
+
warningCodes.push("plugin_loaded");
|
|
291
|
+
warnings.push("plugin inspect did not report Status: loaded");
|
|
292
|
+
}
|
|
293
|
+
if (!packagedHookPath) {
|
|
294
|
+
warningCodes.push("packaged_hook_path");
|
|
295
|
+
warnings.push("plugin inspect did not confirm the packaged hook source");
|
|
296
|
+
}
|
|
297
|
+
if (!breadcrumbLoaded) {
|
|
298
|
+
warningCodes.push("startup_breadcrumb");
|
|
299
|
+
warnings.push("startup log did not contain a post-bundle [openclawbrain] BRAIN LOADED breadcrumb");
|
|
300
|
+
}
|
|
301
|
+
if (!runtimeProofMatched) {
|
|
302
|
+
warningCodes.push("runtime_load_proof_record");
|
|
303
|
+
warnings.push(runtimeLoadProofSnapshot.error !== null
|
|
304
|
+
? `runtime-load-proof snapshot was unreadable: ${runtimeLoadProofSnapshot.error}`
|
|
305
|
+
: runtimeLoadProofSnapshot.exists
|
|
306
|
+
? "runtime-load-proof snapshot did not include the current openclaw home"
|
|
307
|
+
: "runtime-load-proof snapshot was missing");
|
|
308
|
+
}
|
|
309
|
+
if (statusSignals.proofError !== null && statusSignals.proofError !== "none") {
|
|
310
|
+
warningCodes.push(`proof_error:${statusSignals.proofError}`);
|
|
311
|
+
warnings.push(`detailed status reported proofError=${statusSignals.proofError}`);
|
|
312
|
+
}
|
|
313
|
+
for (const step of failedSteps) {
|
|
314
|
+
warningCodes.push(`step:${step.stepId}:${step.resultClass}:${step.captureState}`);
|
|
315
|
+
warnings.push(describeStepWarning(step));
|
|
316
|
+
}
|
|
317
|
+
const uniqueWarningCodes = [...new Set(warningCodes)];
|
|
318
|
+
const uniqueWarnings = [...new Set(warnings)];
|
|
319
|
+
if (runtimeTruthGaps.length === 0 && uniqueWarningCodes.length === 0) {
|
|
277
320
|
return {
|
|
278
321
|
verdict: "success_and_proven",
|
|
279
322
|
severity: "none",
|
|
280
323
|
why: "install, restart, gateway health, plugin load, startup breadcrumb, runtime-load-proof record, and detailed status all aligned",
|
|
324
|
+
missingProofs: [],
|
|
325
|
+
warnings: [],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (runtimeTruthGaps.length === 0) {
|
|
329
|
+
return {
|
|
330
|
+
verdict: "success_but_proof_incomplete",
|
|
331
|
+
severity: "degraded",
|
|
332
|
+
why: `status/runtime evidence stayed healthy, but proof warnings remained: ${uniqueWarningCodes.join(", ")}`,
|
|
333
|
+
missingProofs: uniqueWarningCodes,
|
|
334
|
+
warnings: uniqueWarnings,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const hasUsableStatusTruth = statusSignals.statusOk
|
|
338
|
+
|| statusSignals.loadProofReady
|
|
339
|
+
|| statusSignals.runtimeProven
|
|
340
|
+
|| statusSignals.serveActivePack
|
|
341
|
+
|| statusSignals.routeFnAvailable;
|
|
342
|
+
if (failedDetailedStatusStep && !hasUsableStatusTruth) {
|
|
343
|
+
return {
|
|
344
|
+
verdict: "command_failed",
|
|
345
|
+
severity: "blocking",
|
|
346
|
+
why: `${failedDetailedStatusStep.stepId} ended as ${failedDetailedStatusStep.resultClass} before runtime truth could be established`,
|
|
347
|
+
missingProofs: runtimeTruthGaps,
|
|
348
|
+
warnings: uniqueWarnings,
|
|
281
349
|
};
|
|
282
350
|
}
|
|
283
|
-
const blocking = missingProofs.some((item) => [
|
|
284
|
-
"gateway_health",
|
|
285
|
-
"plugin_loaded",
|
|
286
|
-
"packaged_hook_path",
|
|
287
|
-
"status_ok",
|
|
288
|
-
"load_proof",
|
|
289
|
-
"runtime_proven",
|
|
290
|
-
"serve_active_pack",
|
|
291
|
-
"route_fn",
|
|
292
|
-
].includes(item));
|
|
293
351
|
return {
|
|
294
|
-
verdict:
|
|
295
|
-
severity:
|
|
296
|
-
why: `missing or conflicting
|
|
297
|
-
missingProofs,
|
|
352
|
+
verdict: "degraded_or_failed_proof",
|
|
353
|
+
severity: "blocking",
|
|
354
|
+
why: `missing or conflicting runtime truths: ${runtimeTruthGaps.join(", ")}`,
|
|
355
|
+
missingProofs: [...new Set([...runtimeTruthGaps, ...uniqueWarningCodes])],
|
|
356
|
+
warnings: uniqueWarnings,
|
|
298
357
|
};
|
|
299
358
|
}
|
|
300
359
|
|
|
301
360
|
function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspectText, statusSignals, breadcrumbs, runtimeLoadProofSnapshot }) {
|
|
302
361
|
const passed = [];
|
|
303
362
|
const missing = [];
|
|
363
|
+
const warnings = Array.isArray(verdict.warnings) ? verdict.warnings : [];
|
|
304
364
|
if (steps.find((step) => step.stepId === "01-install")?.resultClass === "success") {
|
|
305
365
|
passed.push("install command succeeded");
|
|
306
366
|
}
|
|
@@ -350,6 +410,9 @@ function buildSummary({ options, steps, verdict, gatewayStatusText, pluginInspec
|
|
|
350
410
|
"## Missing / incomplete",
|
|
351
411
|
...(missing.length === 0 ? ["- none"] : missing.map((item) => `- ${item}`)),
|
|
352
412
|
"",
|
|
413
|
+
"## Warnings",
|
|
414
|
+
...(warnings.length === 0 ? ["- none"] : warnings.map((item) => `- ${item}`)),
|
|
415
|
+
"",
|
|
353
416
|
"## Step ledger",
|
|
354
417
|
...steps.map((step) => `- ${step.stepId}: ${step.skipped ? "skipped" : `${step.resultClass} (${step.captureState})`} - ${step.summary}`),
|
|
355
418
|
];
|
|
@@ -570,11 +633,12 @@ export function captureOperatorProofBundle(options) {
|
|
|
570
633
|
const statusCapture = addStep("05-detailed-status", "detailed status", cliInvocation.command, [...cliInvocation.args, "status", "--openclaw-home", options.openclawHome, "--detailed"]);
|
|
571
634
|
const gatewayLogPath = extractGatewayLogPath(gatewayStatusCapture.stdout);
|
|
572
635
|
const activationRoot = extractActivationRoot(statusCapture.stdout, options.activationRoot ?? null);
|
|
573
|
-
const
|
|
636
|
+
const statusSignals = extractStatusSignals(statusCapture.stdout);
|
|
637
|
+
const runtimeLoadProofPath = normalizeReportedProofPath(statusSignals.proofPath)
|
|
638
|
+
?? path.join(activationRoot, "attachment-truth", "runtime-load-proofs.json");
|
|
574
639
|
const runtimeLoadProofSnapshot = readJsonSnapshot(runtimeLoadProofPath);
|
|
575
640
|
const gatewayLogText = readTextIfExists(gatewayLogPath);
|
|
576
641
|
const breadcrumbs = extractStartupBreadcrumbs(gatewayLogText, bundleStartedAt);
|
|
577
|
-
const statusSignals = extractStatusSignals(statusCapture.stdout);
|
|
578
642
|
writeText(path.join(bundleDir, "extracted-startup-breadcrumbs.log"), breadcrumbs.all.length === 0
|
|
579
643
|
? "<no matching breadcrumbs found>\n"
|
|
580
644
|
: `${breadcrumbs.all.map((entry) => entry.line).join("\n")}\n`);
|