@openclawbrain/openclaw 0.1.0 → 0.1.2

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/index.js CHANGED
@@ -1,20 +1,569 @@
1
1
  import { createHash } from "node:crypto";
2
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
- import { compileRuntime } from "@openclawbrain/compiler";
6
- import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, validateNormalizedEventExport } from "@openclawbrain/contracts";
7
- import { inspectActivationState } from "@openclawbrain/pack-format";
5
+ import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
6
+ import { CONTRACT_IDS, buildNormalizedEventExport, canonicalJson, checksumJsonPayload, createFeedbackEvent, createInteractionEvent, sortNormalizedEvents, validateKernelSurface, validateNormalizedEventExport } from "@openclawbrain/contracts";
7
+ import { describeNormalizedEventExportObservability } from "@openclawbrain/event-export";
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";
8
10
  const DEFAULT_AGENT_ID = "openclaw-runtime";
9
11
  const FEEDBACK_KINDS = new Set(["correction", "teaching", "approval", "suppression"]);
12
+ export const DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY = 8;
13
+ const RECORDED_SESSION_TRACE_CONTRACT = "recorded_session_trace.v1";
14
+ const RECORDED_SESSION_FIXTURE_CONTRACT = "recorded_session_replay_fixture.v1";
15
+ const RECORDED_SESSION_BUNDLE_CONTRACT = "recorded_session_replay_bundle.v1";
10
16
  const RUNTIME_EVENT_EXPORT_BUNDLE_CONTRACT = "normalized_event_export_bundle.v1";
17
+ const DEFAULT_ATTACH_STATUS_MESSAGE = "openclaw attach status probe";
18
+ const DEFAULT_ATTACH_STATUS_RUNTIME_HINTS = ["attach", "status", "probe"];
11
19
  export const RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT = {
12
20
  manifest: "manifest.json",
13
21
  payload: "normalized-event-export.json"
14
22
  };
23
+ const OPENCLAW_LANDING_BOUNDARIES_V1 = {
24
+ compileBoundary: {
25
+ contract: CONTRACT_IDS.runtimeCompile,
26
+ activationSlot: "active",
27
+ entrypoint: "compileRuntimeContext",
28
+ servedFromCandidateBeforePromotion: false,
29
+ learnedRouteEvidenceRequiredWhenManifestRequiresIt: true
30
+ },
31
+ eventExportBoundary: {
32
+ emittedContracts: [CONTRACT_IDS.interactionEvents, CONTRACT_IDS.feedbackEvents],
33
+ entrypoint: "runRuntimeTurn",
34
+ bundleWriteOptional: true,
35
+ writeFailuresEraseSuccessfulCompile: false,
36
+ learningHandoffStaysOffHotPath: true
37
+ },
38
+ activePackBoundary: {
39
+ servingSlot: "active",
40
+ inspectableSlots: ["active", "candidate", "previous"],
41
+ candidateServedBeforePromotion: false,
42
+ previousSlotUsedForRollback: true
43
+ },
44
+ promotionBoundary: {
45
+ candidateSlot: "candidate",
46
+ activeSlot: "active",
47
+ previousSlot: "previous",
48
+ requiresActivationReadyCandidate: true,
49
+ compileSeesCandidateOnlyAfterPromotion: true,
50
+ promotionHappensOffHotPath: true
51
+ },
52
+ failOpenSemantics: {
53
+ missingActivePackFallsBackToStaticContext: true,
54
+ learnedRequiredRouteArtifactDriftHardFails: true,
55
+ hardFailuresDisableStaticFallback: true,
56
+ eventExportWriteFailurePreservesCompile: true
57
+ },
58
+ runtimeResponsibilities: [
59
+ "runtime orchestration and session flow",
60
+ "prompt assembly and response delivery",
61
+ "guarded serve-path fail-open decisions"
62
+ ],
63
+ brainResponsibilities: [
64
+ "normalized event emission and export handoff",
65
+ "candidate pack materialization and learned route refresh",
66
+ "activation staging promotion and rollback",
67
+ "promoted-pack compilation diagnostics"
68
+ ]
69
+ };
15
70
  function toErrorMessage(error) {
16
71
  return error instanceof Error ? error.message : String(error);
17
72
  }
