@openclawbrain/cli 0.4.14 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
3
4
  import path from "node:path";
4
5
  import process from "node:process";
5
6
  import { compileRuntimeFromActivation } from "@openclawbrain/compiler";
@@ -27,6 +28,27 @@ export const RUNTIME_EVENT_EXPORT_BUNDLE_LAYOUT = {
27
28
  manifest: "manifest.json",
28
29
  payload: "normalized-event-export.json"
29
30
  };
31
+ const RECORDED_SESSION_REPLAY_PROOF_MANIFEST_CONTRACT = "recorded_session_replay_proof_manifest.v1";
32
+ const RECORDED_SESSION_REPLAY_PROOF_ENVIRONMENT_CONTRACT = "recorded_session_replay_environment.v1";
33
+ const RECORDED_SESSION_REPLAY_PROOF_SUMMARY_TABLES_CONTRACT = "recorded_session_replay_summary_tables.v1";
34
+ const RECORDED_SESSION_REPLAY_PROOF_COVERAGE_SNAPSHOT_CONTRACT = "recorded_session_replay_coverage_snapshot.v1";
35
+ const RECORDED_SESSION_REPLAY_PROOF_HARDENING_SNAPSHOT_CONTRACT = "recorded_session_replay_hardening_snapshot.v1";
36
+ const RECORDED_SESSION_REPLAY_PROOF_HASHES_CONTRACT = "recorded_session_replay_hashes.v1";
37
+ const RECORDED_SESSION_REPLAY_PROOF_VALIDATION_CONTRACT = "recorded_session_replay_proof_validation.v1";
38
+ const RECORDED_SESSION_REPLAY_MODE_ORDER = ["no_brain", "vector_only", "graph_prior_only", "learned_route"];
39
+ export const RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT = {
40
+ manifest: "manifest.json",
41
+ trace: "trace.json",
42
+ fixture: "fixture.json",
43
+ bundle: "bundle.json",
44
+ environment: "environment.json",
45
+ summary: "summary.md",
46
+ summaryTables: "summary-tables.json",
47
+ coverageSnapshot: "coverage-snapshot.json",
48
+ hardeningSnapshot: "hardening-snapshot.json",
49
+ hashes: "hashes.json",
50
+ modeDir: "modes"
51
+ };
30
52
  function normalizeRuntimeProfileSelector(value, fieldName, fallback = "current_profile") {
31
53
  if (value === undefined || value === null) {
32
54
  return fallback;
@@ -1058,6 +1080,24 @@ export function scanLiveEventExport(input) {
1058
1080
  function readJsonFile(filePath) {
1059
1081
  return JSON.parse(readFileSync(filePath, "utf8"));
1060
1082
  }
1083
+ function checksumTextPayload(value) {
1084
+ return `sha256-${createHash("sha256").update(value).digest("hex")}`;
1085
+ }
1086
+ function orderedRecordedSessionReplayModes(modes) {
1087
+ const byMode = new Map(modes.map((mode) => [mode.mode, mode]));
1088
+ return RECORDED_SESSION_REPLAY_MODE_ORDER
1089
+ .map((mode) => byMode.get(mode))
1090
+ .filter(isPresent);
1091
+ }
1092
+ function recordedSessionReplayModeOutputPath(mode) {
1093
+ return path.posix.join(RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.modeDir, `${mode}.json`);
1094
+ }
1095
+ function resolveRecordedSessionReplayProofPath(rootDir, relativePath, fieldName) {
1096
+ return resolveBundlePayloadPath(rootDir, normalizeNonEmptyString(relativePath, fieldName));
1097
+ }
1098
+ function readRecordedSessionReplayProofText(rootDir, relativePath, fieldName) {
1099
+ return readFileSync(resolveRecordedSessionReplayProofPath(rootDir, relativePath, fieldName), "utf8");
1100
+ }
1061
1101
  export function resolveAsyncTeacherLiveLoopSnapshotPath(activationRoot) {
1062
1102
  return path.join(path.resolve(normalizeNonEmptyString(activationRoot, "activationRoot")), "async-teacher-live-loop.snapshot.json");
1063
1103
  }
@@ -2340,8 +2380,61 @@ function normalizeIsoTimestamp(value, fieldName, fallbackValue) {
2340
2380
  }
2341
2381
  return new Date(candidate).toISOString();
2342
2382
  }
2343
- function normalizeMode(value) {
2344
- return value ?? "heuristic";
2383
+ const RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG = {
2384
+ vector_only: {
2385
+ routeMode: "heuristic",
2386
+ selectionMode: "flat_rank_v1"
2387
+ },
2388
+ graph_prior_only: {
2389
+ routeMode: "heuristic",
2390
+ selectionMode: "graph_walk_v1"
2391
+ },
2392
+ learned_route: {
2393
+ routeMode: "learned",
2394
+ selectionMode: "graph_walk_v1"
2395
+ }
2396
+ };
2397
+ function resolveRuntimeComparativeReplayMode(value) {
2398
+ return Object.prototype.hasOwnProperty.call(RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG, value)
2399
+ ? value
2400
+ : null;
2401
+ }
2402
+ function resolveCompileModePlan(modeValue, selectionModeValue) {
2403
+ const requestedSelectionMode = normalizeCompileSelectionMode(selectionModeValue);
2404
+ const comparativeMode = resolveRuntimeComparativeReplayMode(modeValue);
2405
+ if (comparativeMode === null) {
2406
+ if (modeValue === undefined) {
2407
+ return {
2408
+ comparativeMode: null,
2409
+ routeMode: "heuristic",
2410
+ selectionMode: requestedSelectionMode
2411
+ };
2412
+ }
2413
+ if (modeValue === "heuristic" || modeValue === "learned") {
2414
+ return {
2415
+ comparativeMode: null,
2416
+ routeMode: modeValue,
2417
+ selectionMode: requestedSelectionMode
2418
+ };
2419
+ }
2420
+ throw new Error("mode must be heuristic, learned, vector_only, graph_prior_only, or learned_route");
2421
+ }
2422
+ const plan = RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG[comparativeMode];
2423
+ if (requestedSelectionMode !== undefined && requestedSelectionMode !== plan.selectionMode) {
2424
+ throw new Error(`selectionMode ${requestedSelectionMode} conflicts with comparative mode ${comparativeMode}, expected ${plan.selectionMode}`);
2425
+ }
2426
+ return {
2427
+ comparativeMode,
2428
+ routeMode: plan.routeMode,
2429
+ selectionMode: plan.selectionMode
2430
+ };
2431
+ }
2432
+ function resolveSyntheticTurnMode(value) {
2433
+ const comparativeMode = resolveRuntimeComparativeReplayMode(value);
2434
+ if (comparativeMode !== null) {
2435
+ return RUNTIME_COMPARATIVE_REPLAY_MODE_CONFIG[comparativeMode].routeMode;
2436
+ }
2437
+ return value === "heuristic" || value === "learned" ? value : undefined;
2345
2438
  }
2346
2439
  function normalizeCompileSelectionMode(value) {
2347
2440
  if (value === undefined) {
@@ -2681,8 +2774,9 @@ function appendCompileServeRouteDecisionLog(input) {
2681
2774
  if (input.compileInput.budgetStrategy === "fixed_v1" || input.compileInput.budgetStrategy === "empirical_v1") {
2682
2775
  syntheticTurn.budgetStrategy = input.compileInput.budgetStrategy;
2683
2776
  }
2684
- if (input.compileInput.mode === "heuristic" || input.compileInput.mode === "learned") {
2685
- syntheticTurn.mode = input.compileInput.mode;
2777
+ const syntheticTurnMode = resolveSyntheticTurnMode(input.compileInput.mode);
2778
+ if (syntheticTurnMode !== undefined) {
2779
+ syntheticTurn.mode = syntheticTurnMode;
2686
2780
  }
2687
2781
  if (input.compileInput.runtimeHints !== undefined) {
2688
2782
  syntheticTurn.runtimeHints = input.compileInput.runtimeHints;
@@ -2936,16 +3030,42 @@ export function resolveActivePackForCompile(activationRoot) {
2936
3030
  inspection: inspection.active
2937
3031
  };
2938
3032
  }
3033
+ function normalizeFrozenReplayEvalIdentity(value) {
3034
+ if (value === undefined || value === null) {
3035
+ return null;
3036
+ }
3037
+ if (typeof value !== "object") {
3038
+ throw new Error("_frozenReplayEvalIdentity must be an object");
3039
+ }
3040
+ const frozenIdentity = value;
3041
+ return {
3042
+ packId: normalizeNonEmptyString(frozenIdentity.packId, "_frozenReplayEvalIdentity.packId"),
3043
+ routerIdentity: normalizeOptionalString(frozenIdentity.routerIdentity) ?? null
3044
+ };
3045
+ }
3046
+ function validateFrozenReplayEvalIdentity(target, frozenReplayEvalIdentity) {
3047
+ if (frozenReplayEvalIdentity === null) {
3048
+ return;
3049
+ }
3050
+ const actualRouterIdentity = target.inspection.routerIdentity ?? null;
3051
+ if (target.activePointer.packId === frozenReplayEvalIdentity.packId &&
3052
+ actualRouterIdentity === frozenReplayEvalIdentity.routerIdentity) {
3053
+ return;
3054
+ }
3055
+ throw new Error(`Frozen replay eval identity mismatch: expected pack ${frozenReplayEvalIdentity.packId} (routerIdentity=${frozenReplayEvalIdentity.routerIdentity ?? "null"}), received pack ${target.activePointer.packId} (routerIdentity=${actualRouterIdentity ?? "null"})`);
3056
+ }
2939
3057
  export function compileRuntimeContext(input) {
2940
3058
  const totalStartedAtNs = monotonicClockNs();
2941
3059
  const fallbackActivationRoot = resolveActivationRootForFailure(input.activationRoot);
2942
3060
  let activationRoot = fallbackActivationRoot;
2943
3061
  let agentId = process.env.OPENCLAWBRAIN_AGENT_ID ?? DEFAULT_AGENT_ID;
2944
3062
  let runtimeHints = [];
3063
+ let comparativeMode = null;
2945
3064
  let selectionMode;
2946
3065
  let userMessage = "";
2947
3066
  let maxContextChars;
2948
3067
  let mode = "heuristic";
3068
+ let frozenReplayEvalIdentity = null;
2949
3069
  let routeSelectionStartedAtNs = null;
2950
3070
  let routeSelectionMs = null;
2951
3071
  let promptAssemblyStartedAtNs = null;
@@ -2955,13 +3075,16 @@ export function compileRuntimeContext(input) {
2955
3075
  activationRoot = path.resolve(normalizeNonEmptyString(input.activationRoot, "activationRoot"));
2956
3076
  agentId = normalizeOptionalString(input.agentId) ?? process.env.OPENCLAWBRAIN_AGENT_ID ?? DEFAULT_AGENT_ID;
2957
3077
  runtimeHints = normalizeRuntimeHints(input.runtimeHints);
2958
- selectionMode = normalizeCompileSelectionMode(input.selectionMode);
2959
3078
  userMessage = normalizeNonEmptyString(input.message, "message");
2960
3079
  maxContextChars =
2961
3080
  input.maxContextChars !== undefined
2962
3081
  ? normalizeNonNegativeInteger(input.maxContextChars, "maxContextChars", input.maxContextChars)
2963
3082
  : undefined;
2964
- mode = normalizeMode(input.mode);
3083
+ const compileModePlan = resolveCompileModePlan(input.mode, input.selectionMode);
3084
+ comparativeMode = compileModePlan.comparativeMode;
3085
+ mode = compileModePlan.routeMode;
3086
+ selectionMode = compileModePlan.selectionMode;
3087
+ frozenReplayEvalIdentity = normalizeFrozenReplayEvalIdentity(input._frozenReplayEvalIdentity);
2965
3088
  }
2966
3089
  catch (error) {
2967
3090
  result = failOpenCompileResult(error, fallbackActivationRoot, buildBrainServeHotPathTiming({
@@ -2979,6 +3102,7 @@ export function compileRuntimeContext(input) {
2979
3102
  }
2980
3103
  try {
2981
3104
  const target = resolveActivePackForCompile(activationRoot);
3105
+ validateFrozenReplayEvalIdentity(target, frozenReplayEvalIdentity);
2982
3106
  const resolvedBudget = resolveCompileBudget(target, input);
2983
3107
  routeSelectionStartedAtNs = monotonicClockNs();
2984
3108
  const compile = compileRuntimeFromActivation(activationRoot, {
@@ -2995,11 +3119,29 @@ export function compileRuntimeContext(input) {
2995
3119
  ...(selectionMode !== undefined ? { selectionMode } : {})
2996
3120
  });
2997
3121
  routeSelectionMs = elapsedMsFrom(routeSelectionStartedAtNs);
3122
+ const selectionEngine = selectionMode ?? "flat_rank_v1";
2998
3123
  const compileResponse = {
2999
3124
  ...compile.response,
3000
3125
  diagnostics: {
3001
3126
  ...compile.response.diagnostics,
3002
- notes: uniqueNotes([...compile.response.diagnostics.notes, ...resolvedBudget.notes, "OpenClaw remains the runtime owner"])
3127
+ notes: uniqueNotes([
3128
+ ...compile.response.diagnostics.notes,
3129
+ ...resolvedBudget.notes,
3130
+ `selection_engine=${selectionEngine}`,
3131
+ ...(comparativeMode === null
3132
+ ? []
3133
+ : [
3134
+ `comparative_mode=${comparativeMode}`,
3135
+ `comparative_mode_plan=${mode}+${selectionEngine}`
3136
+ ]),
3137
+ ...(frozenReplayEvalIdentity === null
3138
+ ? []
3139
+ : [
3140
+ `replay_eval_pack_id=${frozenReplayEvalIdentity.packId}`,
3141
+ `replay_eval_router_identity=${frozenReplayEvalIdentity.routerIdentity ?? "null"}`
3142
+ ]),
3143
+ "OpenClaw remains the runtime owner"
3144
+ ])
3003
3145
  }
3004
3146
  };
3005
3147
  promptAssemblyStartedAtNs = monotonicClockNs();
@@ -3882,6 +4024,7 @@ export function runRuntimeTurn(turn, options = {}) {
3882
4024
  ...(turn.mode !== undefined ? { mode: turn.mode } : {}),
3883
4025
  ...(turn.selectionMode !== undefined ? { selectionMode: turn.selectionMode } : {}),
3884
4026
  ...(turn.runtimeHints !== undefined ? { runtimeHints: turn.runtimeHints } : {}),
4027
+ ...(options._frozenReplayEvalIdentity !== undefined ? { _frozenReplayEvalIdentity: options._frozenReplayEvalIdentity } : {}),
3885
4028
  _suppressServeLog: true
3886
4029
  };
3887
4030
  const compileResult = compileRuntimeContext(compileInput);
@@ -4130,6 +4273,7 @@ function ensureRecordedSessionTrace(trace) {
4130
4273
  if (trace.turns.length === 0) {
4131
4274
  throw new Error("recorded session trace requires at least one turn");
4132
4275
  }
4276
+ resolveRecordedSessionReplayEvalTurnCount(trace.turns.length, trace.evalTurnCount, "evalTurnCount");
4133
4277
  for (const [index, cue] of trace.seedCues.entries()) {
4134
4278
  normalizeNonEmptyString(cue.cueId, `seedCues[${index}].cueId`);
4135
4279
  normalizeIsoTimestamp(cue.createdAt, `seedCues[${index}].createdAt`);
@@ -4181,6 +4325,52 @@ function uniqueStringsInOrder(values) {
4181
4325
  }
4182
4326
  return unique;
4183
4327
  }
4328
+ function resolveRecordedSessionReplayEvalTurnCount(turnCount, value, fieldName) {
4329
+ if (turnCount === 0) {
4330
+ return 0;
4331
+ }
4332
+ if (value === undefined) {
4333
+ return 1;
4334
+ }
4335
+ const normalized = normalizeNonNegativeInteger(value, fieldName, value);
4336
+ if (normalized < 1) {
4337
+ throw new Error(`${fieldName} must be at least 1 when turns are present`);
4338
+ }
4339
+ if (normalized > turnCount) {
4340
+ throw new Error(`${fieldName} cannot exceed turns.length (${turnCount})`);
4341
+ }
4342
+ return normalized;
4343
+ }
4344
+ function buildRecordedSessionReplayTurnPlan(fixture) {
4345
+ const evalTurnCount = resolveRecordedSessionReplayEvalTurnCount(fixture.turns.length, fixture.evalTurnCount, "evalTurnCount");
4346
+ const trainTurnCount = Math.max(0, fixture.turns.length - evalTurnCount);
4347
+ return {
4348
+ trainTurns: fixture.turns.slice(0, trainTurnCount),
4349
+ evalTurns: fixture.turns.slice(trainTurnCount),
4350
+ trainTurnCount,
4351
+ evalTurnCount
4352
+ };
4353
+ }
4354
+ function cloneRecordedSessionReplayFrozenEvalIdentity(value) {
4355
+ if (value === null) {
4356
+ return null;
4357
+ }
4358
+ return {
4359
+ packId: value.packId,
4360
+ routerIdentity: value.routerIdentity
4361
+ };
4362
+ }
4363
+ function readRecordedSessionReplayFrozenEvalIdentity(activationRoot) {
4364
+ const inspection = inspectActivationState(activationRoot);
4365
+ const activePointer = inspection.pointers.active;
4366
+ if (inspection.active === null || activePointer === null) {
4367
+ return null;
4368
+ }
4369
+ return {
4370
+ packId: activePointer.packId,
4371
+ routerIdentity: inspection.active.routerIdentity ?? null
4372
+ };
4373
+ }
4184
4374
  function buildRecordedSessionSeedExport(trace) {
4185
4375
  const agentId = normalizeOptionalString(trace.agentId) ?? DEFAULT_AGENT_ID;
4186
4376
  const seedSessionId = `${trace.sessionId}-seed`;
@@ -4292,6 +4482,9 @@ function recordedSessionFixtureBase(trace) {
4292
4482
  revision: trace.workspace.revision,
4293
4483
  ...(trace.workspace.labels !== undefined ? { labels: [...trace.workspace.labels] } : {})
4294
4484
  },
4485
+ ...(trace.evalTurnCount !== undefined
4486
+ ? { evalTurnCount: resolveRecordedSessionReplayEvalTurnCount(trace.turns.length, trace.evalTurnCount, "evalTurnCount") }
4487
+ : {}),
4295
4488
  seedBuiltAt: trace.seedBuiltAt,
4296
4489
  seedActivatedAt: trace.seedActivatedAt,
4297
4490
  seedExport: buildRecordedSessionSeedExport(trace),
@@ -4327,6 +4520,9 @@ function recordedSessionReplayFixtureBase(fixture) {
4327
4520
  revision: fixture.workspace.revision,
4328
4521
  ...(fixture.workspace.labels !== undefined ? { labels: [...fixture.workspace.labels] } : {})
4329
4522
  },
4523
+ ...(fixture.evalTurnCount !== undefined
4524
+ ? { evalTurnCount: resolveRecordedSessionReplayEvalTurnCount(fixture.turns.length, fixture.evalTurnCount, "evalTurnCount") }
4525
+ : {}),
4330
4526
  seedBuiltAt: fixture.seedBuiltAt,
4331
4527
  seedActivatedAt: fixture.seedActivatedAt,
4332
4528
  seedExport: fixture.seedExport,
@@ -4377,7 +4573,49 @@ function buildRecordedSessionTurnObservability(result) {
4377
4573
  freshestCreatedAt: observability.teacherFreshness.freshestCreatedAt ?? freshestSource?.freshestCreatedAt ?? null
4378
4574
  };
4379
4575
  }
4380
- function buildRecordedSessionTurnReport(turnFixture, result, options) {
4576
+ const RECORDED_SESSION_REPLAY_MODE_PLAN = {
4577
+ no_brain: {
4578
+ activationStrategy: "no_brain",
4579
+ runtimeMode: null,
4580
+ routeModeRequested: null,
4581
+ selectionEngine: null,
4582
+ learnedRouting: false
4583
+ },
4584
+ vector_only: {
4585
+ activationStrategy: "seed_pack",
4586
+ runtimeMode: "vector_only",
4587
+ routeModeRequested: "heuristic",
4588
+ selectionEngine: "flat_rank_v1",
4589
+ learnedRouting: false
4590
+ },
4591
+ graph_prior_only: {
4592
+ activationStrategy: "seed_pack",
4593
+ runtimeMode: "graph_prior_only",
4594
+ routeModeRequested: "heuristic",
4595
+ selectionEngine: "graph_walk_v1",
4596
+ learnedRouting: false
4597
+ },
4598
+ learned_route: {
4599
+ activationStrategy: "continuous_learned_loop",
4600
+ runtimeMode: "learned_route",
4601
+ routeModeRequested: "learned",
4602
+ selectionEngine: "graph_walk_v1",
4603
+ learnedRouting: true
4604
+ }
4605
+ };
4606
+ function recordedSessionReplayModePlan(mode) {
4607
+ return RECORDED_SESSION_REPLAY_MODE_PLAN[mode];
4608
+ }
4609
+ function buildRecordedSessionReplayTurnInput(turnFixture, modeRoot, replayMode) {
4610
+ const plan = recordedSessionReplayModePlan(replayMode);
4611
+ return {
4612
+ ...turnFixture.turn,
4613
+ ...(plan.runtimeMode === null ? {} : { mode: plan.runtimeMode }),
4614
+ export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
4615
+ };
4616
+ }
4617
+ function buildRecordedSessionTurnReport(replayMode, turnFixture, result, options) {
4618
+ const plan = recordedSessionReplayModePlan(replayMode);
4381
4619
  const compileOk = result.ok;
4382
4620
  const selectedContextTexts = compileOk ? result.compileResponse.selectedContext.map((block) => block.text) : [];
4383
4621
  const selectedContextIds = compileOk ? result.compileResponse.selectedContext.map((block) => block.id) : [];
@@ -4390,10 +4628,15 @@ function buildRecordedSessionTurnReport(turnFixture, result, options) {
4390
4628
  const eventExportDigest = result.eventExport.ok === true ? result.eventExport.normalizedEventExport.provenance.exportDigest : null;
4391
4629
  return {
4392
4630
  turnId: turnFixture.turnId,
4631
+ replayMode,
4632
+ phase: options.phase,
4393
4633
  compileOk,
4394
4634
  fallbackToStaticContext: result.fallbackToStaticContext,
4395
4635
  hardRequirementViolated: result.hardRequirementViolated,
4396
4636
  activePackId: result.ok ? result.activePackId : null,
4637
+ modeRequested: plan.routeModeRequested,
4638
+ modeEffective: result.ok ? result.compileResponse.diagnostics.modeEffective : null,
4639
+ selectionEngine: plan.selectionEngine,
4397
4640
  usedLearnedRouteFn: result.ok ? result.compileResponse.diagnostics.usedLearnedRouteFn : false,
4398
4641
  routerIdentity: result.ok ? result.compileResponse.diagnostics.routerIdentity : null,
4399
4642
  selectionDigest: result.ok ? result.compileResponse.diagnostics.selectionDigest : null,
@@ -4447,7 +4690,7 @@ function buildRecordedSessionReplayScannerWarnings(mode, turns, activePackChange
4447
4690
  if (turns.some((turn) => turn.compileOk) && selectionDigestTurnCount === 0) {
4448
4691
  warnings.push("selection_digest_missing");
4449
4692
  }
4450
- if (mode === "learned_replay" && activePackChangeCount === 0) {
4693
+ if (mode === "learned_route" && activePackChangeCount === 0) {
4451
4694
  warnings.push("active_pack_never_moved");
4452
4695
  }
4453
4696
  return warnings;
@@ -4479,17 +4722,33 @@ function buildRecordedSessionReplayScannerEvidence(mode, turns) {
4479
4722
  warnings: buildRecordedSessionReplayScannerWarnings(mode, turns, activePackChangeCount)
4480
4723
  };
4481
4724
  }
4482
- function buildRecordedSessionReplayModeSummary(mode, turns) {
4725
+ function buildRecordedSessionReplayModeQualityScore(turns, compileOkCount, phraseHitCount, phraseCount) {
4726
+ if (turns.length === 0) {
4727
+ return 0;
4728
+ }
4729
+ // Score mode quality from aggregate replay coverage so multi-phrase turns
4730
+ // keep their full weight instead of being flattened into a per-turn average.
4731
+ const compileScore = (compileOkCount / turns.length) * 40;
4732
+ const phraseScore = phraseCount === 0 ? 60 : (phraseHitCount / phraseCount) * 60;
4733
+ return Math.min(100, Math.round(compileScore + phraseScore));
4734
+ }
4735
+ function buildRecordedSessionReplayModeSummary(mode, turns, options = {}) {
4736
+ const plan = recordedSessionReplayModePlan(mode);
4483
4737
  const compileOkCount = turns.filter((turn) => turn.compileOk).length;
4484
4738
  const phraseHitCount = turns.reduce((sum, turn) => sum + turn.phraseHits.length, 0);
4485
4739
  const phraseCount = turns.reduce((sum, turn) => sum + turn.expectedContextPhrases.length, 0);
4486
4740
  const usedLearnedRouteTurnCount = turns.filter((turn) => turn.usedLearnedRouteFn).length;
4487
4741
  const promotionCount = turns.filter((turn) => turn.promoted).length;
4488
- const qualityScore = turns.length === 0 ? 0 : Math.round(turns.reduce((sum, turn) => sum + turn.qualityScore, 0) / turns.length);
4742
+ const qualityScore = buildRecordedSessionReplayModeQualityScore(turns, compileOkCount, phraseHitCount, phraseCount);
4489
4743
  const packIds = uniqueStringsInOrder(turns.map((turn) => turn.activePackId).filter(isPresent));
4744
+ const trainTurnCount = turns.filter((turn) => turn.phase === "train").length;
4745
+ const evalTurnCount = turns.filter((turn) => turn.phase === "eval").length;
4490
4746
  const scannerEvidence = buildRecordedSessionReplayScannerEvidence(mode, turns);
4491
4747
  const base = {
4492
4748
  mode,
4749
+ activationStrategy: plan.activationStrategy,
4750
+ modeRequested: plan.routeModeRequested,
4751
+ selectionEngine: plan.selectionEngine,
4493
4752
  qualityScore,
4494
4753
  compileOkCount,
4495
4754
  phraseHitCount,
@@ -4497,6 +4756,10 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
4497
4756
  usedLearnedRouteTurnCount,
4498
4757
  promotionCount,
4499
4758
  packIds,
4759
+ trainTurnCount,
4760
+ evalTurnCount,
4761
+ frozenEvalPackId: options.frozenEvalIdentity?.packId ?? null,
4762
+ frozenEvalRouterIdentity: options.frozenEvalIdentity?.routerIdentity ?? null,
4500
4763
  scannerEvidence
4501
4764
  };
4502
4765
  return {
@@ -4505,10 +4768,15 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
4505
4768
  summary: base,
4506
4769
  turns: turns.map((turn) => ({
4507
4770
  turnId: turn.turnId,
4771
+ phase: turn.phase,
4508
4772
  qualityScore: turn.qualityScore,
4509
4773
  phraseHits: turn.phraseHits,
4510
4774
  missedPhrases: turn.missedPhrases,
4511
4775
  compileOk: turn.compileOk,
4776
+ replayMode: turn.replayMode,
4777
+ modeRequested: turn.modeRequested,
4778
+ modeEffective: turn.modeEffective,
4779
+ selectionEngine: turn.selectionEngine,
4512
4780
  usedLearnedRouteFn: turn.usedLearnedRouteFn,
4513
4781
  activePackId: turn.activePackId,
4514
4782
  selectionDigest: turn.selectionDigest,
@@ -4518,16 +4786,19 @@ function buildRecordedSessionReplayModeSummary(mode, turns) {
4518
4786
  })
4519
4787
  };
4520
4788
  }
4521
- function buildRecordedSessionReplayModeReport(mode, turns) {
4789
+ function buildRecordedSessionReplayModeReport(mode, turns, options = {}) {
4522
4790
  return {
4523
4791
  mode,
4524
- summary: buildRecordedSessionReplayModeSummary(mode, turns),
4792
+ summary: buildRecordedSessionReplayModeSummary(mode, turns, options),
4525
4793
  turns: [...turns]
4526
4794
  };
4527
4795
  }
4528
4796
  function buildRecordedSessionReplayScoreHash(modes) {
4529
4797
  return checksumJsonPayload(modes.map((mode) => ({
4530
4798
  mode: mode.mode,
4799
+ activationStrategy: mode.summary.activationStrategy,
4800
+ modeRequested: mode.summary.modeRequested,
4801
+ selectionEngine: mode.summary.selectionEngine,
4531
4802
  qualityScore: mode.summary.qualityScore,
4532
4803
  compileOkCount: mode.summary.compileOkCount,
4533
4804
  phraseHitCount: mode.summary.phraseHitCount,
@@ -4535,6 +4806,10 @@ function buildRecordedSessionReplayScoreHash(modes) {
4535
4806
  usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
4536
4807
  promotionCount: mode.summary.promotionCount,
4537
4808
  packIds: mode.summary.packIds,
4809
+ trainTurnCount: mode.summary.trainTurnCount,
4810
+ evalTurnCount: mode.summary.evalTurnCount,
4811
+ frozenEvalPackId: mode.summary.frozenEvalPackId,
4812
+ frozenEvalRouterIdentity: mode.summary.frozenEvalRouterIdentity,
4538
4813
  scannerEvidence: mode.summary.scannerEvidence,
4539
4814
  scoreHash: mode.summary.scoreHash
4540
4815
  })));
@@ -4625,45 +4900,42 @@ function runRecordedSessionNoBrainMode(rootDir, fixture) {
4625
4900
  const modeRoot = prepareReplayModeRoot(rootDir, "no_brain");
4626
4901
  const activationRoot = path.join(modeRoot, "activation");
4627
4902
  const turns = fixture.turns.map((turnFixture) => {
4628
- const result = runRuntimeTurn({
4629
- ...turnFixture.turn,
4630
- export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
4631
- }, {
4903
+ const result = runRuntimeTurn(buildRecordedSessionReplayTurnInput(turnFixture, modeRoot, "no_brain"), {
4632
4904
  activationRoot,
4633
4905
  failOpen: true
4634
4906
  });
4635
- return buildRecordedSessionTurnReport(turnFixture, result, {
4907
+ return buildRecordedSessionTurnReport("no_brain", turnFixture, result, {
4908
+ phase: "eval",
4636
4909
  compileActiveVersion: null,
4637
4910
  promoted: false
4638
4911
  });
4639
4912
  });
4640
4913
  return buildRecordedSessionReplayModeReport("no_brain", turns);
4641
4914
  }
4642
- function runRecordedSessionSeedPackMode(rootDir, fixture) {
4643
- const modeRoot = prepareReplayModeRoot(rootDir, "seed_pack");
4915
+ function runRecordedSessionSeededComparativeMode(rootDir, fixture, replayMode) {
4916
+ const modeRoot = prepareReplayModeRoot(rootDir, replayMode);
4644
4917
  const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
4645
4918
  const turns = fixture.turns.map((turnFixture) => {
4646
- const result = runRuntimeTurn({
4647
- ...turnFixture.turn,
4648
- export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
4649
- }, {
4919
+ const result = runRuntimeTurn(buildRecordedSessionReplayTurnInput(turnFixture, modeRoot, replayMode), {
4650
4920
  activationRoot,
4651
4921
  failOpen: false
4652
4922
  });
4653
- return buildRecordedSessionTurnReport(turnFixture, result, {
4923
+ return buildRecordedSessionTurnReport(replayMode, turnFixture, result, {
4924
+ phase: "eval",
4654
4925
  compileActiveVersion: 1,
4655
4926
  promoted: false
4656
4927
  });
4657
4928
  });
4658
- return buildRecordedSessionReplayModeReport("seed_pack", turns);
4929
+ return buildRecordedSessionReplayModeReport(replayMode, turns);
4659
4930
  }
4660
- function runRecordedSessionLearnedReplayMode(rootDir, fixture) {
4661
- const modeRoot = prepareReplayModeRoot(rootDir, "learned_replay");
4931
+ function runRecordedSessionLearnedRouteMode(rootDir, fixture) {
4932
+ const modeRoot = prepareReplayModeRoot(rootDir, "learned_route");
4662
4933
  const { activationRoot } = prepareSeedActivation(modeRoot, fixture);
4663
4934
  const loopRoot = path.join(modeRoot, "loop");
4935
+ const turnPlan = buildRecordedSessionReplayTurnPlan(fixture);
4664
4936
  let state;
4665
4937
  const turns = [];
4666
- for (const turnFixture of fixture.turns) {
4938
+ for (const turnFixture of turnPlan.trainTurns) {
4667
4939
  const compileCreatedAt = normalizeIsoTimestamp(turnFixture.turn.compile?.createdAt, "turn.compile.createdAt", turnFixture.turn.createdAt);
4668
4940
  const result = runContinuousProductLoopTurn({
4669
4941
  activationRoot,
@@ -4682,12 +4954,40 @@ function runRecordedSessionLearnedReplayMode(rootDir, fixture) {
4682
4954
  promoteUpdatedAt: addMinutes(compileCreatedAt, 4)
4683
4955
  });
4684
4956
  state = result.state;
4685
- turns.push(buildRecordedSessionTurnReport(turnFixture, result.turn, {
4957
+ turns.push(buildRecordedSessionTurnReport("learned_route", turnFixture, result.turn, {
4958
+ phase: "train",
4686
4959
  compileActiveVersion: result.compileActiveVersion,
4687
4960
  promoted: result.learning.promoted
4688
4961
  }));
4689
4962
  }
4690
- return buildRecordedSessionReplayModeReport("learned_replay", turns);
4963
+ const frozenEvalIdentity = readRecordedSessionReplayFrozenEvalIdentity(activationRoot);
4964
+ for (const turnFixture of turnPlan.evalTurns) {
4965
+ const currentState = cloneContinuousProductLoopState(state ??
4966
+ createContinuousProductLoopState({
4967
+ activationRoot,
4968
+ loopRoot
4969
+ }));
4970
+ currentState.activationRoot = activationRoot;
4971
+ currentState.loopRoot = loopRoot;
4972
+ const activeBeforeTurn = syncContinuousActivePack(currentState);
4973
+ state = cloneContinuousProductLoopState(currentState);
4974
+ const result = runRuntimeTurn({
4975
+ ...turnFixture.turn,
4976
+ export: buildRecordedSessionTurnExportRoot(modeRoot, turnFixture.turnId)
4977
+ }, {
4978
+ activationRoot,
4979
+ failOpen: false,
4980
+ ...(frozenEvalIdentity === null ? {} : { _frozenReplayEvalIdentity: frozenEvalIdentity })
4981
+ });
4982
+ turns.push(buildRecordedSessionTurnReport("learned_route", turnFixture, result, {
4983
+ phase: "eval",
4984
+ compileActiveVersion: activeBeforeTurn?.version ?? 0,
4985
+ promoted: false
4986
+ }));
4987
+ }
4988
+ return buildRecordedSessionReplayModeReport("learned_route", turns, {
4989
+ frozenEvalIdentity: cloneRecordedSessionReplayFrozenEvalIdentity(frozenEvalIdentity)
4990
+ });
4691
4991
  }
4692
4992
  export function runRecordedSessionReplay(rootDir, fixture) {
4693
4993
  const resolvedRoot = path.resolve(normalizeNonEmptyString(rootDir, "rootDir"));
@@ -4701,8 +5001,9 @@ export function runRecordedSessionReplay(rootDir, fixture) {
4701
5001
  }
4702
5002
  const modes = [
4703
5003
  runRecordedSessionNoBrainMode(resolvedRoot, fixture),
4704
- runRecordedSessionSeedPackMode(resolvedRoot, fixture),
4705
- runRecordedSessionLearnedReplayMode(resolvedRoot, fixture)
5004
+ runRecordedSessionSeededComparativeMode(resolvedRoot, fixture, "vector_only"),
5005
+ runRecordedSessionSeededComparativeMode(resolvedRoot, fixture, "graph_prior_only"),
5006
+ runRecordedSessionLearnedRouteMode(resolvedRoot, fixture)
4706
5007
  ];
4707
5008
  const ranking = modes
4708
5009
  .map((mode) => ({
@@ -4754,7 +5055,14 @@ function rescoreRecordedSessionReplayTurn(turn) {
4754
5055
  }
4755
5056
  function rescoreRecordedSessionReplayMode(mode) {
4756
5057
  const turns = mode.turns.map((turn) => rescoreRecordedSessionReplayTurn(turn));
4757
- return buildRecordedSessionReplayModeReport(mode.mode, turns);
5058
+ return buildRecordedSessionReplayModeReport(mode.mode, turns, {
5059
+ frozenEvalIdentity: mode.summary.frozenEvalPackId === null
5060
+ ? null
5061
+ : {
5062
+ packId: mode.summary.frozenEvalPackId,
5063
+ routerIdentity: mode.summary.frozenEvalRouterIdentity ?? null
5064
+ }
5065
+ });
4758
5066
  }
4759
5067
  export function rescoreRecordedSessionReplayBundle(bundle) {
4760
5068
  const modes = bundle.modes.map((mode) => rescoreRecordedSessionReplayMode(mode));
@@ -4775,6 +5083,558 @@ export function verifyRecordedSessionReplayBundleHashes(bundle) {
4775
5083
  scoreHashMatches: rescored.scoreHash === bundle.scoreHash
4776
5084
  };
4777
5085
  }
5086
+ function buildRecordedSessionReplayProofEnvironment() {
5087
+ return {
5088
+ contract: RECORDED_SESSION_REPLAY_PROOF_ENVIRONMENT_CONTRACT,
5089
+ runtimeOwner: "openclaw",
5090
+ generator: {
5091
+ packageName: "@openclawbrain/cli",
5092
+ entrypoint: "writeRecordedSessionReplayProofBundle",
5093
+ nodeVersion: process.version,
5094
+ platform: process.platform,
5095
+ arch: process.arch
5096
+ },
5097
+ determinism: {
5098
+ hashAlgorithm: "sha256",
5099
+ canonicalJson: true,
5100
+ modeOrder: [...RECORDED_SESSION_REPLAY_MODE_ORDER],
5101
+ scratchReplayRoot: "temporary_directory"
5102
+ }
5103
+ };
5104
+ }
5105
+ function toRecordedSessionReplayProofRate(numerator, denominator) {
5106
+ return denominator > 0 ? Number((numerator / denominator).toFixed(6)) : null;
5107
+ }
5108
+ function buildRecordedSessionReplayProofSummaryTables(bundle) {
5109
+ const orderedModes = orderedRecordedSessionReplayModes(bundle.modes);
5110
+ return {
5111
+ contract: RECORDED_SESSION_REPLAY_PROOF_SUMMARY_TABLES_CONTRACT,
5112
+ traceId: bundle.traceId,
5113
+ winnerMode: bundle.summary.winnerMode,
5114
+ ranking: bundle.summary.ranking.map((entry) => ({ ...entry })),
5115
+ modes: orderedModes.map((mode) => ({
5116
+ mode: mode.mode,
5117
+ turnCount: mode.turns.length,
5118
+ qualityScore: mode.summary.qualityScore,
5119
+ compileOkCount: mode.summary.compileOkCount,
5120
+ phraseHitCount: mode.summary.phraseHitCount,
5121
+ phraseCount: mode.summary.phraseCount,
5122
+ usedLearnedRouteTurnCount: mode.summary.usedLearnedRouteTurnCount,
5123
+ promotionCount: mode.summary.promotionCount,
5124
+ exportTurnCount: mode.summary.scannerEvidence.exportTurnCount,
5125
+ humanLabelCount: mode.summary.scannerEvidence.humanLabelCount,
5126
+ attributedTurnCount: mode.summary.scannerEvidence.attributedTurnCount,
5127
+ activePackChangeCount: mode.summary.scannerEvidence.activePackChangeCount,
5128
+ warningCount: mode.summary.scannerEvidence.warnings.length,
5129
+ scoreHash: mode.summary.scoreHash
5130
+ })),
5131
+ turns: orderedModes.flatMap((mode) => mode.turns.map((turn) => ({
5132
+ mode: mode.mode,
5133
+ turnId: turn.turnId,
5134
+ qualityScore: turn.qualityScore,
5135
+ compileOk: turn.compileOk,
5136
+ phraseHitCount: turn.phraseHits.length,
5137
+ phraseCount: turn.expectedContextPhrases.length,
5138
+ usedLearnedRouteFn: turn.usedLearnedRouteFn,
5139
+ promoted: turn.promoted,
5140
+ activePackId: turn.activePackId,
5141
+ selectionDigest: turn.selectionDigest,
5142
+ eventExportDigest: turn.eventExportDigest,
5143
+ warningCount: turn.warnings.length
5144
+ })))
5145
+ };
5146
+ }
5147
+ function buildRecordedSessionReplayProofCoverageSnapshot(bundle) {
5148
+ const orderedModes = orderedRecordedSessionReplayModes(bundle.modes);
5149
+ const totalTurns = orderedModes.reduce((sum, mode) => sum + mode.turns.length, 0);
5150
+ const compileOkTurnCount = orderedModes.reduce((sum, mode) => sum + mode.summary.compileOkCount, 0);
5151
+ const phraseHitCount = orderedModes.reduce((sum, mode) => sum + mode.summary.phraseHitCount, 0);
5152
+ const phraseCount = orderedModes.reduce((sum, mode) => sum + mode.summary.phraseCount, 0);
5153
+ return {
5154
+ contract: RECORDED_SESSION_REPLAY_PROOF_COVERAGE_SNAPSHOT_CONTRACT,
5155
+ traceId: bundle.traceId,
5156
+ winnerMode: bundle.summary.winnerMode,
5157
+ totalTurns,
5158
+ compileOkTurnCount,
5159
+ compileOkRate: toRecordedSessionReplayProofRate(compileOkTurnCount, totalTurns),
5160
+ phraseHitCount,
5161
+ phraseCount,
5162
+ phraseHitRate: toRecordedSessionReplayProofRate(phraseHitCount, phraseCount),
5163
+ modes: orderedModes.map((mode) => ({
5164
+ mode: mode.mode,
5165
+ turnCount: mode.turns.length,
5166
+ compileOkRate: toRecordedSessionReplayProofRate(mode.summary.compileOkCount, mode.turns.length),
5167
+ phraseHitRate: toRecordedSessionReplayProofRate(mode.summary.phraseHitCount, mode.summary.phraseCount),
5168
+ learnedRouteTurnRate: toRecordedSessionReplayProofRate(mode.summary.usedLearnedRouteTurnCount, mode.turns.length),
5169
+ attributedTurnRate: toRecordedSessionReplayProofRate(mode.summary.scannerEvidence.attributedTurnCount, mode.turns.length)
5170
+ }))
5171
+ };
5172
+ }
5173
+ function buildRecordedSessionReplayProofHardeningSnapshot(bundle) {
5174
+ const orderedModes = orderedRecordedSessionReplayModes(bundle.modes);
5175
+ const totalTurns = orderedModes.reduce((sum, mode) => sum + mode.turns.length, 0);
5176
+ const compileFailureCount = orderedModes.reduce((sum, mode) => sum + (mode.turns.length - mode.summary.compileOkCount), 0);
5177
+ const warningCount = orderedModes.reduce((sum, mode) => sum + mode.summary.scannerEvidence.warnings.length, 0);
5178
+ const promotionCount = orderedModes.reduce((sum, mode) => sum + mode.summary.promotionCount, 0);
5179
+ const exportTurnCount = orderedModes.reduce((sum, mode) => sum + mode.summary.scannerEvidence.exportTurnCount, 0);
5180
+ const attributedTurnCount = orderedModes.reduce((sum, mode) => sum + mode.summary.scannerEvidence.attributedTurnCount, 0);
5181
+ return {
5182
+ contract: RECORDED_SESSION_REPLAY_PROOF_HARDENING_SNAPSHOT_CONTRACT,
5183
+ traceId: bundle.traceId,
5184
+ totalTurns,
5185
+ compileFailureCount,
5186
+ compileFailureRate: toRecordedSessionReplayProofRate(compileFailureCount, totalTurns),
5187
+ warningCount,
5188
+ promotionCount,
5189
+ exportTurnCount,
5190
+ attributedTurnCount,
5191
+ modes: orderedModes.map((mode) => ({
5192
+ mode: mode.mode,
5193
+ warningCount: mode.summary.scannerEvidence.warnings.length,
5194
+ compileFailureCount: mode.turns.length - mode.summary.compileOkCount,
5195
+ promotionCount: mode.summary.promotionCount,
5196
+ exportTurnCount: mode.summary.scannerEvidence.exportTurnCount,
5197
+ attributedTurnCount: mode.summary.scannerEvidence.attributedTurnCount
5198
+ }))
5199
+ };
5200
+ }
5201
+ function buildRecordedSessionReplayProofSummary(input) {
5202
+ const rankingRows = input.summaryTables.ranking.map((entry, index) => `| ${index + 1} | ${entry.mode} | ${entry.qualityScore} |`);
5203
+ const modeRows = input.summaryTables.modes.map((mode) => `| ${mode.mode} | ${mode.turnCount} | ${mode.compileOkCount} | ${mode.phraseHitCount}/${mode.phraseCount} | ${mode.usedLearnedRouteTurnCount} | ${mode.promotionCount} | ${mode.exportTurnCount} | ${mode.humanLabelCount} | ${mode.warningCount} | ${mode.scoreHash} |`);
5204
+ const turnRows = input.summaryTables.turns.map((turn) => `| ${turn.mode} | ${turn.turnId} | ${turn.qualityScore} | ${turn.compileOk ? "yes" : "no"} | ${turn.phraseHitCount}/${turn.phraseCount} | ${turn.usedLearnedRouteFn ? "yes" : "no"} | ${turn.promoted ? "yes" : "no"} | ${turn.activePackId ?? "none"} | ${turn.selectionDigest ?? "none"} |`);
5205
+ const coverageRows = input.coverageSnapshot.modes.map((mode) => `| ${mode.mode} | ${mode.turnCount} | ${mode.compileOkRate ?? "none"} | ${mode.phraseHitRate ?? "none"} | ${mode.learnedRouteTurnRate ?? "none"} | ${mode.attributedTurnRate ?? "none"} |`);
5206
+ const hardeningRows = input.hardeningSnapshot.modes.map((mode) => `| ${mode.mode} | ${mode.warningCount} | ${mode.compileFailureCount} | ${mode.promotionCount} | ${mode.exportTurnCount} | ${mode.attributedTurnCount} |`);
5207
+ return [
5208
+ "# Recorded Session Replay Proof Bundle",
5209
+ "",
5210
+ `- trace id: \`${input.bundle.traceId}\``,
5211
+ `- winner mode: \`${input.bundle.summary.winnerMode ?? "none"}\``,
5212
+ `- trace hash: \`${input.semanticHashes.traceHash}\``,
5213
+ `- fixture hash: \`${input.semanticHashes.fixtureHash}\``,
5214
+ `- score hash: \`${input.semanticHashes.scoreHash}\``,
5215
+ `- bundle hash: \`${input.semanticHashes.bundleHash}\``,
5216
+ "",
5217
+ "## Ranking",
5218
+ "| rank | mode | quality score |",
5219
+ "| --- | --- | ---: |",
5220
+ ...(rankingRows.length === 0 ? ["| - | none | 0 |"] : rankingRows),
5221
+ "",
5222
+ "## Coverage Snapshot",
5223
+ `- compile ok turns: ${input.coverageSnapshot.compileOkTurnCount}/${input.coverageSnapshot.totalTurns}`,
5224
+ `- compile ok rate: ${input.coverageSnapshot.compileOkRate ?? "none"}`,
5225
+ `- phrase hits: ${input.coverageSnapshot.phraseHitCount}/${input.coverageSnapshot.phraseCount}`,
5226
+ `- phrase hit rate: ${input.coverageSnapshot.phraseHitRate ?? "none"}`,
5227
+ "",
5228
+ "| mode | turns | compile ok rate | phrase hit rate | learned route turn rate | attributed turn rate |",
5229
+ "| --- | ---: | ---: | ---: | ---: | ---: |",
5230
+ ...(coverageRows.length === 0 ? ["| - | 0 | none | none | none | none |"] : coverageRows),
5231
+ "",
5232
+ "## Hardening Snapshot",
5233
+ `- compile failures: ${input.hardeningSnapshot.compileFailureCount}/${input.hardeningSnapshot.totalTurns}`,
5234
+ `- compile failure rate: ${input.hardeningSnapshot.compileFailureRate ?? "none"}`,
5235
+ `- warnings: ${input.hardeningSnapshot.warningCount}`,
5236
+ `- promotions: ${input.hardeningSnapshot.promotionCount}`,
5237
+ "",
5238
+ "| mode | warnings | compile failures | promotions | export turns | attributed turns |",
5239
+ "| --- | ---: | ---: | ---: | ---: | ---: |",
5240
+ ...(hardeningRows.length === 0 ? ["| - | 0 | 0 | 0 | 0 | 0 |"] : hardeningRows),
5241
+ "",
5242
+ "## Mode Table",
5243
+ "| mode | turns | compile ok | phrase hits | learned route turns | promotions | export turns | human labels | warnings | score hash |",
5244
+ "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
5245
+ ...(modeRows.length === 0 ? ["| - | 0 | 0 | 0/0 | 0 | 0 | 0 | 0 | 0 | none |"] : modeRows),
5246
+ "",
5247
+ "## Turn Table",
5248
+ "| mode | turn | quality | compile ok | phrase hits | learned route | promoted | active pack | selection digest |",
5249
+ "| --- | --- | ---: | --- | ---: | --- | --- | --- | --- |",
5250
+ ...(turnRows.length === 0 ? ["| - | none | 0 | no | 0/0 | no | no | none | none |"] : turnRows),
5251
+ ""
5252
+ ].join("\n");
5253
+ }
5254
+ function buildRecordedSessionReplayProofManifest(bundle) {
5255
+ return {
5256
+ contract: RECORDED_SESSION_REPLAY_PROOF_MANIFEST_CONTRACT,
5257
+ traceId: bundle.traceId,
5258
+ source: bundle.source,
5259
+ recordedAt: bundle.recordedAt,
5260
+ generatedAt: bundle.generatedAt,
5261
+ hashAlgorithm: "sha256",
5262
+ modeOrder: [...RECORDED_SESSION_REPLAY_MODE_ORDER],
5263
+ contracts: {
5264
+ trace: RECORDED_SESSION_TRACE_CONTRACT,
5265
+ fixture: RECORDED_SESSION_FIXTURE_CONTRACT,
5266
+ bundle: RECORDED_SESSION_BUNDLE_CONTRACT,
5267
+ environment: RECORDED_SESSION_REPLAY_PROOF_ENVIRONMENT_CONTRACT,
5268
+ summaryTables: RECORDED_SESSION_REPLAY_PROOF_SUMMARY_TABLES_CONTRACT,
5269
+ coverageSnapshot: RECORDED_SESSION_REPLAY_PROOF_COVERAGE_SNAPSHOT_CONTRACT,
5270
+ hardeningSnapshot: RECORDED_SESSION_REPLAY_PROOF_HARDENING_SNAPSHOT_CONTRACT,
5271
+ hashes: RECORDED_SESSION_REPLAY_PROOF_HASHES_CONTRACT
5272
+ },
5273
+ hashes: {
5274
+ traceHash: bundle.traceHash,
5275
+ fixtureHash: bundle.fixtureHash,
5276
+ scoreHash: bundle.scoreHash,
5277
+ bundleHash: bundle.bundleHash
5278
+ },
5279
+ files: {
5280
+ trace: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.trace,
5281
+ fixture: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.fixture,
5282
+ bundle: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.bundle,
5283
+ environment: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.environment,
5284
+ summary: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.summary,
5285
+ summaryTables: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.summaryTables,
5286
+ coverageSnapshot: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.coverageSnapshot,
5287
+ hardeningSnapshot: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.hardeningSnapshot,
5288
+ hashes: RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.hashes,
5289
+ modes: RECORDED_SESSION_REPLAY_MODE_ORDER.map((mode) => ({
5290
+ mode,
5291
+ path: recordedSessionReplayModeOutputPath(mode)
5292
+ }))
5293
+ }
5294
+ };
5295
+ }
5296
+ function validateRecordedSessionReplayProofManifest(manifest) {
5297
+ const errors = [];
5298
+ if (manifest.contract !== RECORDED_SESSION_REPLAY_PROOF_MANIFEST_CONTRACT) {
5299
+ errors.push("manifest contract is invalid");
5300
+ }
5301
+ if (canonicalJson(manifest.modeOrder ?? []) !== canonicalJson(RECORDED_SESSION_REPLAY_MODE_ORDER)) {
5302
+ errors.push("manifest modeOrder must stay fixed at no_brain, vector_only, graph_prior_only, learned_route");
5303
+ }
5304
+ if (manifest.hashAlgorithm !== "sha256") {
5305
+ errors.push("manifest hashAlgorithm must be sha256");
5306
+ }
5307
+ if (manifest.contracts?.trace !== RECORDED_SESSION_TRACE_CONTRACT ||
5308
+ manifest.contracts?.fixture !== RECORDED_SESSION_FIXTURE_CONTRACT ||
5309
+ manifest.contracts?.bundle !== RECORDED_SESSION_BUNDLE_CONTRACT ||
5310
+ manifest.contracts?.environment !== RECORDED_SESSION_REPLAY_PROOF_ENVIRONMENT_CONTRACT ||
5311
+ manifest.contracts?.summaryTables !== RECORDED_SESSION_REPLAY_PROOF_SUMMARY_TABLES_CONTRACT ||
5312
+ manifest.contracts?.coverageSnapshot !== RECORDED_SESSION_REPLAY_PROOF_COVERAGE_SNAPSHOT_CONTRACT ||
5313
+ manifest.contracts?.hardeningSnapshot !== RECORDED_SESSION_REPLAY_PROOF_HARDENING_SNAPSHOT_CONTRACT ||
5314
+ manifest.contracts?.hashes !== RECORDED_SESSION_REPLAY_PROOF_HASHES_CONTRACT) {
5315
+ errors.push("manifest contracts block is invalid");
5316
+ }
5317
+ if (manifest.files?.trace !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.trace ||
5318
+ manifest.files?.fixture !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.fixture ||
5319
+ manifest.files?.bundle !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.bundle ||
5320
+ manifest.files?.environment !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.environment ||
5321
+ manifest.files?.summary !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.summary ||
5322
+ manifest.files?.summaryTables !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.summaryTables ||
5323
+ manifest.files?.coverageSnapshot !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.coverageSnapshot ||
5324
+ manifest.files?.hardeningSnapshot !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.hardeningSnapshot ||
5325
+ manifest.files?.hashes !== RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.hashes) {
5326
+ errors.push("manifest top-level file layout is invalid");
5327
+ }
5328
+ const expectedModeFiles = RECORDED_SESSION_REPLAY_MODE_ORDER.map((mode) => ({
5329
+ mode,
5330
+ path: recordedSessionReplayModeOutputPath(mode)
5331
+ }));
5332
+ if (canonicalJson(manifest.files?.modes ?? []) !== canonicalJson(expectedModeFiles)) {
5333
+ errors.push("manifest mode outputs must stay under modes/<mode>.json in canonical order");
5334
+ }
5335
+ return errors;
5336
+ }
5337
+ function validateRecordedSessionReplayProofEnvironment(environment) {
5338
+ const errors = [];
5339
+ if (environment.contract !== RECORDED_SESSION_REPLAY_PROOF_ENVIRONMENT_CONTRACT) {
5340
+ errors.push("environment contract is invalid");
5341
+ }
5342
+ if (environment.runtimeOwner !== "openclaw") {
5343
+ errors.push("environment runtimeOwner must be openclaw");
5344
+ }
5345
+ if (environment.generator?.packageName !== "@openclawbrain/cli" ||
5346
+ environment.generator?.entrypoint !== "writeRecordedSessionReplayProofBundle") {
5347
+ errors.push("environment generator metadata is invalid");
5348
+ }
5349
+ if (normalizeOptionalString(environment.generator?.nodeVersion) === undefined ||
5350
+ normalizeOptionalString(environment.generator?.platform) === undefined ||
5351
+ normalizeOptionalString(environment.generator?.arch) === undefined) {
5352
+ errors.push("environment generator runtime fields are required");
5353
+ }
5354
+ if (environment.determinism?.hashAlgorithm !== "sha256" ||
5355
+ environment.determinism?.canonicalJson !== true ||
5356
+ canonicalJson(environment.determinism?.modeOrder ?? []) !== canonicalJson(RECORDED_SESSION_REPLAY_MODE_ORDER) ||
5357
+ environment.determinism?.scratchReplayRoot !== "temporary_directory") {
5358
+ errors.push("environment determinism block is invalid");
5359
+ }
5360
+ return errors;
5361
+ }
5362
+ function buildRecordedSessionReplayProofFileDigestEntries(rootDir, manifest) {
5363
+ const relativePaths = [
5364
+ manifest.files.trace,
5365
+ manifest.files.fixture,
5366
+ manifest.files.bundle,
5367
+ manifest.files.environment,
5368
+ manifest.files.summary,
5369
+ manifest.files.summaryTables,
5370
+ manifest.files.coverageSnapshot,
5371
+ manifest.files.hardeningSnapshot,
5372
+ manifest.files.modes.map((entry) => entry.path),
5373
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.manifest
5374
+ ].flat();
5375
+ return relativePaths.map((relativePath, index) => ({
5376
+ path: relativePath,
5377
+ digest: checksumTextPayload(readRecordedSessionReplayProofText(rootDir, relativePath, `files[${index}]`))
5378
+ }));
5379
+ }
5380
+ function buildRecordedSessionReplayProofHashes(rootDir, manifest) {
5381
+ return {
5382
+ contract: RECORDED_SESSION_REPLAY_PROOF_HASHES_CONTRACT,
5383
+ algorithm: "sha256",
5384
+ semantic: {
5385
+ traceHash: manifest.hashes.traceHash,
5386
+ fixtureHash: manifest.hashes.fixtureHash,
5387
+ scoreHash: manifest.hashes.scoreHash,
5388
+ bundleHash: manifest.hashes.bundleHash
5389
+ },
5390
+ files: buildRecordedSessionReplayProofFileDigestEntries(rootDir, manifest)
5391
+ };
5392
+ }
5393
+ export function loadRecordedSessionReplayProofBundle(rootDir) {
5394
+ const resolvedRoot = path.resolve(rootDir);
5395
+ const manifestPath = path.join(resolvedRoot, RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.manifest);
5396
+ const manifest = readJsonFile(manifestPath);
5397
+ const tracePath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.trace, "manifest.files.trace");
5398
+ const fixturePath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.fixture, "manifest.files.fixture");
5399
+ const bundlePath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.bundle, "manifest.files.bundle");
5400
+ const environmentPath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.environment, "manifest.files.environment");
5401
+ const summaryPath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.summary, "manifest.files.summary");
5402
+ const summaryTablesPath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.summaryTables, "manifest.files.summaryTables");
5403
+ const coverageSnapshotPath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.coverageSnapshot, "manifest.files.coverageSnapshot");
5404
+ const hardeningSnapshotPath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.hardeningSnapshot, "manifest.files.hardeningSnapshot");
5405
+ const hashesPath = resolveRecordedSessionReplayProofPath(resolvedRoot, manifest.files.hashes, "manifest.files.hashes");
5406
+ const modeOutputs = (manifest.files.modes ?? []).map((entry, index) => {
5407
+ const outputPath = resolveRecordedSessionReplayProofPath(resolvedRoot, entry.path, `manifest.files.modes[${index}].path`);
5408
+ return {
5409
+ mode: entry.mode,
5410
+ path: outputPath,
5411
+ report: readJsonFile(outputPath)
5412
+ };
5413
+ });
5414
+ return {
5415
+ rootDir: resolvedRoot,
5416
+ manifestPath,
5417
+ tracePath,
5418
+ fixturePath,
5419
+ bundlePath,
5420
+ environmentPath,
5421
+ summaryPath,
5422
+ summaryTablesPath,
5423
+ coverageSnapshotPath,
5424
+ hardeningSnapshotPath,
5425
+ hashesPath,
5426
+ manifest,
5427
+ trace: readJsonFile(tracePath),
5428
+ fixture: readJsonFile(fixturePath),
5429
+ bundle: readJsonFile(bundlePath),
5430
+ environment: readJsonFile(environmentPath),
5431
+ summaryText: readFileSync(summaryPath, "utf8"),
5432
+ summaryTables: readJsonFile(summaryTablesPath),
5433
+ coverageSnapshot: readJsonFile(coverageSnapshotPath),
5434
+ hardeningSnapshot: readJsonFile(hardeningSnapshotPath),
5435
+ hashes: readJsonFile(hashesPath),
5436
+ modeOutputs
5437
+ };
5438
+ }
5439
+ export function validateRecordedSessionReplayProofBundle(rootDir) {
5440
+ const resolvedRoot = path.resolve(rootDir);
5441
+ try {
5442
+ const descriptor = loadRecordedSessionReplayProofBundle(resolvedRoot);
5443
+ const errors = [];
5444
+ errors.push(...validateRecordedSessionReplayProofManifest(descriptor.manifest));
5445
+ errors.push(...validateRecordedSessionReplayProofEnvironment(descriptor.environment));
5446
+ if (descriptor.trace.contract !== RECORDED_SESSION_TRACE_CONTRACT) {
5447
+ errors.push("trace.json contract is invalid");
5448
+ }
5449
+ if (descriptor.fixture.contract !== RECORDED_SESSION_FIXTURE_CONTRACT) {
5450
+ errors.push("fixture.json contract is invalid");
5451
+ }
5452
+ if (descriptor.bundle.contract !== RECORDED_SESSION_BUNDLE_CONTRACT) {
5453
+ errors.push("bundle.json contract is invalid");
5454
+ }
5455
+ if (descriptor.summaryTables.contract !== RECORDED_SESSION_REPLAY_PROOF_SUMMARY_TABLES_CONTRACT) {
5456
+ errors.push("summary-tables.json contract is invalid");
5457
+ }
5458
+ if (descriptor.coverageSnapshot.contract !== RECORDED_SESSION_REPLAY_PROOF_COVERAGE_SNAPSHOT_CONTRACT) {
5459
+ errors.push("coverage-snapshot.json contract is invalid");
5460
+ }
5461
+ if (descriptor.hardeningSnapshot.contract !== RECORDED_SESSION_REPLAY_PROOF_HARDENING_SNAPSHOT_CONTRACT) {
5462
+ errors.push("hardening-snapshot.json contract is invalid");
5463
+ }
5464
+ if (descriptor.hashes.contract !== RECORDED_SESSION_REPLAY_PROOF_HASHES_CONTRACT) {
5465
+ errors.push("hashes.json contract is invalid");
5466
+ }
5467
+ if (descriptor.hashes.algorithm !== "sha256") {
5468
+ errors.push("hashes.json algorithm must be sha256");
5469
+ }
5470
+ const rebuiltFixture = buildRecordedSessionReplayFixture(structuredClone(descriptor.trace));
5471
+ if (canonicalJson(rebuiltFixture) !== canonicalJson(descriptor.fixture)) {
5472
+ errors.push("fixture.json does not match the canonical fixture rebuilt from trace.json");
5473
+ }
5474
+ if (descriptor.bundle.traceHash !== descriptor.fixture.traceHash) {
5475
+ errors.push("bundle traceHash does not match fixture traceHash");
5476
+ }
5477
+ if (descriptor.bundle.fixtureHash !== descriptor.fixture.fixtureHash) {
5478
+ errors.push("bundle fixtureHash does not match fixture fixtureHash");
5479
+ }
5480
+ if (descriptor.manifest.hashes.traceHash !== descriptor.fixture.traceHash ||
5481
+ descriptor.manifest.hashes.fixtureHash !== descriptor.fixture.fixtureHash ||
5482
+ descriptor.manifest.hashes.scoreHash !== descriptor.bundle.scoreHash ||
5483
+ descriptor.manifest.hashes.bundleHash !== descriptor.bundle.bundleHash) {
5484
+ errors.push("manifest hashes do not match the replay artifacts");
5485
+ }
5486
+ const summaryTables = buildRecordedSessionReplayProofSummaryTables(descriptor.bundle);
5487
+ if (canonicalJson(summaryTables) !== canonicalJson(descriptor.summaryTables)) {
5488
+ errors.push("summary-tables.json does not match bundle.json");
5489
+ }
5490
+ const coverageSnapshot = buildRecordedSessionReplayProofCoverageSnapshot(descriptor.bundle);
5491
+ if (canonicalJson(coverageSnapshot) !== canonicalJson(descriptor.coverageSnapshot)) {
5492
+ errors.push("coverage-snapshot.json does not match bundle.json");
5493
+ }
5494
+ const hardeningSnapshot = buildRecordedSessionReplayProofHardeningSnapshot(descriptor.bundle);
5495
+ if (canonicalJson(hardeningSnapshot) !== canonicalJson(descriptor.hardeningSnapshot)) {
5496
+ errors.push("hardening-snapshot.json does not match bundle.json");
5497
+ }
5498
+ const expectedSummaryText = buildRecordedSessionReplayProofSummary({
5499
+ bundle: descriptor.bundle,
5500
+ summaryTables: descriptor.summaryTables,
5501
+ coverageSnapshot: descriptor.coverageSnapshot,
5502
+ hardeningSnapshot: descriptor.hardeningSnapshot,
5503
+ semanticHashes: descriptor.hashes.semantic
5504
+ });
5505
+ if (descriptor.summaryText !== expectedSummaryText) {
5506
+ errors.push("summary.md does not match the canonical replay proof summary");
5507
+ }
5508
+ const expectedModeCount = descriptor.bundle.modes.length;
5509
+ if (descriptor.modeOutputs.length !== expectedModeCount) {
5510
+ errors.push("mode output count does not match bundle modes");
5511
+ }
5512
+ const bundleModes = new Map(descriptor.bundle.modes.map((mode) => [mode.mode, mode]));
5513
+ for (const modeOutput of descriptor.modeOutputs) {
5514
+ const expectedMode = bundleModes.get(modeOutput.mode);
5515
+ if (expectedMode === undefined) {
5516
+ errors.push(`unexpected mode output: ${modeOutput.mode}`);
5517
+ continue;
5518
+ }
5519
+ if (canonicalJson(expectedMode) !== canonicalJson(modeOutput.report)) {
5520
+ errors.push(`mode output drift detected for ${modeOutput.mode}`);
5521
+ }
5522
+ }
5523
+ const semanticHashVerification = verifyRecordedSessionReplayBundleHashes(descriptor.bundle);
5524
+ if (!semanticHashVerification.bundleHashMatches) {
5525
+ errors.push("bundleHash verification failed");
5526
+ }
5527
+ if (!semanticHashVerification.scoreHashMatches) {
5528
+ errors.push("scoreHash verification failed");
5529
+ }
5530
+ const expectedHashes = buildRecordedSessionReplayProofHashes(resolvedRoot, descriptor.manifest);
5531
+ const expectedFileDigests = new Map(expectedHashes.files.map((entry) => [entry.path, entry.digest]));
5532
+ let verifiedFileCount = 0;
5533
+ for (const fileEntry of descriptor.hashes.files ?? []) {
5534
+ if (expectedFileDigests.get(fileEntry.path) === fileEntry.digest) {
5535
+ verifiedFileCount += 1;
5536
+ }
5537
+ }
5538
+ const fileHashesMatch = canonicalJson(expectedHashes.files) === canonicalJson(descriptor.hashes.files ?? []);
5539
+ if (!fileHashesMatch) {
5540
+ errors.push("hashes.json file digests do not match the written artifacts");
5541
+ }
5542
+ if (canonicalJson(expectedHashes.semantic) !== canonicalJson(descriptor.hashes.semantic ?? {})) {
5543
+ errors.push("hashes.json semantic hashes do not match the manifest");
5544
+ }
5545
+ return {
5546
+ contract: RECORDED_SESSION_REPLAY_PROOF_VALIDATION_CONTRACT,
5547
+ ok: errors.length === 0,
5548
+ rootDir: resolvedRoot,
5549
+ expectedFileCount: expectedHashes.files.length,
5550
+ verifiedFileCount,
5551
+ fileHashesMatch,
5552
+ bundleHashMatches: semanticHashVerification.bundleHashMatches,
5553
+ scoreHashMatches: semanticHashVerification.scoreHashMatches,
5554
+ errors
5555
+ };
5556
+ }
5557
+ catch (error) {
5558
+ return {
5559
+ contract: RECORDED_SESSION_REPLAY_PROOF_VALIDATION_CONTRACT,
5560
+ ok: false,
5561
+ rootDir: resolvedRoot,
5562
+ expectedFileCount: 0,
5563
+ verifiedFileCount: 0,
5564
+ fileHashesMatch: false,
5565
+ bundleHashMatches: false,
5566
+ scoreHashMatches: false,
5567
+ errors: [toErrorMessage(error)]
5568
+ };
5569
+ }
5570
+ }
5571
+ export function writeRecordedSessionReplayProofBundle(input) {
5572
+ const resolvedRoot = path.resolve(normalizeNonEmptyString(input.rootDir, "rootDir"));
5573
+ const scratchRootParent = path.resolve(normalizeOptionalString(input.scratchRootDir) ?? tmpdir());
5574
+ mkdirSync(resolvedRoot, { recursive: true });
5575
+ mkdirSync(scratchRootParent, { recursive: true });
5576
+ rmSync(path.join(resolvedRoot, RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.modeDir), {
5577
+ recursive: true,
5578
+ force: true
5579
+ });
5580
+ for (const relativePath of [
5581
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.trace,
5582
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.fixture,
5583
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.bundle,
5584
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.environment,
5585
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.summary,
5586
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.summaryTables,
5587
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.coverageSnapshot,
5588
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.hardeningSnapshot,
5589
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.manifest,
5590
+ RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.hashes
5591
+ ]) {
5592
+ rmSync(path.join(resolvedRoot, relativePath), { force: true });
5593
+ }
5594
+ const trace = structuredClone(input.trace);
5595
+ const fixture = buildRecordedSessionReplayFixture(trace);
5596
+ const scratchRoot = mkdtempSync(path.join(scratchRootParent, "openclawbrain-recorded-session-replay-"));
5597
+ let bundle;
5598
+ try {
5599
+ bundle = runRecordedSessionReplay(scratchRoot, fixture);
5600
+ }
5601
+ finally {
5602
+ rmSync(scratchRoot, { recursive: true, force: true });
5603
+ }
5604
+ const environment = buildRecordedSessionReplayProofEnvironment();
5605
+ const summaryTables = buildRecordedSessionReplayProofSummaryTables(bundle);
5606
+ const coverageSnapshot = buildRecordedSessionReplayProofCoverageSnapshot(bundle);
5607
+ const hardeningSnapshot = buildRecordedSessionReplayProofHardeningSnapshot(bundle);
5608
+ const manifest = buildRecordedSessionReplayProofManifest(bundle);
5609
+ const summaryText = buildRecordedSessionReplayProofSummary({
5610
+ bundle,
5611
+ summaryTables,
5612
+ coverageSnapshot,
5613
+ hardeningSnapshot,
5614
+ semanticHashes: manifest.hashes
5615
+ });
5616
+ const modeRoot = path.join(resolvedRoot, RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.modeDir);
5617
+ mkdirSync(modeRoot, { recursive: true });
5618
+ writeFileSync(path.join(resolvedRoot, manifest.files.trace), canonicalJson(trace), "utf8");
5619
+ writeFileSync(path.join(resolvedRoot, manifest.files.fixture), canonicalJson(fixture), "utf8");
5620
+ writeFileSync(path.join(resolvedRoot, manifest.files.bundle), canonicalJson(bundle), "utf8");
5621
+ writeFileSync(path.join(resolvedRoot, manifest.files.environment), canonicalJson(environment), "utf8");
5622
+ writeFileSync(path.join(resolvedRoot, manifest.files.summaryTables), canonicalJson(summaryTables), "utf8");
5623
+ writeFileSync(path.join(resolvedRoot, manifest.files.coverageSnapshot), canonicalJson(coverageSnapshot), "utf8");
5624
+ writeFileSync(path.join(resolvedRoot, manifest.files.hardeningSnapshot), canonicalJson(hardeningSnapshot), "utf8");
5625
+ for (const mode of orderedRecordedSessionReplayModes(bundle.modes)) {
5626
+ writeFileSync(path.join(resolvedRoot, recordedSessionReplayModeOutputPath(mode.mode)), canonicalJson(mode), "utf8");
5627
+ }
5628
+ writeFileSync(path.join(resolvedRoot, manifest.files.summary), summaryText, "utf8");
5629
+ writeFileSync(path.join(resolvedRoot, RECORDED_SESSION_REPLAY_PROOF_BUNDLE_LAYOUT.manifest), canonicalJson(manifest), "utf8");
5630
+ const hashes = buildRecordedSessionReplayProofHashes(resolvedRoot, manifest);
5631
+ writeFileSync(path.join(resolvedRoot, manifest.files.hashes), canonicalJson(hashes), "utf8");
5632
+ const validation = validateRecordedSessionReplayProofBundle(resolvedRoot);
5633
+ if (!validation.ok) {
5634
+ throw new Error(`recorded session replay proof bundle is invalid: ${validation.errors.join("; ")}`);
5635
+ }
5636
+ return loadRecordedSessionReplayProofBundle(resolvedRoot);
5637
+ }
4778
5638
  export const OPERATOR_API_CONTRACT_ID = "openclaw_operator_api.v1";
4779
5639
  export const SUPPORTED_OPERATOR_API_FAMILIES = [
4780
5640
  "bootstrap_attach",
@@ -5737,7 +6597,128 @@ function summarizeTeacherLoopWatchState(input) {
5737
6597
  snapshotUpdatedAt: input.watchSnapshot.updatedAt,
5738
6598
  lastWatchHeartbeatAt,
5739
6599
  pollIntervalSeconds,
5740
- watchState: Number.isFinite(lagMs) && lagMs >= 0 && lagMs <= staleAfterMs ? "watching" : "stale_snapshot"
6600
+ watchState: Number.isFinite(lagMs) && lagMs >= 0 && lagMs <= staleAfterMs ? "watching" : "stale_snapshot"
6601
+ };
6602
+ }
6603
+ function emptyOperatorLearningAttribution(source, snapshotKind, detail) {
6604
+ return {
6605
+ available: false,
6606
+ source,
6607
+ snapshotKind,
6608
+ quality: "unavailable",
6609
+ nonZeroObservationCount: 0,
6610
+ skippedZeroRewardCount: 0,
6611
+ exactMatchCount: 0,
6612
+ heuristicMatchCount: 0,
6613
+ unmatchedCount: 0,
6614
+ ambiguousCount: 0,
6615
+ matchedByMode: {
6616
+ exactDecisionId: 0,
6617
+ exactSelectionDigest: 0,
6618
+ turnCompileEventId: 0,
6619
+ legacyHeuristic: 0
6620
+ },
6621
+ unmatchedByMode: {
6622
+ exactDecisionId: 0,
6623
+ exactSelectionDigest: 0,
6624
+ turnCompileEventId: 0,
6625
+ legacyHeuristic: 0
6626
+ },
6627
+ ambiguousByMode: {
6628
+ exactDecisionId: 0,
6629
+ exactSelectionDigest: 0,
6630
+ turnCompileEventId: 0,
6631
+ legacyHeuristic: 0
6632
+ },
6633
+ detail
6634
+ };
6635
+ }
6636
+ function sumObservationBindingModes(counts) {
6637
+ return (counts.exactDecisionId ?? 0)
6638
+ + (counts.exactSelectionDigest ?? 0)
6639
+ + (counts.turnCompileEventId ?? 0)
6640
+ + (counts.legacyHeuristic ?? 0);
6641
+ }
6642
+ function deriveObservationBindingQuality(summary) {
6643
+ if (summary.nonZeroObservationCount === 0) {
6644
+ return "no_nonzero_observations";
6645
+ }
6646
+ if (summary.ambiguousCount > 0) {
6647
+ if (summary.exactMatchCount > 0) {
6648
+ return "exact_with_ambiguous";
6649
+ }
6650
+ if (summary.heuristicMatchCount > 0) {
6651
+ return "heuristic_with_ambiguous";
6652
+ }
6653
+ return "ambiguous_present";
6654
+ }
6655
+ if (summary.unmatchedCount > 0) {
6656
+ if (summary.exactMatchCount > 0) {
6657
+ return "exact_with_unmatched";
6658
+ }
6659
+ if (summary.heuristicMatchCount > 0) {
6660
+ return "heuristic_with_unmatched";
6661
+ }
6662
+ return "unmatched_present";
6663
+ }
6664
+ if (summary.exactMatchCount > 0 && summary.heuristicMatchCount > 0) {
6665
+ return "exact_plus_heuristic";
6666
+ }
6667
+ if (summary.exactMatchCount > 0) {
6668
+ return "exact_only";
6669
+ }
6670
+ if (summary.heuristicMatchCount > 0) {
6671
+ return "heuristic_only";
6672
+ }
6673
+ return "no_matches";
6674
+ }
6675
+ function summarizeTeacherLoopObservationBinding(snapshot, sourceKind) {
6676
+ const stats = snapshot.learner.lastMaterialization?.candidate.routingBuild?.observationBindingStats ?? null;
6677
+ if (stats === null) {
6678
+ return emptyOperatorLearningAttribution("latest_materialization", sourceKind, snapshot.learner.lastMaterialization === null
6679
+ ? "no materialized candidate pack is visible in the teacher snapshot"
6680
+ : "latest materialization did not expose observation binding stats");
6681
+ }
6682
+ const exactMatchCount = (stats.matched.exactDecisionId ?? 0)
6683
+ + (stats.matched.exactSelectionDigest ?? 0)
6684
+ + (stats.matched.turnCompileEventId ?? 0);
6685
+ const heuristicMatchCount = stats.matched.legacyHeuristic ?? 0;
6686
+ const unmatchedCount = sumObservationBindingModes(stats.unmatched);
6687
+ const ambiguousCount = sumObservationBindingModes(stats.ambiguous);
6688
+ const summary = {
6689
+ available: true,
6690
+ source: "latest_materialization",
6691
+ snapshotKind: sourceKind,
6692
+ quality: "unavailable",
6693
+ nonZeroObservationCount: stats.nonZeroObservationCount ?? 0,
6694
+ skippedZeroRewardCount: stats.skippedZeroRewardCount ?? 0,
6695
+ exactMatchCount,
6696
+ heuristicMatchCount,
6697
+ unmatchedCount,
6698
+ ambiguousCount,
6699
+ matchedByMode: {
6700
+ exactDecisionId: stats.matched.exactDecisionId ?? 0,
6701
+ exactSelectionDigest: stats.matched.exactSelectionDigest ?? 0,
6702
+ turnCompileEventId: stats.matched.turnCompileEventId ?? 0,
6703
+ legacyHeuristic: stats.matched.legacyHeuristic ?? 0
6704
+ },
6705
+ unmatchedByMode: {
6706
+ exactDecisionId: stats.unmatched.exactDecisionId ?? 0,
6707
+ exactSelectionDigest: stats.unmatched.exactSelectionDigest ?? 0,
6708
+ turnCompileEventId: stats.unmatched.turnCompileEventId ?? 0,
6709
+ legacyHeuristic: stats.unmatched.legacyHeuristic ?? 0
6710
+ },
6711
+ ambiguousByMode: {
6712
+ exactDecisionId: stats.ambiguous.exactDecisionId ?? 0,
6713
+ exactSelectionDigest: stats.ambiguous.exactSelectionDigest ?? 0,
6714
+ turnCompileEventId: stats.ambiguous.turnCompileEventId ?? 0,
6715
+ legacyHeuristic: stats.ambiguous.legacyHeuristic ?? 0
6716
+ },
6717
+ detail: "teacher observation binding stats came from the latest materialized candidate"
6718
+ };
6719
+ return {
6720
+ ...summary,
6721
+ quality: deriveObservationBindingQuality(summary)
5741
6722
  };
5742
6723
  }
5743
6724
  function summarizeTeacherLoop(input) {
@@ -5779,6 +6760,7 @@ function summarizeTeacherLoop(input) {
5779
6760
  failureDetail: null,
5780
6761
  lastAppliedMaterializationJobId: null,
5781
6762
  lastMaterializedPackId: null,
6763
+ observationBinding: emptyOperatorLearningAttribution("unavailable", "missing", "no teacher snapshot path supplied"),
5782
6764
  lastObservedDelta: unavailableFromMissing,
5783
6765
  notes: [],
5784
6766
  detail: "no teacher snapshot path supplied"
@@ -5818,6 +6800,7 @@ function summarizeTeacherLoop(input) {
5818
6800
  failureDetail: null,
5819
6801
  lastAppliedMaterializationJobId: null,
5820
6802
  lastMaterializedPackId: null,
6803
+ observationBinding: emptyOperatorLearningAttribution("unavailable", "missing", "teacher snapshot could not be loaded"),
5821
6804
  lastObservedDelta: unavailableFromMissing,
5822
6805
  notes: [],
5823
6806
  detail: "teacher snapshot could not be loaded"
@@ -5866,6 +6849,7 @@ function summarizeTeacherLoop(input) {
5866
6849
  snapshot.learner.lastMaterialization?.jobId ??
5867
6850
  null,
5868
6851
  lastMaterializedPackId: snapshot.learner.lastMaterialization?.candidate.summary.packId ?? null,
6852
+ observationBinding: summarizeTeacherLoopObservationBinding(snapshot, loaded.sourceKind),
5869
6853
  lastObservedDelta: loaded.sourceKind === "watch_snapshot" && watchSnapshot !== null
5870
6854
  ? cloneLastObservedDelta(watchSnapshot.lastObservedDelta)
5871
6855
  : unavailableFromAsync,
@@ -5994,7 +6978,7 @@ function summarizeLearningWarningStates(input) {
5994
6978
  input.teacherSnapshot.queue.depth >= input.teacherSnapshot.queue.capacity) {
5995
6979
  warnings.add("teacher_queue_full");
5996
6980
  }
5997
- if (input.teacherSnapshot.diagnostics.latestFreshness === "stale") {
6981
+ if (input.teacherSnapshot.diagnostics.latestFreshness === "stale" && input.teacherSnapshot.diagnostics.lastNoOpReason !== "no_teacher_artifacts") {
5998
6982
  warnings.add("teacher_labels_stale");
5999
6983
  }
6000
6984
  if (input.teacherSnapshot.diagnostics.lastNoOpReason === "no_teacher_artifacts") {
@@ -6619,6 +7603,10 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode, profileId)
6619
7603
  installState: report.hook.installState,
6620
7604
  loadability: report.hook.loadability,
6621
7605
  loadProof: report.hook.loadProof,
7606
+ guardSeverity: report.hook.guardSeverity,
7607
+ guardActionability: report.hook.guardActionability,
7608
+ guardSummary: report.hook.guardSummary,
7609
+ guardAction: report.hook.guardAction,
6622
7610
  desynced: report.hook.desynced,
6623
7611
  detail: report.hook.detail
6624
7612
  },
@@ -6665,7 +7653,10 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode, profileId)
6665
7653
  ? "current profile would fail open to static context because no serving pack is available"
6666
7654
  : report.servePath.state === "hard_fail"
6667
7655
  ? "current profile cannot serve because the learned-route or activation requirement hard-failed"
6668
- : "current profile serve state has not been compile-probed yet"
7656
+ : "current profile serve state has not been compile-probed yet"
7657
+ },
7658
+ learningAttribution: {
7659
+ ...report.teacherLoop.observationBinding
6669
7660
  },
6670
7661
  passiveLearning,
6671
7662
  currentTurnAttribution: buildCurrentProfileTurnAttributionFromReport(report, policyMode, profileId)