73
+ function buildAsyncTeacherLoopNotes(input) {
74
+ return [
75
+ `teacher_queue_depth=${input.queueDepth}`,
76
+ `teacher_freshness=${input.latestFreshness}`,
77
+ `teacher_artifacts_total=${input.artifactCount}`,
78
+ `teacher_artifacts_emitted=${input.emittedArtifactCount}`,
79
+ `teacher_artifacts_deduped=${input.dedupedArtifactCount}`,
80
+ `teacher_budget=${input.sparseFeedback.teacherBudget}`,
81
+ `teacher_delay_ms=${input.sparseFeedback.teacherDelayMs}`,
82
+ `teacher_feedback_mask=correction:${input.sparseFeedback.feedbackMask.correction},teaching:${input.sparseFeedback.feedbackMask.teaching},approval:${input.sparseFeedback.feedbackMask.approval},suppression:${input.sparseFeedback.feedbackMask.suppression}`,
83
+ `teacher_feedback_eligible=${input.sparseFeedback.eligibleFeedbackCount}`,
84
+ `teacher_feedback_masked=${input.sparseFeedback.maskedFeedbackCount}`,
85
+ `teacher_feedback_delayed=${input.sparseFeedback.delayedFeedbackCount}`,
86
+ `teacher_feedback_budgeted_out=${input.sparseFeedback.budgetedOutFeedbackCount}`,
87
+ `teacher_background_amplified=${input.sparseFeedback.amplifiedBackgroundLabelCount}`,
88
+ `teacher_noop=${input.noOpReason}`,
89
+ input.materialization === null ? "teacher_materialization=noop" : `teacher_materialized_pack=${input.materialization.candidate.summary.packId}`
90
+ ];
91
+ }
92
+ function cloneAlwaysOnLearningMaterializationJobOrNull(value) {
93
+ return value === null ? null : structuredClone(value);
94
+ }
95
+ function cloneTeacherSupervisionArtifacts(value) {
96
+ return [...structuredClone(value)];
97
+ }
98
+ function cloneCanonicalSupervision(value) {
99
+ return structuredClone(value);
100
+ }
101
+ function cloneContinuousProductLoopPackVersion(value) {
102
+ return structuredClone(value);
103
+ }
104
+ function cloneContinuousProductLoopState(value) {
105
+ return structuredClone(value);
106
+ }
107
+ function buildNormalizedEventDedupId(event) {
108
+ return checksumJsonPayload({
109
+ contract: event.contract,
110
+ eventId: event.eventId,
111
+ agentId: event.agentId,
112
+ sessionId: event.sessionId,
113
+ channel: event.channel,
114
+ sequence: event.sequence,
115
+ kind: event.kind,
116
+ createdAt: event.createdAt,
117
+ source: event.source,
118
+ packId: "packId" in event ? event.packId ?? null : null,
119
+ content: "content" in event ? event.content : null,
120
+ messageId: event.messageId ?? null,
121
+ relatedInteractionId: "relatedInteractionId" in event ? event.relatedInteractionId ?? null : null
122
+ });
123
+ }
124
+ function mergeRuntimeEventHistory(current, incoming) {
125
+ const merged = sortNormalizedEvents([
126
+ ...current.interactionEvents,
127
+ ...current.feedbackEvents,
128
+ ...incoming.interactionEvents,
129
+ ...incoming.feedbackEvents
130
+ ]);
131
+ const deduped = [];
132
+ const seen = new Set();
133
+ for (const event of merged) {
134
+ const dedupId = buildNormalizedEventDedupId(event);
135
+ if (seen.has(dedupId)) {
136
+ continue;
137
+ }
138
+ seen.add(dedupId);
139
+ deduped.push(event);
140
+ }
141
+ return {
142
+ interactionEvents: deduped.filter((event) => event.contract === CONTRACT_IDS.interactionEvents),
143
+ feedbackEvents: deduped.filter((event) => event.contract === CONTRACT_IDS.feedbackEvents)
144
+ };
145
+ }
146
+ function buildContinuousTurnExport(turn, loopRoot) {
147
+ const exportSeed = checksumJsonPayload({
148
+ sessionId: turn.sessionId,
149
+ channel: turn.channel,
150
+ sourceStream: turn.sourceStream ?? null,
151
+ userMessage: turn.userMessage,
152
+ createdAt: turn.createdAt ?? null,
153
+ sequenceStart: turn.sequenceStart ?? null,
154
+ compileCreatedAt: turn.compile?.createdAt ?? null,
155
+ delivery: turn.delivery === false
156
+ ? false
157
+ : turn.delivery === undefined || turn.delivery === null
158
+ ? null
159
+ : turn.delivery === true
160
+ ? true
161
+ : {
162
+ createdAt: turn.delivery.createdAt ?? null,
163
+ messageId: turn.delivery.messageId ?? null,
164
+ sequence: turn.delivery.sequence ?? null
165
+ },
166
+ feedback: (turn.feedback ?? [])
167
+ .filter((item) => item !== null)
168
+ .map((item) => ({
169
+ content: item.content,
170
+ createdAt: item.createdAt ?? null,
171
+ sequence: item.sequence ?? null,
172
+ kind: item.kind ?? null,
173
+ messageId: item.messageId ?? null,
174
+ relatedInteractionId: item.relatedInteractionId ?? null
175
+ }))
176
+ })
177
+ .replace(/^sha256-/u, "")
178
+ .slice(0, 12);
179
+ const exportName = `${turn.sessionId}-${exportSeed}`;
180
+ return {
181
+ rootDir: path.join(loopRoot, "event-exports", exportName),
182
+ exportName
183
+ };
184
+ }
185
+ function withContinuousTurnExport(turn, loopRoot) {
186
+ if (turn.export !== undefined && turn.export !== null) {
187
+ return {
188
+ ...turn,
189
+ export: {
190
+ ...turn.export
191
+ }
192
+ };
193
+ }
194
+ return {
195
+ ...turn,
196
+ export: buildContinuousTurnExport(turn, loopRoot)
197
+ };
198
+ }
199
+ function buildPackVersion(version, target) {
200
+ return {
201
+ version,
202
+ packId: target.packId,
203
+ routePolicy: target.routePolicy,
204
+ routerIdentity: target.routerIdentity,
205
+ workspaceSnapshot: target.workspaceSnapshot,
206
+ workspaceRevision: target.workspaceRevision,
207
+ eventRange: {
208
+ start: target.eventRange.start,
209
+ end: target.eventRange.end,
210
+ count: target.eventRange.count
211
+ },
212
+ eventExportDigest: target.eventExportDigest,
213
+ builtAt: target.builtAt
214
+ };
215
+ }
216
+ function buildLearningCandidateTarget(candidate) {
217
+ return {
218
+ packId: candidate.summary.packId,
219
+ routePolicy: candidate.summary.routePolicy,
220
+ routerIdentity: candidate.payloads.router?.routerIdentity ?? null,
221
+ workspaceSnapshot: candidate.summary.workspaceSnapshot,
222
+ workspaceRevision: candidate.manifest.provenance.workspace.revision,
223
+ eventRange: {
224
+ start: candidate.summary.eventRange.start,
225
+ end: candidate.summary.eventRange.end,
226
+ count: candidate.summary.eventRange.count
227
+ },
228
+ eventExportDigest: candidate.summary.eventExportDigest,
229
+ builtAt: candidate.manifest.provenance.builtAt
230
+ };
231
+ }
232
+ function registerPackVersion(state, target) {
233
+ const existing = state.packLineage.find((entry) => entry.packId === target.packId);
234
+ if (existing !== undefined) {
235
+ return cloneContinuousProductLoopPackVersion(existing);
236
+ }
237
+ const created = buildPackVersion(state.nextPackVersion, target);
238
+ state.packLineage.push(cloneContinuousProductLoopPackVersion(created));
239
+ state.nextPackVersion += 1;
240
+ return created;
241
+ }
242
+ function tryReadActivePackTarget(rootDir) {
243
+ try {
244
+ return describeActivationTarget(rootDir, "active", { requireActivationReady: true });
245
+ }
246
+ catch {
247
+ return null;
248
+ }
249
+ }
250
+ function syncContinuousActivePack(state) {
251
+ const activeTarget = tryReadActivePackTarget(state.activationRoot);
252
+ if (activeTarget === null) {
253
+ state.currentActivePack = null;
254
+ state.activePackVersion = 0;
255
+ return null;
256
+ }
257
+ const activePack = registerPackVersion(state, activeTarget);
258
+ state.currentActivePack = cloneContinuousProductLoopPackVersion(activePack);
259
+ state.activePackVersion = activePack.version;
260
+ return activePack;
261
+ }
262
+ function buildContinuousPackRoot(loopRoot, packVersion) {
263
+ return path.join(loopRoot, "packs", `v${String(packVersion.version).padStart(4, "0")}-${packVersion.packId}`);
264
+ }
265
+ export function buildCanonicalSupervision(normalizedEventExport) {
266
+ const feedback = normalizedEventExport.feedbackEvents.map((event) => ({
267
+ eventId: event.eventId,
268
+ kind: event.kind,
269
+ sequence: event.sequence,
270
+ createdAt: event.createdAt,
271
+ content: event.content,
272
+ relatedInteractionId: event.relatedInteractionId ?? null
273
+ }));
274
+ const compilePackIds = [
275
+ ...new Set(normalizedEventExport.interactionEvents.flatMap((event) => event.kind === "memory_compiled" && event.packId ? [event.packId] : []))
276
+ ];
277
+ const relatedInteractionIds = [...new Set(feedback.flatMap((event) => (event.relatedInteractionId ? [event.relatedInteractionId] : [])))];
278
+ const feedbackCounts = {
279
+ corrections: feedback.filter((event) => event.kind === "correction").length,
280
+ teachings: feedback.filter((event) => event.kind === "teaching").length,
281
+ approvals: feedback.filter((event) => event.kind === "approval").length,
282
+ suppressions: feedback.filter((event) => event.kind === "suppression").length
283
+ };
284
+ const supervisionDigest = checksumJsonPayload({
285
+ exportDigest: normalizedEventExport.provenance.exportDigest,
286
+ eventRange: {
287
+ start: normalizedEventExport.range.start,
288
+ end: normalizedEventExport.range.end,
289
+ count: normalizedEventExport.range.count
290
+ },
291
+ sourceStreams: normalizedEventExport.provenance.sourceStreams,
292
+ humanLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.humanLabels,
293
+ selfLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.selfLabels,
294
+ feedback,
295
+ compilePackIds,
296
+ relatedInteractionIds
297
+ });
298
+ return {
299
+ runtimeOwner: "openclaw",
300
+ exportDigest: normalizedEventExport.provenance.exportDigest,
301
+ supervisionDigest,
302
+ sessionId: normalizedEventExport.provenance.sessionId,
303
+ channel: normalizedEventExport.provenance.channel,
304
+ eventRange: {
305
+ start: normalizedEventExport.range.start,
306
+ end: normalizedEventExport.range.end,
307
+ count: normalizedEventExport.range.count
308
+ },
309
+ sourceStreams: [...normalizedEventExport.provenance.sourceStreams],
310
+ humanLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.humanLabels,
311
+ selfLabelCount: normalizedEventExport.provenance.learningSurface.labelHarvest.selfLabels,
312
+ feedbackCounts,
313
+ compilePackIds,
314
+ relatedInteractionIds,
315
+ feedback
316
+ };
317
+ }
318
+ export function createContinuousProductLoopState(input) {
319
+ const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
320
+ const loopRoot = path.resolve(normalizeNonEmptyString(input.loopRoot, "loopRoot"));
321
+ const activeTarget = tryReadActivePackTarget(activationRoot);
322
+ const activePack = activeTarget === null ? null : buildPackVersion(1, activeTarget);
323
+ return {
324
+ runtimeOwner: "openclaw",
325
+ activationRoot,
326
+ loopRoot,
327
+ interactionEvents: [],
328
+ feedbackEvents: [],
329
+ learner: createAlwaysOnLearningRuntimeState(),
330
+ runtimePlasticity: null,
331
+ activePackVersion: activePack?.version ?? 0,
332
+ currentActivePack: activePack === null ? null : cloneContinuousProductLoopPackVersion(activePack),
333
+ candidatePack: null,
334
+ packLineage: activePack === null ? [] : [cloneContinuousProductLoopPackVersion(activePack)],
335
+ nextPackVersion: activePack === null ? 1 : 2,
336
+ promotionCount: 0,
337
+ lastSupervision: null
338
+ };
339
+ }
340
+ function mergeUniqueEvents(current, additions) {
341
+ const merged = new Map();
342
+ for (const event of [...current, ...additions]) {
343
+ merged.set(buildNormalizedEventDedupId(event), structuredClone(event));
344
+ }
345
+ return [...merged.values()].sort((left, right) => left.sequence - right.sequence || left.createdAt.localeCompare(right.createdAt));
346
+ }
347
+ function mergeTeacherArtifacts(current, additions) {
348
+ const merged = new Map();
349
+ for (const artifact of [...current, ...additions]) {
350
+ const existing = merged.get(artifact.dedupId);
351
+ if (existing === undefined ||
352
+ Date.parse(artifact.freshness.observedAt) > Date.parse(existing.freshness.observedAt) ||
353
+ (artifact.freshness.observedAt === existing.freshness.observedAt && artifact.artifactId.localeCompare(existing.artifactId) < 0)) {
354
+ merged.set(artifact.dedupId, structuredClone(artifact));
355
+ }
356
+ }
357
+ return [...merged.values()].sort((left, right) => {
358
+ if (left.freshness.status !== right.freshness.status) {
359
+ return left.freshness.status === "fresh" ? -1 : 1;
360
+ }
361
+ if (left.createdAt !== right.createdAt) {
362
+ return Date.parse(right.createdAt) - Date.parse(left.createdAt);
363
+ }
364
+ return left.artifactId.localeCompare(right.artifactId);
365
+ });
366
+ }
367
+ function latestTeacherFreshness(artifacts) {
368
+ return artifacts[0]?.freshness.status ?? "none";
369
+ }
370
+ export class AsyncTeacherLiveLoop {
371
+ input;
372
+ queueCapacity;
373
+ staleAfterMs;
374
+ queuedExportDigests = new Set();
375
+ seenExportDigests = new Set();
376
+ queue = [];
377
+ drainPromise = null;
378
+ interactionEvents = [];
379
+ feedbackEvents = [];
380
+ teacherArtifacts = [];
381
+ learnerState = createAlwaysOnLearningRuntimeState();
382
+ lastMaterialization = null;
383
+ diagnostics = {
384
+ acceptedExportCount: 0,
385
+ processedExportCount: 0,
386
+ duplicateExportCount: 0,
387
+ droppedExportCount: 0,
388
+ emittedArtifactCount: 0,
389
+ dedupedArtifactCount: 0,
390
+ lastProcessedAt: null,
391
+ latestFreshness: "none",
392
+ lastNoOpReason: "none",
393
+ notes: buildAsyncTeacherLoopNotes({
394
+ queueDepth: 0,
395
+ latestFreshness: "none",
396
+ artifactCount: 0,
397
+ emittedArtifactCount: 0,
398
+ dedupedArtifactCount: 0,
399
+ sparseFeedback: this.learnerState.sparseFeedback,
400
+ noOpReason: "none",
401
+ materialization: null
402
+ })
403
+ };
404
+ constructor(input) {
405
+ this.input = input;
406
+ this.queueCapacity = input.maxQueuedExports ?? DEFAULT_ASYNC_TEACHER_QUEUE_CAPACITY;
407
+ this.staleAfterMs = input.staleAfterMs ?? DEFAULT_TEACHER_SUPERVISION_STALE_AFTER_MS;
408
+ if (!Number.isInteger(this.queueCapacity) || this.queueCapacity <= 0) {
409
+ throw new Error("maxQueuedExports must be a positive integer");
410
+ }
411
+ if (!Number.isInteger(this.staleAfterMs) || this.staleAfterMs <= 0) {
412
+ throw new Error("staleAfterMs must be a positive integer");
413
+ }
414
+ }
415
+ enqueueNormalizedEventExport(normalizedEventExport, options = {}) {
416
+ const validationErrors = validateNormalizedEventExport(normalizedEventExport);
417
+ if (validationErrors.length > 0) {
418
+ throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
419
+ }
420
+ const exportDigest = normalizedEventExport.provenance.exportDigest;
421
+ if (this.seenExportDigests.has(exportDigest) || this.queuedExportDigests.has(exportDigest)) {
422
+ this.diagnostics.duplicateExportCount += 1;
423
+ this.diagnostics.lastNoOpReason = "duplicate_export";
424
+ this.refreshNotes();
425
+ return {
426
+ accepted: false,
427
+ exportDigest,
428
+ queueDepth: this.queue.length,
429
+ notes: [...this.diagnostics.notes],
430
+ reason: "duplicate_export"
431
+ };
432
+ }
433
+ if (this.queue.length >= this.queueCapacity) {
434
+ this.diagnostics.droppedExportCount += 1;
435
+ this.diagnostics.lastNoOpReason = "queue_full";
436
+ this.refreshNotes();
437
+ return {
438
+ accepted: false,
439
+ exportDigest,
440
+ queueDepth: this.queue.length,
441
+ notes: [...this.diagnostics.notes],
442
+ reason: "queue_full"
443
+ };
444
+ }
445
+ const observedAt = options.observedAt ?? normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString();
446
+ this.queue.push({
447
+ jobId: `teacher-loop-${createHash("sha256").update(`${exportDigest}:${observedAt}`).digest("hex")}`,
448
+ exportDigest,
449
+ observedAt,
450
+ normalizedEventExport: structuredClone(normalizedEventExport)
451
+ });
452
+ this.queuedExportDigests.add(exportDigest);
453
+ this.diagnostics.acceptedExportCount += 1;
454
+ this.refreshNotes();
455
+ void this.ensureDrain();
456
+ return {
457
+ accepted: true,
458
+ exportDigest,
459
+ queueDepth: this.queue.length,
460
+ notes: [...this.diagnostics.notes],
461
+ reason: null
462
+ };
463
+ }
464
+ async flush() {
465
+ await this.ensureDrain();
466
+ return this.snapshot();
467
+ }
468
+ snapshot() {
469
+ return {
470
+ runtimeOwner: "openclaw",
471
+ queue: {
472
+ capacity: this.queueCapacity,
473
+ depth: this.queue.length,
474
+ running: this.drainPromise !== null
475
+ },
476
+ teacher: {
477
+ artifactCount: this.teacherArtifacts.length,
478
+ artifacts: cloneTeacherSupervisionArtifacts(this.teacherArtifacts),
479
+ latestFreshness: this.diagnostics.latestFreshness
480
+ },
481
+ learner: {
482
+ state: structuredClone(this.learnerState),
483
+ lastMaterialization: cloneAlwaysOnLearningMaterializationJobOrNull(this.lastMaterialization)
484
+ },
485
+ diagnostics: {
486
+ ...this.diagnostics,
487
+ notes: [...this.diagnostics.notes]
488
+ }
489
+ };
490
+ }
491
+ async ensureDrain() {
492
+ if (this.drainPromise === null) {
493
+ this.drainPromise = this.drain().finally(() => {
494
+ this.drainPromise = null;
495
+ });
496
+ }
497
+ await this.drainPromise;
498
+ if (this.queue.length > 0) {
499
+ await this.ensureDrain();
500
+ }
501
+ }
502
+ async drain() {
503
+ while (this.queue.length > 0) {
504
+ const job = this.queue.shift();
505
+ this.queuedExportDigests.delete(job.exportDigest);
506
+ this.seenExportDigests.add(job.exportDigest);
507
+ this.interactionEvents = mergeUniqueEvents(this.interactionEvents, job.normalizedEventExport.interactionEvents);
508
+ this.feedbackEvents = mergeUniqueEvents(this.feedbackEvents, job.normalizedEventExport.feedbackEvents);
509
+ const mergedNormalizedEventExport = buildNormalizedEventExport({
510
+ interactionEvents: this.interactionEvents,
511
+ feedbackEvents: this.feedbackEvents
512
+ });
513
+ const builtArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
514
+ normalizedEventExport: mergedNormalizedEventExport,
515
+ observedAt: job.observedAt,
516
+ staleAfterMs: this.staleAfterMs,
517
+ ...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {})
518
+ });
519
+ const currentDedupIds = new Set(this.teacherArtifacts.map((artifact) => artifact.dedupId));
520
+ const nextTeacherArtifacts = mergeTeacherArtifacts(this.teacherArtifacts, builtArtifacts);
521
+ const emittedArtifactCount = builtArtifacts.filter((artifact) => !currentDedupIds.has(artifact.dedupId)).length;
522
+ const dedupedArtifactCount = builtArtifacts.length - emittedArtifactCount;
523
+ this.teacherArtifacts = nextTeacherArtifacts;
524
+ const learnerResult = advanceAlwaysOnLearningRuntime({
525
+ packLabel: this.input.packLabel,
526
+ workspace: this.input.workspace,
527
+ interactionEvents: this.interactionEvents,
528
+ feedbackEvents: this.feedbackEvents,
529
+ teacherSupervisionArtifacts: this.teacherArtifacts,
530
+ learnedRouting: this.input.learnedRouting,
531
+ state: this.learnerState,
532
+ builtAt: this.input.builtAt ?? job.observedAt,
533
+ ...(this.input.offlineArtifacts !== undefined ? { offlineArtifacts: this.input.offlineArtifacts } : {}),
534
+ ...(this.input.structuralOps !== undefined ? { structuralOps: this.input.structuralOps } : {}),
535
+ ...(this.input.sparseFeedback !== undefined ? { sparseFeedback: this.input.sparseFeedback } : {}),
536
+ ...(this.input.liveSliceSize !== undefined ? { liveSliceSize: this.input.liveSliceSize } : {}),
537
+ ...(this.input.backfillSliceSize !== undefined ? { backfillSliceSize: this.input.backfillSliceSize } : {}),
538
+ ...(this.input.cadence !== undefined ? { cadence: this.input.cadence } : {})
539
+ });
540
+ this.learnerState = structuredClone(learnerResult.state);
541
+ this.lastMaterialization = cloneAlwaysOnLearningMaterializationJobOrNull(learnerResult.materialization);
542
+ this.diagnostics.processedExportCount += 1;
543
+ this.diagnostics.emittedArtifactCount += emittedArtifactCount;
544
+ this.diagnostics.dedupedArtifactCount += dedupedArtifactCount;
545
+ this.diagnostics.lastProcessedAt = job.observedAt;
546
+ this.diagnostics.latestFreshness = latestTeacherFreshness(this.teacherArtifacts);
547
+ this.diagnostics.lastNoOpReason = emittedArtifactCount === 0 ? "no_teacher_artifacts" : "none";
548
+ this.refreshNotes();
549
+ }
550
+ }
551
+ refreshNotes() {
552
+ this.diagnostics.notes = buildAsyncTeacherLoopNotes({
553
+ queueDepth: this.queue.length,
554
+ latestFreshness: this.diagnostics.latestFreshness,
555
+ artifactCount: this.teacherArtifacts.length,
556
+ emittedArtifactCount: this.diagnostics.emittedArtifactCount,
557
+ dedupedArtifactCount: this.diagnostics.dedupedArtifactCount,
558
+ sparseFeedback: this.learnerState.sparseFeedback,
559
+ noOpReason: this.diagnostics.lastNoOpReason,
560
+ materialization: this.lastMaterialization
561
+ });
562
+ }
563
+ }
564
+ export function createAsyncTeacherLiveLoop(input) {
565
+ return new AsyncTeacherLiveLoop(input);
566
+ }
18
567
  function readJsonFile(filePath) {
19
568
  return JSON.parse(readFileSync(filePath, "utf8"));
20
569
  }
@@ -209,15 +758,38 @@ export function formatPromptContext(compileResponse) {
209
758
  lines.push("[/BRAIN_CONTEXT]");
210
759
  return `${lines.join("\n")}\n`;
211
760
  }
212
- function fallbackCompileResult(error, activationRoot) {
761
+ function failOpenCompileResult(error, activationRoot) {
213
762
  return {
214
763
  ok: false,
215
764
  fallbackToStaticContext: true,
765
+ hardRequirementViolated: false,
216
766
  activationRoot: path.resolve(activationRoot),
217
767
  error: toErrorMessage(error),
218
768
  brainContext: ""
219
769
  };
220
770
  }
771
+ function classifyCompileFailure(error, activationRoot) {
772
+ const resolvedActivationRoot = path.resolve(activationRoot);
773
+ try {
774
+ const inspection = inspectActivationState(resolvedActivationRoot);
775
+ const active = inspection.active;
776
+ if (active !== null && active.routePolicy === "requires_learned_routing") {
777
+ const failureReason = active.findings.length > 0 ? active.findings.join("; ") : toErrorMessage(error);
778
+ return {
779
+ ok: false,
780
+ fallbackToStaticContext: false,
781
+ hardRequirementViolated: true,
782
+ activationRoot: resolvedActivationRoot,
783
+ error: `Learned-routing hotpath hard requirement violated for active pack ${active.packId} (routerIdentity=${active.routerIdentity ?? "null"}): ${failureReason}`,
784
+ brainContext: ""
785
+ };
786
+ }
787
+ }
788
+ catch {
789
+ return failOpenCompileResult(error, resolvedActivationRoot);
790
+ }
791
+ return failOpenCompileResult(error, resolvedActivationRoot);
792
+ }
221
793
  export function resolveActivePackForCompile(activationRoot) {
222
794
  const resolvedActivationRoot = path.resolve(normalizeNonEmptyString(activationRoot, "activationRoot"));
223
795
  const inspection = inspectActivationState(resolvedActivationRoot);
@@ -240,29 +812,348 @@ export function compileRuntimeContext(input) {
240
812
  const runtimeHints = normalizeRuntimeHints(input.runtimeHints);
241
813
  try {
242
814
  const target = resolveActivePackForCompile(activationRoot);
243
- const compileResponse = compileRuntime(target.activePointer.packRootDir, {
815
+ const compile = compileRuntimeFromActivation(activationRoot, {
244
816
  contract: CONTRACT_IDS.runtimeCompile,
245
817
  agentId,
246
818
  userMessage: normalizeNonEmptyString(input.message, "message"),
247
819
  maxContextBlocks: normalizeNonNegativeInteger(input.maxContextBlocks, "maxContextBlocks", 4),
820
+ ...(input.maxContextChars !== undefined
821
+ ? { maxContextChars: normalizeNonNegativeInteger(input.maxContextChars, "maxContextChars", input.maxContextChars) }
822
+ : {}),
248
823
  modeRequested: normalizeMode(input.mode),
249
824
  activePackId: target.activePointer.packId,
825
+ ...(input.compactionMode !== undefined ? { compactionMode: input.compactionMode } : {}),
250
826
  ...(runtimeHints.length > 0 ? { runtimeHints } : {})
251
827
  });
828
+ const compileResponse = {
829
+ ...compile.response,
830
+ diagnostics: {
831
+ ...compile.response.diagnostics,
832
+ notes: [...compile.response.diagnostics.notes, "OpenClaw remains the runtime owner"]
833
+ }
834
+ };
252
835
  return {
253
836
  ok: true,
254
837
  fallbackToStaticContext: false,
838
+ hardRequirementViolated: false,
255
839
  activationRoot,
256
- activePackId: target.activePointer.packId,
840
+ activePackId: compile.target.packId,
257
841
  packRootDir: path.resolve(target.activePointer.packRootDir),
258
842
  compileResponse,
259
843
  brainContext: formatPromptContext(compileResponse)
260
844
  };
261
845
  }
262
846
  catch (error) {
263
- return fallbackCompileResult(error, activationRoot);
847
+ return classifyCompileFailure(error, activationRoot);
264
848
  }
265
849
  }
850
+ function readDiagnosticNoteValue(notes, prefix) {
851
+ const note = notes.find((entry) => entry.startsWith(prefix));
852
+ return note === undefined ? null : note.slice(prefix.length);
853
+ }
854
+ function readDiagnosticNoteList(notes, prefix) {
855
+ const value = readDiagnosticNoteValue(notes, prefix);
856
+ if (value === null || value.length === 0) {
857
+ return [];
858
+ }
859
+ return value
860
+ .split("|")
861
+ .map((entry) => entry.trim())
862
+ .filter((entry) => entry.length > 0);
863
+ }
864
+ function sortedUniqueStrings(values) {
865
+ return [...new Set(values.filter((value) => value.length > 0))].sort((left, right) => left.localeCompare(right));
866
+ }
867
+ function isStableKernelContextBlock(block) {
868
+ if (block.id.includes(":event:") || block.id.includes(":teacher:")) {
869
+ return false;
870
+ }
871
+ if (block.source.startsWith("split:") || block.source.startsWith("merge:")) {
872
+ return false;
873
+ }
874
+ return true;
875
+ }
876
+ function buildContextAttributionSummary(input) {
877
+ const selectionTiers = readDiagnosticNoteValue(input.notes ?? [], "selection_tiers=");
878
+ const selectedContext = [...(input.selectedContext ?? [])];
879
+ const stableKernelBlocks = selectedContext.filter((block) => isStableKernelContextBlock(block));
880
+ const brainCompiledBlocks = selectedContext.filter((block) => !isStableKernelContextBlock(block));
881
+ if (input.unprobed) {
882
+ return {
883
+ selectedContextCount: 0,
884
+ stableKernelBlockCount: 0,
885
+ brainCompiledBlockCount: 0,
886
+ stableKernelSources: [],
887
+ brainCompiledSources: [],
888
+ selectionTiers,
889
+ evidence: "unprobed",
890
+ detail: "compile probe was not run, so kernel-vs-brain attribution is unknown"
891
+ };
892
+ }
893
+ if (input.hardRequirementViolated) {
894
+ return {
895
+ selectedContextCount: 0,
896
+ stableKernelBlockCount: 0,
897
+ brainCompiledBlockCount: 0,
898
+ stableKernelSources: [],
899
+ brainCompiledSources: [],
900
+ selectionTiers,
901
+ evidence: "hard_fail",
902
+ detail: "learned-routing hard requirement failed before any compiled brain context could be selected"
903
+ };
904
+ }
905
+ if (input.fallbackToStaticContext) {
906
+ return {
907
+ selectedContextCount: 0,
908
+ stableKernelBlockCount: 0,
909
+ brainCompiledBlockCount: 0,
910
+ stableKernelSources: [],
911
+ brainCompiledSources: [],
912
+ selectionTiers,
913
+ evidence: "fail_open_static_context",
914
+ detail: "serve probe fell back to static context, so no compiled brain context was selected"
915
+ };
916
+ }
917
+ const evidence = brainCompiledBlocks.length > 0
918
+ ? input.usedLearnedRouteFn === true
919
+ ? "route_fn_and_brain_context"
920
+ : "brain_context_only"
921
+ : input.usedLearnedRouteFn === true
922
+ ? "route_fn_only"
923
+ : "stable_kernel_only";
924
+ const detailPrefix = `selected=${selectedContext.length}; tiers=${selectionTiers ?? "unknown"}; kernel=${stableKernelBlocks.length}; brain=${brainCompiledBlocks.length}`;
925
+ const detail = evidence === "route_fn_and_brain_context"
926
+ ? `${detailPrefix}; learned route ran and non-seed brain-compiled context was selected`
927
+ : evidence === "brain_context_only"
928
+ ? `${detailPrefix}; non-seed brain-compiled context was selected without learned-route evidence`
929
+ : evidence === "route_fn_only"
930
+ ? `${detailPrefix}; learned route ran but selected context stayed inside the stable kernel`
931
+ : `${detailPrefix}; selected context stayed inside the stable kernel`;
932
+ return {
933
+ selectedContextCount: selectedContext.length,
934
+ stableKernelBlockCount: stableKernelBlocks.length,
935
+ brainCompiledBlockCount: brainCompiledBlocks.length,
936
+ stableKernelSources: sortedUniqueStrings(stableKernelBlocks.map((block) => block.source)),
937
+ brainCompiledSources: sortedUniqueStrings(brainCompiledBlocks.map((block) => block.source)),
938
+ selectionTiers,
939
+ evidence,
940
+ detail
941
+ };
942
+ }
943
+ function buildAttachCompileStatus(result, observability, activePackId) {
944
+ if (!result.ok) {
945
+ return {
946
+ ok: false,
947
+ fallbackToStaticContext: result.fallbackToStaticContext,
948
+ hardRequirementViolated: result.hardRequirementViolated,
949
+ activePackId,
950
+ usedLearnedRouteFn: null,
951
+ routerIdentity: observability?.learnedRouteFn.routerIdentity ?? null,
952
+ initMode: observability?.initHandoff.initMode ?? null,
953
+ handoffState: observability?.initHandoff.handoffState ?? null,
954
+ seedSources: observability?.initHandoff.seedSources ?? [],
955
+ contextAttribution: buildContextAttributionSummary({
956
+ fallbackToStaticContext: result.fallbackToStaticContext,
957
+ hardRequirementViolated: result.hardRequirementViolated,
958
+ usedLearnedRouteFn: null
959
+ }),
960
+ notes: [],
961
+ error: result.error
962
+ };
963
+ }
964
+ const notes = [...result.compileResponse.diagnostics.notes];
965
+ const contextAttribution = buildContextAttributionSummary({
966
+ fallbackToStaticContext: false,
967
+ hardRequirementViolated: false,
968
+ usedLearnedRouteFn: result.compileResponse.diagnostics.usedLearnedRouteFn,
969
+ selectedContext: result.compileResponse.selectedContext,
970
+ notes
971
+ });
972
+ return {
973
+ ok: true,
974
+ fallbackToStaticContext: false,
975
+ hardRequirementViolated: false,
976
+ activePackId: result.activePackId,
977
+ usedLearnedRouteFn: result.compileResponse.diagnostics.usedLearnedRouteFn,
978
+ routerIdentity: result.compileResponse.diagnostics.routerIdentity,
979
+ initMode: readDiagnosticNoteValue(notes, "init_mode=") ?? observability?.initHandoff.initMode ?? null,
980
+ handoffState: readDiagnosticNoteValue(notes, "handoff_state=") ?? observability?.initHandoff.handoffState ?? null,
981
+ seedSources: readDiagnosticNoteList(notes, "seed_sources=").length > 0
982
+ ? readDiagnosticNoteList(notes, "seed_sources=")
983
+ : observability?.initHandoff.seedSources ?? [],
984
+ contextAttribution,
985
+ notes,
986
+ error: null
987
+ };
988
+ }
989
+ function buildAttachSuccessSignals(input) {
990
+ const signals = [];
991
+ const activeTarget = input.observability?.target ?? null;
992
+ if (input.inspection.active?.activationReady) {
993
+ signals.push(`active_pack_ready:${input.inspection.active.packId}`);
994
+ }
995
+ if (activeTarget !== null) {
996
+ signals.push(`active_workspace_snapshot:${activeTarget.workspaceSnapshot}`);
997
+ }
998
+ if (input.compile?.ok) {
999
+ signals.push(`compile_ok:${input.compile.activePackId ?? "unknown"}`);
1000
+ }
1001
+ if (input.compile !== null) {
1002
+ signals.push(`context:${input.compile.contextAttribution.evidence}`);
1003
+ signals.push(`context_blocks:kernel=${input.compile.contextAttribution.stableKernelBlockCount},brain=${input.compile.contextAttribution.brainCompiledBlockCount}`);
1004
+ }
1005
+ if (input.compile?.handoffState !== null && input.compile?.handoffState !== undefined) {
1006
+ signals.push(`handoff:${input.compile.handoffState}`);
1007
+ }
1008
+ if (input.inspection.active?.routePolicy === "requires_learned_routing") {
1009
+ if (input.compile?.ok && input.compile.usedLearnedRouteFn === true && input.compile.routerIdentity !== null) {
1010
+ signals.push(`learned_route_compile_verified:${input.compile.routerIdentity}`);
1011
+ }
1012
+ }
1013
+ else if (input.compile?.ok) {
1014
+ signals.push("heuristic_compile_verified");
1015
+ }
1016
+ return signals;
1017
+ }
1018
+ function buildAttachStatusCompileInput(activationRoot, compile) {
1019
+ if (compile === false) {
1020
+ return null;
1021
+ }
1022
+ return {
1023
+ activationRoot,
1024
+ agentId: normalizeOptionalString(compile?.agentId) ?? `${DEFAULT_AGENT_ID}-attach-status`,
1025
+ message: normalizeOptionalString(compile?.message) ?? DEFAULT_ATTACH_STATUS_MESSAGE,
1026
+ ...(compile?.maxContextBlocks !== undefined ? { maxContextBlocks: compile.maxContextBlocks } : {}),
1027
+ ...(compile?.maxContextChars !== undefined ? { maxContextChars: compile.maxContextChars } : {}),
1028
+ ...(compile?.mode !== undefined ? { mode: compile.mode } : {}),
1029
+ ...(compile?.compactionMode !== undefined ? { compactionMode: compile.compactionMode } : {}),
1030
+ runtimeHints: compile?.runtimeHints ?? [...DEFAULT_ATTACH_STATUS_RUNTIME_HINTS]
1031
+ };
1032
+ }
1033
+ export function describeAttachStatus(input) {
1034
+ const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
1035
+ const inspection = inspectActivationState(activationRoot);
1036
+ const activeObservability = inspection.active === null ? null : describeActivationObservability(activationRoot, "active");
1037
+ const compileInput = buildAttachStatusCompileInput(activationRoot, input.compile);
1038
+ const compile = compileInput === null
1039
+ ? null
1040
+ : buildAttachCompileStatus(compileRuntimeContext(compileInput), activeObservability, inspection.active?.packId ?? null);
1041
+ return {
1042
+ runtimeOwner: "openclaw",
1043
+ activationRoot,
1044
+ inspection,
1045
+ activeObservability,
1046
+ compile,
1047
+ landingBoundaries: structuredClone(OPENCLAW_LANDING_BOUNDARIES_V1),
1048
+ successSignals: buildAttachSuccessSignals({
1049
+ inspection,
1050
+ observability: activeObservability,
1051
+ compile
1052
+ })
1053
+ };
1054
+ }
1055
+ export function rollbackRuntimeAttach(input) {
1056
+ const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
1057
+ const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
1058
+ const dryRun = input.dryRun === true;
1059
+ const before = inspectActivationState(activationRoot, updatedAt);
1060
+ const findings = [...before.rollback.findings];
1061
+ const allowed = before.rollback.allowed;
1062
+ if (!allowed) {
1063
+ return {
1064
+ runtimeOwner: "openclaw",
1065
+ activationRoot,
1066
+ updatedAt,
1067
+ dryRun,
1068
+ allowed,
1069
+ findings,
1070
+ before: {
1071
+ activePackId: before.active?.packId ?? before.pointers.active?.packId ?? null,
1072
+ candidatePackId: before.candidate?.packId ?? before.pointers.candidate?.packId ?? null,
1073
+ previousPackId: before.previous?.packId ?? before.pointers.previous?.packId ?? null
1074
+ },
1075
+ after: null,
1076
+ restoredPackId: null,
1077
+ parkedCandidatePackId: null
1078
+ };
1079
+ }
1080
+ const after = dryRun
1081
+ ? before.rollback.nextPointers
1082
+ : rollbackActivePack(activationRoot, updatedAt).pointers;
1083
+ return {
1084
+ runtimeOwner: "openclaw",
1085
+ activationRoot,
1086
+ updatedAt,
1087
+ dryRun,
1088
+ allowed,
1089
+ findings,
1090
+ before: {
1091
+ activePackId: before.active?.packId ?? before.pointers.active?.packId ?? null,
1092
+ candidatePackId: before.candidate?.packId ?? before.pointers.candidate?.packId ?? null,
1093
+ previousPackId: before.previous?.packId ?? before.pointers.previous?.packId ?? null
1094
+ },
1095
+ after: after === null ? null : {
1096
+ activePackId: after.active?.packId ?? null,
1097
+ candidatePackId: after.candidate?.packId ?? null,
1098
+ previousPackId: after.previous?.packId ?? null
1099
+ },
1100
+ restoredPackId: after?.active?.packId ?? null,
1101
+ parkedCandidatePackId: after?.candidate?.packId ?? null
1102
+ };
1103
+ }
1104
+ function resolveBootstrapNormalizedEventExport(input) {
1105
+ const interactionEvents = [...(input.interactionEvents ?? [])];
1106
+ const feedbackEvents = [...(input.feedbackEvents ?? [])];
1107
+ if (input.normalizedEventExport !== undefined && (interactionEvents.length > 0 || feedbackEvents.length > 0)) {
1108
+ throw new Error("Provide normalizedEventExport or interactionEvents/feedbackEvents, not both");
1109
+ }
1110
+ const normalizedEventExport = input.normalizedEventExport !== undefined
1111
+ ? structuredClone(input.normalizedEventExport)
1112
+ : buildNormalizedEventExport({
1113
+ interactionEvents,
1114
+ feedbackEvents
1115
+ });
1116
+ const validationErrors = validateNormalizedEventExport(normalizedEventExport);
1117
+ if (validationErrors.length > 0) {
1118
+ throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
1119
+ }
1120
+ return normalizedEventExport;
1121
+ }
1122
+ export function bootstrapRuntimeAttach(input) {
1123
+ const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
1124
+ const packRoot = path.resolve(normalizeNonEmptyString(input.packRoot, "packRoot"));
1125
+ const normalizedEventExport = resolveBootstrapNormalizedEventExport(input);
1126
+ const builtAt = normalizeIsoTimestamp(input.builtAt, "builtAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt ?? new Date().toISOString());
1127
+ const activatedAt = normalizeIsoTimestamp(input.activatedAt, "activatedAt", builtAt);
1128
+ const teacherSupervisionArtifacts = buildTeacherSupervisionArtifactsFromNormalizedEventExport({
1129
+ normalizedEventExport,
1130
+ observedAt: activatedAt,
1131
+ ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
1132
+ });
1133
+ const descriptor = materializeCandidatePackFromNormalizedEventExport(packRoot, {
1134
+ packLabel: normalizeNonEmptyString(input.packLabel, "packLabel"),
1135
+ workspace: input.workspace,
1136
+ normalizedEventExport,
1137
+ teacherSupervisionArtifacts,
1138
+ learnedRouting: input.learnedRouting ?? true,
1139
+ builtAt,
1140
+ ...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
1141
+ ...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
1142
+ ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {})
1143
+ });
1144
+ activatePack(activationRoot, packRoot, activatedAt);
1145
+ return {
1146
+ runtimeOwner: "openclaw",
1147
+ activationRoot,
1148
+ packRoot,
1149
+ packId: descriptor.manifest.packId,
1150
+ normalizedEventExport,
1151
+ status: describeAttachStatus({
1152
+ activationRoot,
1153
+ ...(input.compile !== undefined ? { compile: input.compile } : {})
1154
+ })
1155
+ };
1156
+ }
266
1157
  function buildCompileInteractionEvent(input) {
267
1158
  if (!input.compileResult.ok) {
268
1159
  return null;
@@ -466,6 +1357,9 @@ export function runRuntimeTurn(turn, options = {}) {
466
1357
  ...(turn.runtimeHints !== undefined ? { runtimeHints: turn.runtimeHints } : {})
467
1358
  };
468
1359
  const compileResult = compileRuntimeContext(compileInput);
1360
+ if (!compileResult.ok && compileResult.hardRequirementViolated) {
1361
+ throw new Error(compileResult.error);
1362
+ }
469
1363
  const warnings = [];
470
1364
  try {
471
1365
  const normalizedEventExport = buildNormalizedRuntimeEventExport(turn, compileResult);
@@ -492,4 +1386,1345 @@ export function runRuntimeTurn(turn, options = {}) {
492
1386
  };
493
1387
  }
494
1388
  }
1389
+ export function runContinuousProductLoopTurn(input) {
1390
+ const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
1391
+ const loopRoot = path.resolve(normalizeNonEmptyString(input.loopRoot, "loopRoot"));
1392
+ const failOpen = input.failOpen !== false;
1393
+ const currentState = cloneContinuousProductLoopState(input.state ??
1394
+ createContinuousProductLoopState({
1395
+ activationRoot,
1396
+ loopRoot
1397
+ }));
1398
+ currentState.activationRoot = activationRoot;
1399
+ currentState.loopRoot = loopRoot;
1400
+ const activeBeforeTurn = syncContinuousActivePack(currentState);
1401
+ const compileActiveVersion = activeBeforeTurn?.version ?? 0;
1402
+ const compileActivePackId = activeBeforeTurn?.packId ?? null;
1403
+ const turn = withContinuousTurnExport(input.turn, loopRoot);
1404
+ const turnResult = runRuntimeTurn(turn, {
1405
+ activationRoot,
1406
+ failOpen
1407
+ });
1408
+ const learningWarnings = [];
1409
+ let supervision = null;
1410
+ const learning = {
1411
+ warnings: learningWarnings,
1412
+ supervisionDigest: null,
1413
+ bridgeDigest: null,
1414
+ selectedSliceIds: [],
1415
+ materializationJobId: null,
1416
+ materializationReason: null,
1417
+ materializationLane: null,
1418
+ candidateRootDir: null,
1419
+ candidatePack: currentState.candidatePack === null ? null : cloneContinuousProductLoopPackVersion(currentState.candidatePack),
1420
+ runtimePlasticity: currentState.runtimePlasticity === null ? null : structuredClone(currentState.runtimePlasticity),
1421
+ promotionAllowed: false,
1422
+ promotionFindings: [],
1423
+ promoted: false
1424
+ };
1425
+ if (!turnResult.eventExport.ok) {
1426
+ learningWarnings.push(`continuous learner skipped: ${turnResult.eventExport.error}`);
1427
+ return {
1428
+ runtimeOwner: "openclaw",
1429
+ compileActiveVersion,
1430
+ compileActivePackId,
1431
+ turn: turnResult,
1432
+ supervision,
1433
+ learning,
1434
+ state: cloneContinuousProductLoopState(currentState)
1435
+ };
1436
+ }
1437
+ const normalizedEventExport = turnResult.eventExport.normalizedEventExport;
1438
+ supervision = buildCanonicalSupervision(normalizedEventExport);
1439
+ learning.supervisionDigest = supervision.supervisionDigest;
1440
+ currentState.lastSupervision = cloneCanonicalSupervision(supervision);
1441
+ const mergedHistory = mergeRuntimeEventHistory(currentState, normalizedEventExport);
1442
+ currentState.interactionEvents = mergedHistory.interactionEvents;
1443
+ currentState.feedbackEvents = mergedHistory.feedbackEvents;
1444
+ try {
1445
+ const learnerResult = advanceAlwaysOnLearningRuntime({
1446
+ packLabel: input.packLabel,
1447
+ workspace: input.workspace,
1448
+ interactionEvents: currentState.interactionEvents,
1449
+ feedbackEvents: currentState.feedbackEvents,
1450
+ learnedRouting: input.learnedRouting ?? true,
1451
+ state: currentState.learner,
1452
+ builtAt: normalizeIsoTimestamp(input.candidateBuiltAt, "candidateBuiltAt", normalizedEventExport.range.lastCreatedAt ?? normalizedEventExport.range.firstCreatedAt),
1453
+ ...(input.offlineArtifacts !== undefined ? { offlineArtifacts: input.offlineArtifacts } : {}),
1454
+ ...(input.structuralOps !== undefined ? { structuralOps: input.structuralOps } : {}),
1455
+ ...(input.sparseFeedback !== undefined ? { sparseFeedback: input.sparseFeedback } : {}),
1456
+ ...(input.liveSliceSize !== undefined ? { liveSliceSize: input.liveSliceSize } : {}),
1457
+ ...(input.backfillSliceSize !== undefined ? { backfillSliceSize: input.backfillSliceSize } : {}),
1458
+ ...(input.cadence !== undefined ? { cadence: input.cadence } : {})
1459
+ });
1460
+ currentState.learner = structuredClone(learnerResult.state);
1461
+ currentState.runtimePlasticity = learnerResult.state.runtimePlasticity === null ? null : structuredClone(learnerResult.state.runtimePlasticity);
1462
+ learning.runtimePlasticity = currentState.runtimePlasticity === null ? null : structuredClone(currentState.runtimePlasticity);
1463
+ learning.bridgeDigest = learnerResult.bridge.bridgeDigest;
1464
+ learning.selectedSliceIds = learnerResult.selectedSlices.map((slice) => slice.sliceId);
1465
+ learning.materializationJobId = learnerResult.materialization?.jobId ?? null;
1466
+ learning.materializationReason = learnerResult.materialization?.reason ?? null;
1467
+ learning.materializationLane = learnerResult.materialization?.lane ?? null;
1468
+ if (learnerResult.materialization !== null) {
1469
+ const candidatePack = registerPackVersion(currentState, buildLearningCandidateTarget(learnerResult.materialization.candidate));
1470
+ const candidateRootDir = buildContinuousPackRoot(loopRoot, candidatePack);
1471
+ const descriptor = materializeAlwaysOnLearningCandidatePack(candidateRootDir, learnerResult.materialization);
1472
+ const candidateTarget = describePackCompileTarget(descriptor);
1473
+ learning.candidateRootDir = candidateRootDir;
1474
+ learning.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
1475
+ currentState.candidatePack = cloneContinuousProductLoopPackVersion(candidatePack);
1476
+ const stagedAt = normalizeIsoTimestamp(input.stageUpdatedAt, "stageUpdatedAt", descriptor.manifest.provenance.builtAt);
1477
+ stageCandidatePack(activationRoot, candidateRootDir, stagedAt);
1478
+ const stagedInspection = inspectActivationState(activationRoot, stagedAt);
1479
+ learning.promotionAllowed = stagedInspection.promotion.allowed;
1480
+ learning.promotionFindings = [...stagedInspection.promotion.findings];
1481
+ if ((input.autoPromote ?? true) && stagedInspection.promotion.allowed) {
1482
+ const promotedAt = normalizeIsoTimestamp(input.promoteUpdatedAt, "promoteUpdatedAt", stagedAt);
1483
+ promoteCandidatePack(activationRoot, promotedAt);
1484
+ currentState.promotionCount += 1;
1485
+ currentState.candidatePack = null;
1486
+ learning.promoted = true;
1487
+ const activePack = registerPackVersion(currentState, candidateTarget);
1488
+ currentState.currentActivePack = cloneContinuousProductLoopPackVersion(activePack);
1489
+ currentState.activePackVersion = activePack.version;
1490
+ syncContinuousActivePack(currentState);
1491
+ }
1492
+ }
1493
+ }
1494
+ catch (error) {
1495
+ if (!failOpen) {
1496
+ throw error;
1497
+ }
1498
+ learningWarnings.push(`continuous learner failed open: ${toErrorMessage(error)}`);
1499
+ }
1500
+ return {
1501
+ runtimeOwner: "openclaw",
1502
+ compileActiveVersion,
1503
+ compileActivePackId,
1504
+ turn: turnResult,
1505
+ supervision,
1506
+ learning,
1507
+ state: cloneContinuousProductLoopState(currentState)
1508
+ };
1509
+ }
1510
+ function ensureRecordedSessionTrace(trace) {
1511
+ if (trace.contract !== RECORDED_SESSION_TRACE_CONTRACT) {
1512
+ throw new Error(`${RECORDED_SESSION_TRACE_CONTRACT} contract is required`);
1513
+ }
1514
+ normalizeNonEmptyString(trace.traceId, "traceId");
1515
+ normalizeIsoTimestamp(trace.recordedAt, "recordedAt");
1516
+ normalizeIsoTimestamp(trace.bundleBuiltAt, "bundleBuiltAt");
1517
+ normalizeNonEmptyString(trace.sessionId, "sessionId");
1518
+ normalizeNonEmptyString(trace.channel, "channel");
1519
+ normalizeNonEmptyString(trace.sourceStream, "sourceStream");
1520
+ normalizeIsoTimestamp(trace.seedBuiltAt, "seedBuiltAt");
1521
+ normalizeIsoTimestamp(trace.seedActivatedAt, "seedActivatedAt");
1522
+ if (trace.privacy.sanitized !== true) {
1523
+ throw new Error("recorded session trace must be explicitly sanitized");
1524
+ }
1525
+ if (trace.seedCues.length === 0) {
1526
+ throw new Error("recorded session trace requires at least one seed cue");
1527
+ }
1528
+ if (trace.turns.length === 0) {
1529
+ throw new Error("recorded session trace requires at least one turn");
1530
+ }
1531
+ for (const [index, cue] of trace.seedCues.entries()) {
1532
+ normalizeNonEmptyString(cue.cueId, `seedCues[${index}].cueId`);
1533
+ normalizeIsoTimestamp(cue.createdAt, `seedCues[${index}].createdAt`);
1534
+ normalizeNonEmptyString(cue.content, `seedCues[${index}].content`);
1535
+ }
1536
+ for (const [index, turn] of trace.turns.entries()) {
1537
+ normalizeIsoTimestamp(turn.createdAt, `turns[${index}].createdAt`);
1538
+ normalizeNonEmptyString(turn.userMessage, `turns[${index}].userMessage`);
1539
+ if ((turn.expectedContextPhrases ?? []).length === 0) {
1540
+ throw new Error(`turns[${index}].expectedContextPhrases must include at least one phrase`);
1541
+ }
1542
+ for (const [feedbackIndex, feedback] of (turn.feedback ?? []).entries()) {
1543
+ normalizeIsoTimestamp(feedback.createdAt, `turns[${index}].feedback[${feedbackIndex}].createdAt`);
1544
+ normalizeNonEmptyString(feedback.content, `turns[${index}].feedback[${feedbackIndex}].content`);
1545
+ }
1546
+ }
1547
+ }
1548
+ function padReplayNumber(value) {
1549
+ return String(value).padStart(2, "0");
1550
+ }
1551
+ function replayTurnId(traceId, index, explicitValue) {
1552
+ return normalizeOptionalString(explicitValue) ?? `${traceId}-turn-${padReplayNumber(index + 1)}`;
1553
+ }
1554
+ function replaySequenceStart(index) {
1555
+ return 1_000 + index * 10;
1556
+ }
1557
+ function replayMessageId(turnId) {
1558
+ return `msg-${turnId}`;
1559
+ }
1560
+ function addMinutes(value, minutes) {
1561
+ return new Date(Date.parse(value) + minutes * 60_000).toISOString();
1562
+ }
1563
+ function normalizeReplayPhrase(value) {
1564
+ return value.toLowerCase().replace(/\s+/gu, " ").trim();
1565
+ }
1566
+ function hasReplayPhrase(texts, phrase) {
1567
+ const normalizedPhrase = normalizeReplayPhrase(phrase);
1568
+ return texts.some((text) => normalizeReplayPhrase(text).includes(normalizedPhrase));
1569
+ }
1570
+ function uniqueStringsInOrder(values) {
1571
+ const seen = new Set();
1572
+ const unique = [];
1573
+ for (const value of values) {
1574
+ if (seen.has(value)) {
1575
+ continue;
1576
+ }
1577
+ seen.add(value);
1578
+ unique.push(value);
1579
+ }
1580
+ return unique;
1581
+ }
1582
+ function buildRecordedSessionSeedExport(trace) {
1583
+ const agentId = normalizeOptionalString(trace.agentId) ?? DEFAULT_AGENT_ID;
1584
+ const seedSessionId = `${trace.sessionId}-seed`;
1585
+ const sourceStream = `${trace.sourceStream}/seed`;
1586
+ const interactionEvents = [];
1587
+ const feedbackEvents = [];
1588
+ let sequence = 1;
1589
+ for (const cue of trace.seedCues) {
1590
+ const interactionEventId = `${seedSessionId}:${cue.cueId}:interaction`;
1591
+ const feedbackEventId = `${seedSessionId}:${cue.cueId}:feedback`;
1592
+ const interaction = createInteractionEvent({
1593
+ eventId: interactionEventId,
1594
+ agentId,
1595
+ sessionId: seedSessionId,
1596
+ channel: trace.channel,
1597
+ sequence,
1598
+ kind: "operator_override",
1599
+ createdAt: cue.createdAt,
1600
+ source: {
1601
+ runtimeOwner: "openclaw",
1602
+ stream: sourceStream
1603
+ },
1604
+ messageId: `${cue.cueId}-seed-message`
1605
+ });
1606
+ sequence += 1;
1607
+ const feedback = createFeedbackEvent({
1608
+ eventId: feedbackEventId,
1609
+ agentId,
1610
+ sessionId: seedSessionId,
1611
+ channel: trace.channel,
1612
+ sequence,
1613
+ kind: cue.kind ?? "teaching",
1614
+ createdAt: addMinutes(cue.createdAt, 1),
1615
+ source: {
1616
+ runtimeOwner: "openclaw",
1617
+ stream: sourceStream
1618
+ },
1619
+ content: cue.content,
1620
+ relatedInteractionId: interaction.eventId
1621
+ });
1622
+ sequence += 1;
1623
+ interactionEvents.push(interaction);
1624
+ feedbackEvents.push(feedback);
1625
+ }
1626
+ return buildNormalizedEventExport({
1627
+ interactionEvents,
1628
+ feedbackEvents
1629
+ });
1630
+ }
1631
+ function recordedSessionFixtureBase(trace) {
1632
+ const traceHash = checksumJsonPayload(trace);
1633
+ const turns = trace.turns.map((turn, index) => {
1634
+ const turnId = replayTurnId(trace.traceId, index, turn.turnId);
1635
+ const sequenceStart = replaySequenceStart(index);
1636
+ return {
1637
+ turnId,
1638
+ turn: {
1639
+ ...(trace.agentId !== undefined && trace.agentId !== null ? { agentId: trace.agentId } : {}),
1640
+ sessionId: trace.sessionId,
1641
+ channel: trace.channel,
1642
+ sourceStream: trace.sourceStream,
1643
+ userMessage: turn.userMessage,
1644
+ createdAt: turn.createdAt,
1645
+ sequenceStart,
1646
+ maxContextBlocks: 3,
1647
+ mode: "heuristic",
1648
+ ...(turn.runtimeHints !== undefined ? { runtimeHints: [...turn.runtimeHints] } : {}),
1649
+ compile: {
1650
+ createdAt: turn.createdAt,
1651
+ sequence: sequenceStart
1652
+ },
1653
+ delivery: turn.deliveredAt === undefined || turn.deliveredAt === null
1654
+ ? false
1655
+ : {
1656
+ createdAt: turn.deliveredAt,
1657
+ sequence: sequenceStart + 1,
1658
+ messageId: replayMessageId(turnId)
1659
+ },
1660
+ feedback: (turn.feedback ?? []).map((feedback, feedbackIndex) => ({
1661
+ createdAt: feedback.createdAt,
1662
+ content: feedback.content,
1663
+ sequence: sequenceStart + 2 + feedbackIndex,
1664
+ kind: feedback.kind ?? null
1665
+ }))
1666
+ },
1667
+ expectedContextPhrases: [...turn.expectedContextPhrases],
1668
+ minimumPhraseHits: Math.max(1, turn.minimumPhraseHits ?? turn.expectedContextPhrases.length)
1669
+ };
1670
+ });
1671
+ return {
1672
+ contract: RECORDED_SESSION_FIXTURE_CONTRACT,
1673
+ traceId: trace.traceId,
1674
+ source: trace.source,
1675
+ recordedAt: trace.recordedAt,
1676
+ bundleBuiltAt: trace.bundleBuiltAt,
1677
+ traceHash,
1678
+ privacy: {
1679
+ sanitized: true,
1680
+ notes: [...trace.privacy.notes]
1681
+ },
1682
+ workspace: {
1683
+ workspaceId: trace.workspace.workspaceId,
1684
+ snapshotId: trace.workspace.snapshotId,
1685
+ capturedAt: trace.workspace.capturedAt,
1686
+ rootDir: trace.workspace.rootDir,
1687
+ ...(trace.workspace.branch !== undefined ? { branch: trace.workspace.branch } : {}),
1688
+ revision: trace.workspace.revision,
1689
+ ...(trace.workspace.labels !== undefined ? { labels: [...trace.workspace.labels] } : {})
1690
+ },
1691
+ seedBuiltAt: trace.seedBuiltAt,
1692
+ seedActivatedAt: trace.seedActivatedAt,
1693
+ seedExport: buildRecordedSessionSeedExport(trace),
1694
+ turns
1695
+ };
1696
+ }
1697
+ export function buildRecordedSessionReplayFixture(trace) {
1698
+ ensureRecordedSessionTrace(trace);
1699
+ const base = recordedSessionFixtureBase(trace);
1700
+ return {
1701
+ ...base,
1702
+ fixtureHash: checksumJsonPayload(base)
1703
+ };
1704
+ }
1705
+ function recordedSessionReplayFixtureBase(fixture) {
1706
+ return {
1707
+ contract: RECORDED_SESSION_FIXTURE_CONTRACT,
1708
+ traceId: fixture.traceId,
1709
+ source: fixture.source,
1710
+ recordedAt: fixture.recordedAt,
1711
+ bundleBuiltAt: fixture.bundleBuiltAt,
1712
+ traceHash: fixture.traceHash,
1713
+ privacy: {
1714
+ sanitized: true,
1715
+ notes: [...fixture.privacy.notes]
1716
+ },
1717
+ workspace: {
1718
+ workspaceId: fixture.workspace.workspaceId,
1719
+ snapshotId: fixture.workspace.snapshotId,
1720
+ capturedAt: fixture.workspace.capturedAt,
1721
+ rootDir: fixture.workspace.rootDir,
1722
+ ...(fixture.workspace.branch !== undefined ? { branch: fixture.workspace.branch } : {}),
1723
+ revision: fixture.workspace.revision,
1724
+ ...(fixture.workspace.labels !== undefined ? { labels: [...fixture.workspace.labels] } : {})
1725
+ },
1726
+ seedBuiltAt: fixture.seedBuiltAt,
1727
+ seedActivatedAt: fixture.seedActivatedAt,
1728
+ seedExport: fixture.seedExport,
1729
+ turns: fixture.turns.map((turn) => ({
1730
+ turnId: turn.turnId,
1731
+ turn: structuredClone(turn.turn),
1732
+ expectedContextPhrases: [...turn.expectedContextPhrases],
1733
+ minimumPhraseHits: turn.minimumPhraseHits
1734
+ }))
1735
+ };
1736
+ }
1737
+ function buildReplayTurnScore(input) {
1738
+ const phraseHits = input.expectedContextPhrases.filter((phrase) => hasReplayPhrase(input.texts, phrase));
1739
+ const missedPhrases = input.expectedContextPhrases.filter((phrase) => !phraseHits.includes(phrase));
1740
+ const compileScore = input.compileOk ? 40 : 0;
1741
+ const phraseScore = input.expectedContextPhrases.length === 0 ? 60 : Math.round((phraseHits.length / input.expectedContextPhrases.length) * 60);
1742
+ return {
1743
+ phraseHits,
1744
+ missedPhrases,
1745
+ qualityScore: Math.min(100, compileScore + phraseScore)
1746
+ };
1747
+ }
1748
+ function buildRecordedSessionTurnReport(turnFixture, result, options) {
1749
+ const compileOk = result.ok;
1750
+ const selectedContextTexts = compileOk ? result.compileResponse.selectedContext.map((block) => block.text) : [];
1751
+ const selectedContextIds = compileOk ? result.compileResponse.selectedContext.map((block) => block.id) : [];
1752
+ const scoring = buildReplayTurnScore({
1753
+ compileOk,
1754
+ texts: selectedContextTexts,
1755
+ expectedContextPhrases: turnFixture.expectedContextPhrases
1756
+ });
1757
+ const eventExportDigest = result.eventExport.ok === true ? result.eventExport.normalizedEventExport.provenance.exportDigest : null;
1758
+ return {
1759
+ turnId: turnFixture.turnId,
1760
+ compileOk,
1761
+ fallbackToStaticContext: result.fallbackToStaticContext,
1762
+ hardRequirementViolated: result.hardRequirementViolated,
1763
+ activePackId: result.ok ? result.activePackId : null,
1764
+ usedLearnedRouteFn: result.ok ? result.compileResponse.diagnostics.usedLearnedRouteFn : false,
1765
+ routerIdentity: result.ok ? result.compileResponse.diagnostics.routerIdentity : null,
1766
+ selectionDigest: result.ok ? result.compileResponse.diagnostics.selectionDigest : null,
1767
+ selectedContextIds,
1768
+ selectedContextTexts,
1769
+ eventExportDigest,
1770
+ expectedContextPhrases: [...turnFixture.expectedContextPhrases],
1771
+ minimumPhraseHits: turnFixture.minimumPhraseHits,
1772
+ phraseHits: scoring.phraseHits,
1773
+ missedPhrases: scoring.missedPhrases,
1774
+ qualityScore: scoring.qualityScore,
1775
+ compileActiveVersion: options.compileActiveVersion,
1776
+ promoted: options.promoted,
1777
+ warnings: [...result.warnings]
1778
+ };
1779
+ }
1780
+ function buildRecordedSessionReplayModeSummary(mode, turns) {
1781
+ const compileOkCount = turns.filter((turn) => turn.compileOk).length;
1782
+ const phraseHitCount = turns.reduce((sum, turn) => sum + turn.phraseHits.length, 0);
1783
+ const phraseCount = turns.reduce((sum, turn) => sum + turn.expectedContextPhrases.length, 0);
1784
+ const usedLearnedRouteTurnCount = turns.filter((turn) => turn.usedLearnedRouteFn).length;
1785
+ const promotionCount = turns.filter((turn) => turn.promoted).length;
1786
+ const qualityScore = turns.length === 0 ? 0 : Math.round(turns.reduce((sum, turn) => sum + turn.qualityScore, 0) / turns.length);
1787
+ const packIds = uniqueStringsInOrder(turns.map((turn) => turn.activePackId).filter(isPresent));
1788
+ const base = {
1789
+ mode,
1790
+ qualityScore,
1791
+ compileOkCount,
1792
+ phraseHitCount,
1793
+ phraseCount,
1794
+ usedLearnedRouteTurnCount,
1795
+ promotionCount,
1796
+ packIds
1797
+ };
1798
+ return {
1799
+ ...base,
1800
+ scoreHash: checksumJsonPayload({
1801
+ summary: base,
1802
+ turns: turns.map((turn) => ({
1803
+ turnId: turn.turnId,
1804
+ qualityScore: turn.qualityScore,
1805
+ phraseHits: turn.phraseHits,
1806
+ missedPhrases: turn.missedPhrases,
1807
+ compileOk: turn.compileOk,
1808
+ usedLearnedRouteFn: turn.usedLearnedRouteFn,
1809
+ activePackId: turn.activePackId,
1810
+ selectionDigest: turn.selectionDigest,
1811
+ promoted: turn.promoted,
1812
+ compileActiveVersion: turn.compileActiveVersion
1813
+ }))
1814
+ })
1815
+ };
1816
+ }
1817
+ function buildRecordedSessionReplayModeReport(mode, turns) {
1818
+ return {
1819
+ mode,
1820
+ summary: buildRecordedSessionReplayModeSummary(mode, turns),
1821
+ turns: [...turns]
1822
+ };
1823
+ }
1824
+ function buildRecordedSessionReplayScoreHash(modes) {
1825
+ return checksumJsonPayload(modes.map((mode) => ({
1826
+ mode: mode.mode,
1827
+ qualityScore: mode.summary.qualityScore,
1828
+ compileOkCount: mode.summary.compileOkCount,
1829
+ phraseHitCount: mode.summary.phraseHitCount,
1830
+ phraseCount: mode.summary.phraseCount,
1831
+ usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
1832
+ promotionCount: mode.summary.promotionCount,
1833
+ packIds: mode.summary.packIds,
1834
+ scoreHash: mode.summary.scoreHash
1835
+ })));
1836
+ }
1837
+ function recordedSessionReplayBundleBase(bundle) {
1838
+ return {
1839
+ contract: RECORDED_SESSION_BUNDLE_CONTRACT,
1840
+ traceId: bundle.traceId,
1841
+ source: bundle.source,
1842
+ recordedAt: bundle.recordedAt,
1843
+ generatedAt: bundle.generatedAt,
1844
+ traceHash: bundle.traceHash,
1845
+ fixtureHash: bundle.fixtureHash,
1846
+ scoreHash: bundle.scoreHash,
1847
+ privacy: {
1848
+ sanitized: true,
1849
+ notes: [...bundle.privacy.notes]
1850
+ },
1851
+ modes: bundle.modes.map((mode) => ({
1852
+ mode: mode.mode,
1853
+ summary: {
1854
+ ...mode.summary,
1855
+ packIds: [...mode.summary.packIds]
1856
+ },
1857
+ turns: mode.turns.map((turn) => ({
1858
+ ...turn,
1859
+ selectedContextIds: [...turn.selectedContextIds],
1860
+ selectedContextTexts: [...turn.selectedContextTexts],
1861
+ expectedContextPhrases: [...turn.expectedContextPhrases],
1862
+ phraseHits: [...turn.phraseHits],
1863
+ missedPhrases: [...turn.missedPhrases],
1864
+ warnings: [...turn.warnings]
1865
+ }))
1866
+ })),
1867
+ summary: {
1868
+ winnerMode: bundle.summary.winnerMode,
1869
+ ranking: bundle.summary.ranking.map((entry) => ({ ...entry }))
1870
+ }
1871
+ };
1872
+ }
1873
+ function buildRecordedSessionTurnExportRoot(modeRoot, turnId) {
1874
+ return {
1875
+ rootDir: path.join(modeRoot, "exports", turnId),
1876
+ exportName: turnId
1877
+ };
1878
+ }
1879
+ function prepareReplayModeRoot(rootDir, mode) {
1880
+ const modeRoot = path.resolve(path.join(rootDir, mode));
1881
+ rmSync(modeRoot, { recursive: true, force: true });
1882
+ mkdirSync(modeRoot, { recursive: true });
1883
+ return modeRoot;
1884
+ }
1885
+ function prepareSeedActivation(rootDir, fixture) {
1886
+ const activationRoot = path.join(rootDir, "activation");
1887
+ const seedPackRoot = path.join(rootDir, "seed-pack");
1888
+ const seedPack = materializeCandidatePackFromNormalizedEventExport(seedPackRoot, {
1889
+ packLabel: `${fixture.traceId}-seed`,
1890
+ workspace: fixture.workspace,
1891
+ normalizedEventExport: fixture.seedExport,
1892
+ learnedRouting: false,
1893
+ builtAt: fixture.seedBuiltAt,
1894
+ offlineArtifacts: ["recorded-session-replay-seed"],
1895
+ structuralOps: {
1896
+ connect: 1
1897
+ }
1898
+ });
1899
+ activatePack(activationRoot, seedPackRoot, fixture.seedActivatedAt);
1900
+ return {
1901
+ activationRoot,
1902
+ seedPackId: seedPack.manifest.packId
1903
+ };
1904
+ }
1905
+ function runRecordedSessionNoBrainMode(rootDir, fixture) {
1906
+ const modeRoot = prepareReplayModeRoot(rootDir, "no_brain");
1907
+ const activationRoot = path.join(modeRoot, "activation");
1908
+ const turns = fixture.turns.map((turnFixture) => {
1909
+ const result = runRuntimeTurn({
1910
+ ...turnFixture.turn,
1911
+ export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
1912
+ }, {
1913
+ activationRoot,
1914
+ failOpen: true
1915
+ });
1916
+ return buildRecordedSessionTurnReport(turnFixture, result, {
1917
+ compileActiveVersion: null,
1918
+ promoted: false
1919
+ });
1920
+ });
1921
+ return buildRecordedSessionReplayModeReport("no_brain", turns);
1922
+ }
1923
+ function runRecordedSessionSeedPackMode(rootDir, fixture) {
1924
+ const modeRoot = prepareReplayModeRoot(rootDir, "seed_pack");
1925
+ const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
1926
+ const turns = fixture.turns.map((turnFixture) => {
1927
+ const result = runRuntimeTurn({
1928
+ ...turnFixture.turn,
1929
+ export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
1930
+ }, {
1931
+ activationRoot,
1932
+ failOpen: false
1933
+ });
1934
+ return buildRecordedSessionTurnReport(turnFixture, result, {
1935
+ compileActiveVersion: 1,
1936
+ promoted: false
1937
+ });
1938
+ });
1939
+ return buildRecordedSessionReplayModeReport("seed_pack", turns);
1940
+ }
1941
+ function runRecordedSessionLearnedReplayMode(rootDir, fixture) {
1942
+ const modeRoot = prepareReplayModeRoot(rootDir, "learned_replay");
1943
+ const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
1944
+ const loopRoot = path.join(modeRoot, "loop");
1945
+ let state;
1946
+ const turns = [];
1947
+ for (const turnFixture of fixture.turns) {
1948
+ const compileCreatedAt = normalizeIsoTimestamp(turnFixture.turn.compile?.createdAt, "turn.compile.createdAt", turnFixture.turn.createdAt);
1949
+ const result = runContinuousProductLoopTurn({
1950
+ activationRoot,
1951
+ loopRoot,
1952
+ packLabel: `${fixture.traceId}-learned`,
1953
+ workspace: fixture.workspace,
1954
+ ...(state !== undefined ? { state } : {}),
1955
+ learnedRouting: true,
1956
+ failOpen: false,
1957
+ turn: {
1958
+ ...turnFixture.turn,
1959
+ export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
1960
+ },
1961
+ candidateBuiltAt: addMinutes(compileCreatedAt, 2),
1962
+ stageUpdatedAt: addMinutes(compileCreatedAt, 3),
1963
+ promoteUpdatedAt: addMinutes(compileCreatedAt, 4)
1964
+ });
1965
+ state = result.state;
1966
+ turns.push(buildRecordedSessionTurnReport(turnFixture, result.turn, {
1967
+ compileActiveVersion: result.compileActiveVersion,
1968
+ promoted: result.learning.promoted
1969
+ }));
1970
+ }
1971
+ return buildRecordedSessionReplayModeReport("learned_replay", turns);
1972
+ }
1973
+ export function runRecordedSessionReplay(rootDir, fixture) {
1974
+ const resolvedRoot = path.resolve(normalizeNonEmptyString(rootDir, "rootDir"));
1975
+ const seedExportErrors = validateNormalizedEventExport(fixture.seedExport);
1976
+ if (seedExportErrors.length > 0) {
1977
+ throw new Error(`recorded session replay seed export is invalid: ${seedExportErrors.join("; ")}`);
1978
+ }
1979
+ const expectedFixtureHash = checksumJsonPayload(recordedSessionReplayFixtureBase(fixture));
1980
+ if (fixture.fixtureHash !== expectedFixtureHash) {
1981
+ throw new Error(`recorded session replay fixtureHash mismatch: expected ${expectedFixtureHash}, received ${fixture.fixtureHash}`);
1982
+ }
1983
+ const modes = [
1984
+ runRecordedSessionNoBrainMode(resolvedRoot, fixture),
1985
+ runRecordedSessionSeedPackMode(resolvedRoot, fixture),
1986
+ runRecordedSessionLearnedReplayMode(resolvedRoot, fixture)
1987
+ ];
1988
+ const ranking = modes
1989
+ .map((mode) => ({
1990
+ mode: mode.mode,
1991
+ qualityScore: mode.summary.qualityScore
1992
+ }))
1993
+ .sort((left, right) => right.qualityScore - left.qualityScore || left.mode.localeCompare(right.mode));
1994
+ const scoreHash = buildRecordedSessionReplayScoreHash(modes);
1995
+ const base = {
1996
+ contract: RECORDED_SESSION_BUNDLE_CONTRACT,
1997
+ traceId: fixture.traceId,
1998
+ source: fixture.source,
1999
+ recordedAt: fixture.recordedAt,
2000
+ generatedAt: fixture.bundleBuiltAt,
2001
+ traceHash: fixture.traceHash,
2002
+ fixtureHash: fixture.fixtureHash,
2003
+ scoreHash,
2004
+ privacy: {
2005
+ sanitized: true,
2006
+ notes: [...fixture.privacy.notes]
2007
+ },
2008
+ modes,
2009
+ summary: {
2010
+ winnerMode: ranking[0]?.mode ?? null,
2011
+ ranking
2012
+ }
2013
+ };
2014
+ return {
2015
+ ...base,
2016
+ bundleHash: checksumJsonPayload(base)
2017
+ };
2018
+ }
2019
+ function rescoreRecordedSessionReplayTurn(turn) {
2020
+ const scoring = buildReplayTurnScore({
2021
+ compileOk: turn.compileOk,
2022
+ texts: turn.selectedContextTexts,
2023
+ expectedContextPhrases: turn.expectedContextPhrases
2024
+ });
2025
+ return {
2026
+ ...turn,
2027
+ phraseHits: scoring.phraseHits,
2028
+ missedPhrases: scoring.missedPhrases,
2029
+ qualityScore: scoring.qualityScore,
2030
+ selectedContextIds: [...turn.selectedContextIds],
2031
+ selectedContextTexts: [...turn.selectedContextTexts],
2032
+ expectedContextPhrases: [...turn.expectedContextPhrases],
2033
+ warnings: [...turn.warnings]
2034
+ };
2035
+ }
2036
+ function rescoreRecordedSessionReplayMode(mode) {
2037
+ const turns = mode.turns.map((turn) => rescoreRecordedSessionReplayTurn(turn));
2038
+ return buildRecordedSessionReplayModeReport(mode.mode, turns);
2039
+ }
2040
+ export function rescoreRecordedSessionReplayBundle(bundle) {
2041
+ const modes = bundle.modes.map((mode) => rescoreRecordedSessionReplayMode(mode));
2042
+ return {
2043
+ scoreHash: buildRecordedSessionReplayScoreHash(modes),
2044
+ modes: modes.map((mode) => ({
2045
+ mode: mode.mode,
2046
+ qualityScore: mode.summary.qualityScore,
2047
+ scoreHash: mode.summary.scoreHash
2048
+ }))
2049
+ };
2050
+ }
2051
+ export function verifyRecordedSessionReplayBundleHashes(bundle) {
2052
+ const rescored = rescoreRecordedSessionReplayBundle(bundle);
2053
+ const rebuiltBundleHash = checksumJsonPayload(recordedSessionReplayBundleBase(bundle));
2054
+ return {
2055
+ bundleHashMatches: rebuiltBundleHash === bundle.bundleHash,
2056
+ scoreHashMatches: rescored.scoreHash === bundle.scoreHash
2057
+ };
2058
+ }
2059
+ function summarizeOperatorSlot(slot, updatedAt) {
2060
+ if (slot === null) {
2061
+ return null;
2062
+ }
2063
+ return {
2064
+ slot: slot.slot,
2065
+ packId: slot.packId,
2066
+ activationReady: slot.activationReady,
2067
+ routePolicy: slot.routePolicy,
2068
+ routerIdentity: slot.routerIdentity,
2069
+ workspaceSnapshot: slot.workspaceSnapshot,
2070
+ workspaceRevision: slot.workspaceRevision,
2071
+ eventRange: { ...slot.eventRange },
2072
+ eventExportDigest: slot.eventExportDigest,
2073
+ builtAt: slot.builtAt,
2074
+ updatedAt,
2075
+ findings: [...slot.findings]
2076
+ };
2077
+ }
2078
+ function summarizeLastPromotion(inspection) {
2079
+ if (inspection.active === null) {
2080
+ return {
2081
+ known: false,
2082
+ at: null,
2083
+ confidence: "no_active_pack",
2084
+ note: "active slot is empty, so no promotion can be proven"
2085
+ };
2086
+ }
2087
+ if (inspection.previous !== null) {
2088
+ return {
2089
+ known: inspection.pointers.active?.updatedAt !== null,
2090
+ at: inspection.pointers.active?.updatedAt ?? null,
2091
+ confidence: "proven_from_previous_pointer",
2092
+ note: "previous pointer is retained, so the current active pack is the last promoted pack"
2093
+ };
2094
+ }
2095
+ return {
2096
+ known: false,
2097
+ at: null,
2098
+ confidence: "unknown_from_local_pointers",
2099
+ note: "no previous pointer is retained, so local activation pointers cannot prove the last promotion time"
2100
+ };
2101
+ }
2102
+ function summarizeCandidateAheadBy(candidateAheadBy) {
2103
+ if (candidateAheadBy === null) {
2104
+ return [];
2105
+ }
2106
+ return Object.entries(candidateAheadBy)
2107
+ .filter(([, changed]) => changed === true)
2108
+ .map(([field]) => field)
2109
+ .sort();
2110
+ }
2111
+ function summarizeBrainState(active, observability) {
2112
+ if (active === null) {
2113
+ return {
2114
+ state: "no_active_pack",
2115
+ initMode: null,
2116
+ runtimePlasticitySource: null,
2117
+ seedStateVisible: false,
2118
+ seedBlockCount: 0,
2119
+ activePackId: null,
2120
+ activeWorkspaceSnapshot: null,
2121
+ activeEventExportDigest: null,
2122
+ detail: "no active pack is pinned, so the serve path can only fail open or hard fail"
2123
+ };
2124
+ }
2125
+ const state = observability.initHandoff.handoffState;
2126
+ const detail = state === "pg_promoted_pack_authoritative"
2127
+ ? "serving is pinned to a PG-promoted pack rather than seed-state authority"
2128
+ : state === "seed_state_authoritative"
2129
+ ? "serving is still pinned to the current seed-state authority"
2130
+ : "init/handoff metadata is missing for the active pack";
2131
+ return {
2132
+ state,
2133
+ initMode: observability.initHandoff.initMode,
2134
+ runtimePlasticitySource: observability.graphDynamics.runtimePlasticitySource,
2135
+ seedStateVisible: observability.initHandoff.seedStateVisible,
2136
+ seedBlockCount: observability.initHandoff.seedBlockCount,
2137
+ activePackId: active.packId,
2138
+ activeWorkspaceSnapshot: active.workspaceSnapshot,
2139
+ activeEventExportDigest: active.eventExportDigest,
2140
+ detail
2141
+ };
2142
+ }
2143
+ function summarizeServePath(compile) {
2144
+ if (compile === null) {
2145
+ return {
2146
+ state: "unprobed",
2147
+ fallbackToStaticContext: false,
2148
+ hardRequirementViolated: false,
2149
+ activePackId: null,
2150
+ usedLearnedRouteFn: null,
2151
+ routerIdentity: null,
2152
+ selectionMode: null,
2153
+ refreshStatus: null,
2154
+ freshnessChecksum: null,
2155
+ contextAttribution: buildContextAttributionSummary({
2156
+ fallbackToStaticContext: false,
2157
+ hardRequirementViolated: false,
2158
+ usedLearnedRouteFn: null,
2159
+ unprobed: true
2160
+ }),
2161
+ error: null
2162
+ };
2163
+ }
2164
+ if (!compile.ok) {
2165
+ return {
2166
+ state: compile.hardRequirementViolated ? "hard_fail" : "fail_open_static_context",
2167
+ fallbackToStaticContext: compile.fallbackToStaticContext,
2168
+ hardRequirementViolated: compile.hardRequirementViolated,
2169
+ activePackId: compile.activePackId,
2170
+ usedLearnedRouteFn: compile.usedLearnedRouteFn,
2171
+ routerIdentity: compile.routerIdentity,
2172
+ selectionMode: null,
2173
+ refreshStatus: null,
2174
+ freshnessChecksum: null,
2175
+ contextAttribution: compile.contextAttribution,
2176
+ error: compile.error
2177
+ };
2178
+ }
2179
+ return {
2180
+ state: "serving_active_pack",
2181
+ fallbackToStaticContext: compile.fallbackToStaticContext,
2182
+ hardRequirementViolated: compile.hardRequirementViolated,
2183
+ activePackId: compile.activePackId,
2184
+ usedLearnedRouteFn: compile.usedLearnedRouteFn,
2185
+ routerIdentity: compile.routerIdentity,
2186
+ selectionMode: readDiagnosticNoteValue(compile.notes, "selection_mode="),
2187
+ refreshStatus: readDiagnosticNoteValue(compile.notes, "router_refresh_status="),
2188
+ freshnessChecksum: readDiagnosticNoteValue(compile.notes, "router_freshness_checksum="),
2189
+ contextAttribution: compile.contextAttribution,
2190
+ error: compile.error
2191
+ };
2192
+ }
2193
+ function loadOperatorEventExport(input) {
2194
+ const eventExportPath = normalizeOptionalString(input.eventExportPath);
2195
+ if (eventExportPath === undefined) {
2196
+ return null;
2197
+ }
2198
+ const resolvedPath = path.resolve(eventExportPath);
2199
+ const stats = statSync(resolvedPath);
2200
+ if (stats.isDirectory()) {
2201
+ const bundle = loadRuntimeEventExportBundle(resolvedPath);
2202
+ return {
2203
+ sourcePath: resolvedPath,
2204
+ sourceKind: "bundle_root",
2205
+ normalizedEventExport: bundle.normalizedEventExport
2206
+ };
2207
+ }
2208
+ const normalizedEventExport = readJsonFile(resolvedPath);
2209
+ const validationErrors = validateNormalizedEventExport(normalizedEventExport);
2210
+ if (validationErrors.length > 0) {
2211
+ throw new Error(`normalized event export is invalid: ${validationErrors.join("; ")}`);
2212
+ }
2213
+ return {
2214
+ sourcePath: resolvedPath,
2215
+ sourceKind: "payload",
2216
+ normalizedEventExport
2217
+ };
2218
+ }
2219
+ function summarizeSupervision(input) {
2220
+ const loaded = loadOperatorEventExport(input);
2221
+ if (loaded === null) {
2222
+ return {
2223
+ available: false,
2224
+ sourcePath: null,
2225
+ sourceKind: "missing",
2226
+ exportDigest: null,
2227
+ flowing: null,
2228
+ sourceCount: 0,
2229
+ freshestSourceStream: null,
2230
+ freshestCreatedAt: null,
2231
+ freshestKind: null,
2232
+ humanLabelCount: null,
2233
+ sources: [],
2234
+ detail: "no event export path supplied"
2235
+ };
2236
+ }
2237
+ const observability = describeNormalizedEventExportObservability(loaded.normalizedEventExport);
2238
+ const freshestSource = observability.supervisionFreshnessBySource[0] ?? null;
2239
+ const flowing = observability.teacherFreshness.freshestCreatedAt !== null && observability.teacherFreshness.humanLabelCount > 0;
2240
+ return {
2241
+ available: true,
2242
+ sourcePath: loaded.sourcePath,
2243
+ sourceKind: loaded.sourceKind,
2244
+ exportDigest: observability.exportDigest,
2245
+ flowing,
2246
+ sourceCount: observability.supervisionFreshnessBySource.length,
2247
+ freshestSourceStream: observability.teacherFreshness.sourceStream ?? freshestSource?.sourceStream ?? null,
2248
+ freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null,
2249
+ freshestKind: observability.teacherFreshness.freshestKind ?? freshestSource?.freshestKind ?? null,
2250
+ humanLabelCount: observability.teacherFreshness.humanLabelCount,
2251
+ sources: [...observability.teacherFreshness.sources],
2252
+ detail: flowing
2253
+ ? "human supervision is visible in the supplied export"
2254
+ : "the supplied export does not yet show human supervision"
2255
+ };
2256
+ }
2257
+ function loadTeacherSnapshot(input) {
2258
+ const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
2259
+ if (teacherSnapshotPath === undefined) {
2260
+ return null;
2261
+ }
2262
+ const snapshot = readJsonFile(path.resolve(teacherSnapshotPath));
2263
+ if (snapshot.runtimeOwner !== "openclaw") {
2264
+ throw new Error("teacher snapshot runtimeOwner must be openclaw");
2265
+ }
2266
+ return snapshot;
2267
+ }
2268
+ function summarizeTeacherLoop(input) {
2269
+ const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
2270
+ if (teacherSnapshotPath === undefined) {
2271
+ return {
2272
+ available: false,
2273
+ sourcePath: null,
2274
+ lastNoOpReason: "unavailable",
2275
+ latestFreshness: "unavailable",
2276
+ lastProcessedAt: null,
2277
+ queueDepth: null,
2278
+ queueCapacity: null,
2279
+ running: null,
2280
+ lastMaterializedPackId: null,
2281
+ notes: [],
2282
+ detail: "no teacher snapshot path supplied"
2283
+ };
2284
+ }
2285
+ const snapshot = loadTeacherSnapshot(input);
2286
+ if (snapshot === null) {
2287
+ return {
2288
+ available: false,
2289
+ sourcePath: path.resolve(teacherSnapshotPath),
2290
+ lastNoOpReason: "unavailable",
2291
+ latestFreshness: "unavailable",
2292
+ lastProcessedAt: null,
2293
+ queueDepth: null,
2294
+ queueCapacity: null,
2295
+ running: null,
2296
+ lastMaterializedPackId: null,
2297
+ notes: [],
2298
+ detail: "teacher snapshot could not be loaded"
2299
+ };
2300
+ }
2301
+ return {
2302
+ available: true,
2303
+ sourcePath: path.resolve(teacherSnapshotPath),
2304
+ lastNoOpReason: snapshot.diagnostics.lastNoOpReason,
2305
+ latestFreshness: snapshot.diagnostics.latestFreshness,
2306
+ lastProcessedAt: snapshot.diagnostics.lastProcessedAt,
2307
+ queueDepth: snapshot.queue.depth,
2308
+ queueCapacity: snapshot.queue.capacity,
2309
+ running: snapshot.queue.running,
2310
+ lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
2311
+ notes: [...snapshot.diagnostics.notes],
2312
+ detail: "async teacher diagnostics loaded"
2313
+ };
2314
+ }
2315
+ function summarizeAlwaysOnLearning(input) {
2316
+ const teacherSnapshotPath = normalizeOptionalString(input.teacherSnapshotPath);
2317
+ if (teacherSnapshotPath === undefined) {
2318
+ return {
2319
+ available: false,
2320
+ sourcePath: null,
2321
+ bootstrapped: null,
2322
+ mode: "unavailable",
2323
+ nextPriorityLane: "unavailable",
2324
+ pendingLive: null,
2325
+ pendingBackfill: null,
2326
+ pendingTotal: null,
2327
+ freshLivePriority: null,
2328
+ learnedRange: null,
2329
+ materializationCount: null,
2330
+ lastMaterializedAt: null,
2331
+ lastMaterializationReason: null,
2332
+ lastMaterializationLane: null,
2333
+ lastMaterializationPriority: null,
2334
+ lastMaterializedPackId: null,
2335
+ detail: "no teacher snapshot path supplied"
2336
+ };
2337
+ }
2338
+ const snapshot = loadTeacherSnapshot(input);
2339
+ if (snapshot === null) {
2340
+ return {
2341
+ available: false,
2342
+ sourcePath: path.resolve(teacherSnapshotPath),
2343
+ bootstrapped: null,
2344
+ mode: "unavailable",
2345
+ nextPriorityLane: "unavailable",
2346
+ pendingLive: null,
2347
+ pendingBackfill: null,
2348
+ pendingTotal: null,
2349
+ freshLivePriority: null,
2350
+ learnedRange: null,
2351
+ materializationCount: null,
2352
+ lastMaterializedAt: null,
2353
+ lastMaterializationReason: null,
2354
+ lastMaterializationLane: null,
2355
+ lastMaterializationPriority: null,
2356
+ lastMaterializedPackId: null,
2357
+ detail: "teacher snapshot could not be loaded"
2358
+ };
2359
+ }
2360
+ const plan = describeAlwaysOnLearningRuntimeState(snapshot.learner.state, snapshot.learner.lastMaterialization);
2361
+ return {
2362
+ available: true,
2363
+ sourcePath: path.resolve(teacherSnapshotPath),
2364
+ bootstrapped: plan.bootstrapped,
2365
+ mode: plan.mode,
2366
+ nextPriorityLane: plan.nextPriorityLane,
2367
+ pendingLive: plan.pending.live,
2368
+ pendingBackfill: plan.pending.backfill,
2369
+ pendingTotal: plan.pending.total,
2370
+ freshLivePriority: plan.pending.freshLivePriority,
2371
+ learnedRange: plan.learnedRange === null ? null : { ...plan.learnedRange },
2372
+ materializationCount: plan.materialization.count,
2373
+ lastMaterializedAt: plan.materialization.lastMaterializedAt,
2374
+ lastMaterializationReason: plan.materialization.lastReason,
2375
+ lastMaterializationLane: plan.materialization.lastLane,
2376
+ lastMaterializationPriority: plan.materialization.lastPriority,
2377
+ lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
2378
+ detail: plan.pending.freshLivePriority
2379
+ ? "fresh live slices remain ahead of passive catch-up"
2380
+ : plan.pending.backfill > 0
2381
+ ? "passive backfill remains queued behind the current live state"
2382
+ : plan.bootstrapped
2383
+ ? "fast-init has handed off to the current learned export without queued backlog"
2384
+ : "learner is waiting for the first export"
2385
+ };
2386
+ }
2387
+ function buildOperatorFindings(report) {
2388
+ const findings = [];
2389
+ const push = (severity, code, summary, detail) => {
2390
+ findings.push({ severity, code, summary, detail });
2391
+ };
2392
+ if (report.active === null) {
2393
+ push("fail", "active_missing", "active slot is empty", "activate a healthy pack before serving or troubleshooting promotions");
2394
+ }
2395
+ else if (!report.active.activationReady) {
2396
+ 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
+ }
2398
+ else {
2399
+ push("pass", "active_ready", `active slot is ready: ${report.active.packId}`, "serving can inspect the active pack without activation drift");
2400
+ }
2401
+ if (report.learnedRouting.required) {
2402
+ if (report.learnedRouting.available) {
2403
+ push("pass", "learned_route_ready", `learned routing is active: ${report.learnedRouting.routerIdentity ?? "unknown-router"}`, `updateCount=${report.learnedRouting.updateCount ?? 0}; labels=${report.learnedRouting.collectedLabelsTotal ?? 0}; handoff=${report.learnedRouting.handoffState}`);
2404
+ }
2405
+ else {
2406
+ push("fail", "learned_route_missing", "active pack requires learned routing but the learned route artifact is unavailable", "repair the router artifact or promote a healthy learned-routing candidate before serving");
2407
+ }
2408
+ }
2409
+ else {
2410
+ push("pass", "learned_route_optional", "active pack does not require learned routing", `handoff=${report.learnedRouting.handoffState}`);
2411
+ }
2412
+ if (report.servePath.state === "serving_active_pack") {
2413
+ push("pass", "serve_path_verified", `serve path compiles from active pack ${report.servePath.activePackId ?? "unknown-pack"}`, `selection=${report.servePath.selectionMode ?? "unknown"}; tiers=${report.servePath.contextAttribution.selectionTiers ?? "unknown"}; router=${report.servePath.routerIdentity ?? "none"}; routeFreshness=${report.servePath.freshnessChecksum ?? "unknown"}`);
2414
+ }
2415
+ else if (report.servePath.state === "fail_open_static_context") {
2416
+ push("warn", "serve_path_fail_open", "serve path would fail open to static context", report.servePath.error ?? "compile probe fell back to static context");
2417
+ }
2418
+ else if (report.servePath.state === "hard_fail") {
2419
+ push("fail", "serve_path_hard_fail", "serve path would hard fail on the learned-routing requirement", report.servePath.error ?? "compile probe violated a learned-routing hard requirement");
2420
+ }
2421
+ else {
2422
+ push("warn", "serve_path_unprobed", "serve path was not probed", "operator surface could not verify fail-open versus hard-fail behavior");
2423
+ }
2424
+ if (report.learnedRouting.required && report.servePath.state === "serving_active_pack" && report.servePath.usedLearnedRouteFn !== true) {
2425
+ push("fail", "serve_path_route_evidence_missing", "serve path compiled without learned-route evidence on a learned-routing pack", `router=${report.servePath.routerIdentity ?? report.learnedRouting.routerIdentity ?? "none"}; selection=${report.servePath.selectionMode ?? "unknown"}`);
2426
+ }
2427
+ if (report.servePath.state === "serving_active_pack") {
2428
+ if (report.servePath.contextAttribution.brainCompiledBlockCount > 0) {
2429
+ push("pass", "brain_context_visible", "serve probe selected brain-compiled context", `brainSources=${formatList(report.servePath.contextAttribution.brainCompiledSources)}; kernelSources=${formatList(report.servePath.contextAttribution.stableKernelSources)}; evidence=${report.servePath.contextAttribution.evidence}`);
2430
+ }
2431
+ else {
2432
+ push("warn", "brain_context_kernel_only", "serve probe stayed inside the stable kernel", `kernelSources=${formatList(report.servePath.contextAttribution.stableKernelSources)}; evidence=${report.servePath.contextAttribution.evidence}; ${report.servePath.contextAttribution.detail}`);
2433
+ }
2434
+ }
2435
+ if (report.candidate === null) {
2436
+ push("pass", "candidate_missing", "no candidate pack is currently staged", "steady state can legitimately run without a staged candidate until the next refresh lands");
2437
+ }
2438
+ else if (!report.candidate.activationReady) {
2439
+ push("warn", "candidate_unhealthy", `candidate slot is not activation-ready: ${report.candidate.packId}`, report.candidate.findings.join("; ") || "fix candidate pack payloads before promotion");
2440
+ }
2441
+ else if (report.promotion.allowed) {
2442
+ push("pass", "promotion_ready", `candidate is promotion-ready: ${report.candidate.packId}`, report.freshness.candidateAheadBy.length === 0
2443
+ ? "candidate is staged and promotion is allowed"
2444
+ : `candidate is ahead on ${report.freshness.candidateAheadBy.join(", ")}`);
2445
+ }
2446
+ else {
2447
+ push("warn", "promotion_blocked", `candidate is staged but promotion is blocked: ${report.candidate.packId}`, report.promotion.findings.join("; ") || "inspect freshness and activation findings before promotion");
2448
+ }
2449
+ if (report.promotion.lastPromotion.known) {
2450
+ push("pass", "last_promotion_known", `last promotion is proven at ${report.promotion.lastPromotion.at}`, report.promotion.lastPromotion.note);
2451
+ }
2452
+ else {
2453
+ push("warn", "last_promotion_unknown", "last promotion is not provable from local activation pointers", report.promotion.lastPromotion.note);
2454
+ }
2455
+ if (report.rollback.allowed) {
2456
+ push("pass", "rollback_ready", `rollback is ready to restore ${report.rollback.previousPackId ?? "the previous pack"}`, report.previous === null ? "previous pack is pointer-visible even if slot inspection is unavailable" : `previous pack=${report.previous.packId}`);
2457
+ }
2458
+ else {
2459
+ push("warn", "rollback_blocked", "rollback is not ready", report.rollback.findings.join("; ") || "previous pointer is missing or no rollback target is retained");
2460
+ }
2461
+ if (!report.supervision.available) {
2462
+ push("warn", "supervision_unavailable", "supervision flow is not inspectable yet", "pass `--event-export <bundle-root-or-payload>` to inspect local supervision freshness");
2463
+ }
2464
+ else if (report.supervision.flowing) {
2465
+ push("pass", "supervision_visible", `supervision is flowing through ${report.supervision.freshestSourceStream ?? "unknown-source"}`, `freshest=${report.supervision.freshestCreatedAt ?? "unknown"}; humanLabels=${report.supervision.humanLabelCount ?? 0}`);
2466
+ }
2467
+ else {
2468
+ push("warn", "supervision_not_flowing", "the supplied export does not yet show human supervision", `sourcePath=${report.supervision.sourcePath ?? "unknown"}; exportDigest=${report.supervision.exportDigest ?? "unknown"}`);
2469
+ }
2470
+ if (!report.teacherLoop.available) {
2471
+ push("warn", "teacher_snapshot_unavailable", "last async no-op reason is not inspectable yet", "pass `--teacher-snapshot <snapshot.json>` to inspect duplicate/no-op handling");
2472
+ }
2473
+ else {
2474
+ push("pass", "teacher_snapshot_loaded", `last async no-op reason is ${report.teacherLoop.lastNoOpReason}`, `latestFreshness=${report.teacherLoop.latestFreshness}; queue=${report.teacherLoop.queueDepth}/${report.teacherLoop.queueCapacity}`);
2475
+ }
2476
+ return findings;
2477
+ }
2478
+ function summarizeOperatorStatus(findings) {
2479
+ if (findings.some((finding) => finding.severity === "fail")) {
2480
+ return "fail";
2481
+ }
2482
+ if (findings.some((finding) => finding.severity === "warn")) {
2483
+ return "warn";
2484
+ }
2485
+ return "ok";
2486
+ }
2487
+ function yesNo(value) {
2488
+ if (value === null) {
2489
+ return "unknown";
2490
+ }
2491
+ return value ? "yes" : "no";
2492
+ }
2493
+ function formatList(values, empty = "none") {
2494
+ return values.length === 0 ? empty : values.join(",");
2495
+ }
2496
+ function formatCompactList(values, empty = "none", maxItems = 2, maxLength = 20) {
2497
+ if (values.length === 0) {
2498
+ return empty;
2499
+ }
2500
+ const visible = values.slice(0, maxItems).map((value) => formatCompactValue(value, empty, maxLength));
2501
+ return values.length > maxItems ? `${visible.join("|")}+${values.length - maxItems}more` : visible.join("|");
2502
+ }
2503
+ function formatCompactValue(value, empty = "none", maxLength = 24) {
2504
+ if (value === null || value === undefined || value.length === 0) {
2505
+ return empty;
2506
+ }
2507
+ return value.length <= maxLength ? value : `${value.slice(0, maxLength)}…`;
2508
+ }
2509
+ export function buildOperatorSurfaceReport(input) {
2510
+ const activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
2511
+ const updatedAt = normalizeIsoTimestamp(input.updatedAt, "updatedAt", new Date().toISOString());
2512
+ const inspection = inspectActivationState(activationRoot, updatedAt);
2513
+ const observability = describeActivationObservability(activationRoot, "active", {
2514
+ updatedAt
2515
+ });
2516
+ const attachStatus = describeAttachStatus({ activationRoot });
2517
+ const active = summarizeOperatorSlot(inspection.active, inspection.pointers.active?.updatedAt ?? null);
2518
+ const reportBase = {
2519
+ generatedAt: updatedAt,
2520
+ activationRoot,
2521
+ active,
2522
+ candidate: summarizeOperatorSlot(inspection.candidate, inspection.pointers.candidate?.updatedAt ?? null),
2523
+ previous: summarizeOperatorSlot(inspection.previous, inspection.pointers.previous?.updatedAt ?? null),
2524
+ freshness: {
2525
+ activeBehindPromotionReadyCandidate: observability.promotionFreshness.activeBehindPromotionReadyCandidate,
2526
+ candidateAheadBy: summarizeCandidateAheadBy(observability.promotionFreshness.candidateAheadBy)
2527
+ },
2528
+ brain: summarizeBrainState(active, observability),
2529
+ learnedRouting: {
2530
+ required: observability.learnedRouteFn.required,
2531
+ available: observability.learnedRouteFn.available,
2532
+ routerIdentity: observability.learnedRouteFn.routerIdentity,
2533
+ routeFnVersion: observability.learnedRouteFn.routeFnVersion,
2534
+ trainingMethod: observability.learnedRouteFn.trainingMethod,
2535
+ routerTrainedAt: observability.learnedRouteFn.routerTrainedAt,
2536
+ objective: observability.learnedRouteFn.objective,
2537
+ pgProfile: observability.learnedRouteFn.pgProfile,
2538
+ objectiveChecksum: observability.learnedRouteFn.objectiveChecksum,
2539
+ updateMechanism: observability.learnedRouteFn.updateMechanism,
2540
+ updateVersion: observability.learnedRouteFn.updateVersion,
2541
+ updateCount: observability.learnedRouteFn.updateCount,
2542
+ supervisionCount: observability.learnedRouteFn.supervisionCount,
2543
+ collectedLabelsTotal: observability.learnedRouteFn.collectedLabels?.total ?? null,
2544
+ freshnessChecksum: observability.learnedRouteFn.freshnessChecksum,
2545
+ handoffState: observability.initHandoff.handoffState,
2546
+ initMode: observability.initHandoff.initMode,
2547
+ seedStateVisible: observability.initHandoff.seedStateVisible
2548
+ },
2549
+ servePath: summarizeServePath(attachStatus.compile),
2550
+ promotion: {
2551
+ allowed: inspection.promotion.allowed,
2552
+ findings: [...inspection.promotion.findings],
2553
+ lastPromotion: summarizeLastPromotion(inspection),
2554
+ activeUpdatedAt: inspection.pointers.active?.updatedAt ?? null,
2555
+ candidateUpdatedAt: inspection.pointers.candidate?.updatedAt ?? null,
2556
+ previousUpdatedAt: inspection.pointers.previous?.updatedAt ?? null
2557
+ },
2558
+ rollback: {
2559
+ allowed: inspection.rollback.allowed,
2560
+ findings: [...inspection.rollback.findings],
2561
+ previousPackId: inspection.previous?.packId ?? inspection.pointers.previous?.packId ?? null,
2562
+ state: inspection.rollback.allowed ? "ready" : inspection.active === null ? "unknown" : "blocked"
2563
+ },
2564
+ supervision: summarizeSupervision(input),
2565
+ learning: summarizeAlwaysOnLearning(input),
2566
+ teacherLoop: summarizeTeacherLoop(input)
2567
+ };
2568
+ const findings = buildOperatorFindings(reportBase);
2569
+ return {
2570
+ ...reportBase,
2571
+ status: summarizeOperatorStatus(findings),
2572
+ findings
2573
+ };
2574
+ }
2575
+ function formatSlot(label, slot) {
2576
+ if (slot === null) {
2577
+ return `${label.padEnd(11)}pack=none ready=no`;
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"}`;
2580
+ }
2581
+ export function formatOperatorRollbackReport(result) {
2582
+ const header = result.allowed ? (result.dryRun ? "ROLLBACK ready" : "ROLLBACK ok") : "ROLLBACK blocked";
2583
+ return [
2584
+ header,
2585
+ `preview ${yesNo(result.dryRun)} activation=${result.activationRoot} updatedAt=${result.updatedAt}`,
2586
+ `before active=${result.before.activePackId ?? "none"} candidate=${result.before.candidatePackId ?? "none"} previous=${result.before.previousPackId ?? "none"}`,
2587
+ `after active=${result.after?.activePackId ?? "none"} candidate=${result.after?.candidatePackId ?? "none"} previous=${result.after?.previousPackId ?? "none"}`,
2588
+ `result restored=${result.restoredPackId ?? "none"} parkedCandidate=${result.parkedCandidatePackId ?? "none"}`,
2589
+ `findings ${formatList(result.findings)}`
2590
+ ].join("\n");
2591
+ }
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
+ /**
2681
+ * Describes the kernel/brain boundary for a single compile response.
2682
+ *
2683
+ * Combines:
2684
+ * - Brain context summary (from the compile response diagnostics)
2685
+ * - Kernel surface validation (if a surface descriptor is supplied)
2686
+ * - A coverage advisory based on routing signals
2687
+ *
2688
+ * See `docs/kernel-brain-boundary.md` for the full decision framework.
2689
+ */
2690
+ export function describeKernelBrainBoundary(compileResponse, surface) {
2691
+ const diag = compileResponse.diagnostics;
2692
+ // Collect the roles of selected blocks.
2693
+ const selectedRoles = [
2694
+ ...new Set(compileResponse.selectedContext
2695
+ .map((b) => b.source)
2696
+ .filter((s) => typeof s === "string" && s.length > 0))
2697
+ ];
2698
+ // Detect whether any block was compacted (compactedFrom set on the block).
2699
+ const compactionApplied = compileResponse.selectedContext.some((b) => Array.isArray(b.compactedFrom) && (b.compactedFrom?.length ?? 0) > 0);
2700
+ // Coverage advisory.
2701
+ // Token match evidence lives in diagnostics.notes as "selection_mode=token_match(...)"
2702
+ // or "selection_tiers=token_match_only" / "selection_tiers=token_match+priority_fallback".
2703
+ const notesStr = diag.notes.join(" ");
2704
+ const hasTokenMatches = notesStr.includes("selection_mode=token_match") ||
2705
+ notesStr.includes("selection_tiers=token_match");
2706
+ let brainCoverageAdvisory;
2707
+ if (diag.usedLearnedRouteFn && hasTokenMatches) {
2708
+ brainCoverageAdvisory = "likely_covered";
2709
+ }
2710
+ else if (hasTokenMatches || diag.usedLearnedRouteFn) {
2711
+ brainCoverageAdvisory = "partial";
2712
+ }
2713
+ else {
2714
+ brainCoverageAdvisory = "likely_gap";
2715
+ }
2716
+ const kernelValidation = surface !== undefined ? validateKernelSurface(surface) : null;
2717
+ return {
2718
+ brain: {
2719
+ packId: compileResponse.packId,
2720
+ mode: diag.modeEffective,
2721
+ selectedBlockCount: compileResponse.selectedContext.length,
2722
+ selectedRoles,
2723
+ usedLearnedRouteFn: diag.usedLearnedRouteFn,
2724
+ compactionApplied
2725
+ },
2726
+ kernelValidation,
2727
+ brainCoverageAdvisory
2728
+ };
2729
+ }
495
2730
  //# sourceMappingURL=index.js.map