@lcv-ideas-software/cross-review 4.0.7 → 4.1.0
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/CHANGELOG.md +207 -0
- package/README.md +3 -1
- package/dist/scripts/smoke.js +179 -79
- package/dist/scripts/smoke.js.map +1 -1
- package/dist/src/core/cache-manifest.d.ts +2 -2
- package/dist/src/core/cache-manifest.js +15 -9
- package/dist/src/core/cache-manifest.js.map +1 -1
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +2 -2
- package/dist/src/core/orchestrator.js +63 -63
- package/dist/src/core/orchestrator.js.map +1 -1
- package/dist/src/core/session-store.d.ts +35 -34
- package/dist/src/core/session-store.js +269 -156
- package/dist/src/core/session-store.js.map +1 -1
- package/dist/src/dashboard/server.js +5 -1
- package/dist/src/dashboard/server.js.map +1 -1
- package/dist/src/mcp/server.js +41 -33
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/security/redact.js +13 -2
- package/dist/src/security/redact.js.map +1 -1
- package/package.json +3 -1
package/dist/scripts/smoke.js
CHANGED
|
@@ -175,7 +175,7 @@ const events = [];
|
|
|
175
175
|
const holder = {};
|
|
176
176
|
const orchestrator = new CrossReviewOrchestrator(config, (event) => {
|
|
177
177
|
events.push(event.type);
|
|
178
|
-
holder.orchestrator?.store.appendEvent(event);
|
|
178
|
+
void holder.orchestrator?.store.appendEvent(event);
|
|
179
179
|
});
|
|
180
180
|
holder.orchestrator = orchestrator;
|
|
181
181
|
const adapterExpectations = [
|
|
@@ -260,27 +260,32 @@ const overlappingPem = [
|
|
|
260
260
|
pemMarker("END", "RSA PRIVATE KEY"),
|
|
261
261
|
].join("\n");
|
|
262
262
|
assert.equal(redact(`before ${overlappingPem} after`), "before [REDACTED] after");
|
|
263
|
+
// v4.1.0 / F4 security hardening: pre-v4.1.0 LEAKED unterminated PRIVATE
|
|
264
|
+
// KEY blocks (BEGIN without matching END — e.g. truncated logs). v4.1.0
|
|
265
|
+
// redacts from `begin.index` to end-of-string for unterminated blocks.
|
|
266
|
+
// Test was previously pinning the leak as expected behavior; updated to
|
|
267
|
+
// pin the no-leak contract.
|
|
263
268
|
const unterminatedPem = `${pemMarker("BEGIN", "EC PRIVATE KEY")}\nmissing end`;
|
|
264
|
-
assert.equal(redact(unterminatedPem),
|
|
269
|
+
assert.equal(redact(unterminatedPem), "[REDACTED]");
|
|
265
270
|
const completeThenUnterminated = [
|
|
266
271
|
pemBlock("RSA PRIVATE KEY", "first"),
|
|
267
272
|
"preserve this middle text",
|
|
268
273
|
pemMarker("BEGIN", "RSA PRIVATE KEY"),
|
|
269
274
|
"missing end",
|
|
270
275
|
].join("\n");
|
|
271
|
-
assert.equal(redact(completeThenUnterminated), [
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
assert.equal(redact(completeThenUnterminated), ["[REDACTED]", "preserve this middle text", "[REDACTED]"].join("\n"));
|
|
277
|
+
// v4.1.0 / F4 security hardening: pre-v4.1.0 left these adversarial
|
|
278
|
+
// inputs UNREDACTED (returning the original) because the unterminated-
|
|
279
|
+
// BEGIN branch silently fell through. v4.1.0 redacts from the first
|
|
280
|
+
// BEGIN marker to end-of-string. The performance budget (< 1 s for 2 000
|
|
281
|
+
// nested begins) is preserved.
|
|
277
282
|
const adversarialPem = `${pemMarker("BEGIN", "EC PRIVATE KEY")}\n${pemMarker("BEGIN", "DSA PRIVATE KEY").repeat(2_000)}`;
|
|
278
283
|
const adversarialStarted = Date.now();
|
|
279
|
-
assert.equal(redact(adversarialPem),
|
|
284
|
+
assert.equal(redact(adversarialPem), "[REDACTED]");
|
|
280
285
|
assert.equal(Date.now() - adversarialStarted < 1_000, true);
|
|
281
286
|
const repeatedSameLabelStarted = Date.now();
|
|
282
287
|
const repeatedSameLabel = pemMarker("BEGIN", "RSA PRIVATE KEY").repeat(2_000);
|
|
283
|
-
assert.equal(redact(repeatedSameLabel),
|
|
288
|
+
assert.equal(redact(repeatedSameLabel), "[REDACTED]");
|
|
284
289
|
assert.equal(Date.now() - repeatedSameLabelStarted < 1_000, true);
|
|
285
290
|
const constructedToken = ["sk", "test", "A".repeat(24)].join("-");
|
|
286
291
|
assert.equal(redact(`token ${constructedToken}`), "token [REDACTED]");
|
|
@@ -601,28 +606,28 @@ assert.match(reviewPrompt, /not as instructions that override/);
|
|
|
601
606
|
assert.match(reviewPrompt, /OUT OF SCOPE/);
|
|
602
607
|
assert.ok(reviewPrompt.indexOf("## Review Focus") < reviewPrompt.indexOf("## Original Task"), "Review Focus must be front-loaded before the task body");
|
|
603
608
|
assert.doesNotMatch(reviewPrompt, /\/focus\s+services\/billing/);
|
|
604
|
-
const evidence = orchestrator.store.attachEvidence(result.session.session_id, {
|
|
609
|
+
const evidence = await orchestrator.store.attachEvidence(result.session.session_id, {
|
|
605
610
|
label: "smoke evidence",
|
|
606
611
|
content: "smoke evidence body",
|
|
607
612
|
content_type: "text/markdown",
|
|
608
613
|
extension: "md",
|
|
609
614
|
});
|
|
610
615
|
assert.equal(fs.existsSync(path.join(config.data_dir, "sessions", result.session.session_id, evidence.path)), true);
|
|
611
|
-
const escalated = orchestrator.store.escalateToOperator(result.session.session_id, {
|
|
616
|
+
const escalated = await orchestrator.store.escalateToOperator(result.session.session_id, {
|
|
612
617
|
reason: "smoke operator escalation",
|
|
613
618
|
severity: "info",
|
|
614
619
|
});
|
|
615
620
|
assert.equal(escalated.operator_escalations?.at(-1)?.severity, "info");
|
|
616
|
-
const fresh = orchestrator.store.init("fresh unfinished smoke session", "operator", probes);
|
|
621
|
+
const fresh = await orchestrator.store.init("fresh unfinished smoke session", "operator", probes);
|
|
617
622
|
assert.equal(SWEEP_MIN_IDLE_MS, 24 * 60 * 60 * 1000);
|
|
618
|
-
assert.equal(orchestrator.store.sweepIdle(0, "aborted", "fresh_smoke_stale").length, 0);
|
|
623
|
+
assert.equal((await orchestrator.store.sweepIdle(0, "aborted", "fresh_smoke_stale")).length, 0);
|
|
619
624
|
assert.equal(orchestrator.store.read(fresh.session_id).outcome, undefined);
|
|
620
|
-
const stale = orchestrator.store.init("old unfinished smoke session", "operator", probes);
|
|
625
|
+
const stale = await orchestrator.store.init("old unfinished smoke session", "operator", probes);
|
|
621
626
|
const staleMetaPath = orchestrator.store.metaPath(stale.session_id);
|
|
622
627
|
const staleMeta = JSON.parse(fs.readFileSync(staleMetaPath, "utf8"));
|
|
623
628
|
staleMeta.updated_at = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
|
624
629
|
fs.writeFileSync(staleMetaPath, `${JSON.stringify(staleMeta, null, 2)}\n`, "utf8");
|
|
625
|
-
const swept = orchestrator.store.sweepIdle(0, "aborted", "smoke_stale");
|
|
630
|
+
const swept = await orchestrator.store.sweepIdle(0, "aborted", "smoke_stale");
|
|
626
631
|
assert.equal(swept.some((session) => session.session_id === stale.session_id), true);
|
|
627
632
|
assert.equal(orchestrator.store.read(stale.session_id).outcome, "aborted");
|
|
628
633
|
assert.equal(orchestrator.store.read(fresh.session_id).outcome, undefined);
|
|
@@ -861,8 +866,8 @@ assert.equal(untilStoppedDefaultBudget.converged, false);
|
|
|
861
866
|
assert.equal(untilStoppedDefaultBudget.session.outcome, "max-rounds");
|
|
862
867
|
assert.equal(untilStoppedDefaultBudget.session.outcome_reason, "budget_exceeded");
|
|
863
868
|
assert.equal(untilStoppedDefaultBudget.rounds, 1);
|
|
864
|
-
const recoverySession = orchestrator.store.init("interrupted smoke session", "operator", probes);
|
|
865
|
-
orchestrator.store.markInFlight(recoverySession.session_id, {
|
|
869
|
+
const recoverySession = await orchestrator.store.init("interrupted smoke session", "operator", probes);
|
|
870
|
+
await orchestrator.store.markInFlight(recoverySession.session_id, {
|
|
866
871
|
round: 1,
|
|
867
872
|
peers: ["codex"],
|
|
868
873
|
started_at: new Date().toISOString(),
|
|
@@ -873,7 +878,7 @@ orchestrator.store.markInFlight(recoverySession.session_id, {
|
|
|
873
878
|
reviewer_peers: ["codex"],
|
|
874
879
|
},
|
|
875
880
|
});
|
|
876
|
-
const recoveredInterrupted = orchestrator.store.recoverInterruptedSessions();
|
|
881
|
+
const recoveredInterrupted = await orchestrator.store.recoverInterruptedSessions();
|
|
877
882
|
assert.equal(recoveredInterrupted.some((session) => session.session_id === recoverySession.session_id), true);
|
|
878
883
|
assert.equal(orchestrator.store.read(recoverySession.session_id).control?.status, "recovered_after_restart");
|
|
879
884
|
const abortController = new AbortController();
|
|
@@ -900,6 +905,7 @@ const preflightBlocked = await preflightOrchestrator.askPeers({
|
|
|
900
905
|
assert.equal(preflightBlocked.converged, false);
|
|
901
906
|
assert.equal(preflightBlocked.round.rejected.at(-1)?.failure_class, "budget_preflight");
|
|
902
907
|
assert.equal(preflightBlocked.session.outcome_reason, "budget_preflight");
|
|
908
|
+
await orchestrator.store.flushPendingEvents();
|
|
903
909
|
const eventful = orchestrator.store.readEvents(formatRecovered.session.session_id);
|
|
904
910
|
assert.equal(eventful.some((event) => event.type === "round.completed"), true);
|
|
905
911
|
assert.equal(eventful.some((event) => event.type === "peer.token.delta"), true);
|
|
@@ -927,6 +933,7 @@ const directStreamChars = directStreamEvents
|
|
|
927
933
|
.reduce((total, event) => total + Number(event.data?.chars ?? 0), 0);
|
|
928
934
|
assert.equal(directStreamChars, directStubResult.text.length);
|
|
929
935
|
assert.deepEqual(eventful.map((event) => event.seq), eventful.map((_, index) => index + 1));
|
|
936
|
+
await orchestrator.store.flushPendingEvents();
|
|
930
937
|
const metrics = orchestrator.store.metrics();
|
|
931
938
|
assert.equal(metrics.fallback_events, 1);
|
|
932
939
|
assert.equal((metrics.peer_failures.cancelled ?? 0) >= 1, true);
|
|
@@ -940,8 +947,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
940
947
|
...config,
|
|
941
948
|
data_dir: smokeTmpDir("session-doctor"),
|
|
942
949
|
});
|
|
943
|
-
const doctorSession = doctorStore.init("doctor self-lead legacy fixture", "claude", []);
|
|
944
|
-
doctorStore.markInFlight(doctorSession.session_id, {
|
|
950
|
+
const doctorSession = await doctorStore.init("doctor self-lead legacy fixture", "claude", []);
|
|
951
|
+
await doctorStore.markInFlight(doctorSession.session_id, {
|
|
945
952
|
round: 1,
|
|
946
953
|
peers: ["codex"],
|
|
947
954
|
started_at: new Date().toISOString(),
|
|
@@ -955,25 +962,25 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
955
962
|
lead_peer: "claude",
|
|
956
963
|
},
|
|
957
964
|
});
|
|
958
|
-
doctorStore.appendEvent({
|
|
965
|
+
await doctorStore.appendEvent({
|
|
959
966
|
type: "peer.token.delta",
|
|
960
967
|
session_id: doctorSession.session_id,
|
|
961
968
|
round: 1,
|
|
962
969
|
peer: "codex",
|
|
963
970
|
data: { chars: 12 },
|
|
964
971
|
});
|
|
965
|
-
doctorStore.appendEvent({
|
|
972
|
+
await doctorStore.appendEvent({
|
|
966
973
|
type: "peer.token.completed",
|
|
967
974
|
session_id: doctorSession.session_id,
|
|
968
975
|
round: 1,
|
|
969
976
|
peer: "codex",
|
|
970
977
|
data: { chars: 12 },
|
|
971
978
|
});
|
|
972
|
-
const malformedSession = doctorStore.init("doctor malformed events fixture", "operator", []);
|
|
979
|
+
const malformedSession = await doctorStore.init("doctor malformed events fixture", "operator", []);
|
|
973
980
|
fs.writeFileSync(doctorStore.eventsPath(malformedSession.session_id), "{bad-json\n", "utf8");
|
|
974
981
|
// v2.22.0 (A.P2): self_lead_metadata is hidden by default. Pass
|
|
975
982
|
// includeLegacy=true here to preserve the original behavior assertion.
|
|
976
|
-
const doctor = doctorStore.sessionDoctor(5, true);
|
|
983
|
+
const doctor = await doctorStore.sessionDoctor(5, true);
|
|
977
984
|
assert.equal(doctor.totals.sessions, 2);
|
|
978
985
|
assert.equal(doctor.totals.open, 2);
|
|
979
986
|
assert.equal(doctor.totals.self_lead_metadata, 1);
|
|
@@ -996,8 +1003,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
996
1003
|
data_dir: smokeTmpDir("session-doctor-legacy"),
|
|
997
1004
|
});
|
|
998
1005
|
// Fixture: legacy self-lead session (caller==lead_peer)
|
|
999
|
-
const legacySession = filterStore.init("legacy self-lead fixture", "claude", []);
|
|
1000
|
-
filterStore.markInFlight(legacySession.session_id, {
|
|
1006
|
+
const legacySession = await filterStore.init("legacy self-lead fixture", "claude", []);
|
|
1007
|
+
await filterStore.markInFlight(legacySession.session_id, {
|
|
1001
1008
|
round: 1,
|
|
1002
1009
|
peers: ["codex"],
|
|
1003
1010
|
started_at: new Date().toISOString(),
|
|
@@ -1012,13 +1019,13 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1012
1019
|
},
|
|
1013
1020
|
});
|
|
1014
1021
|
// Default call: array hidden, totals visible, recommendation mentions include_legacy.
|
|
1015
|
-
const defaultReport = filterStore.sessionDoctor(20);
|
|
1022
|
+
const defaultReport = await filterStore.sessionDoctor(20);
|
|
1016
1023
|
assert.equal(defaultReport.totals.self_lead_metadata, 1, "totals.self_lead_metadata count must remain visible when array is suppressed");
|
|
1017
1024
|
assert.equal(defaultReport.findings.self_lead_metadata.length, 0, "findings.self_lead_metadata must be empty by default (legacy noise suppression)");
|
|
1018
1025
|
const hasIncludeLegacyHint = defaultReport.recommendations.some((rec) => rec.includes("include_legacy=true"));
|
|
1019
1026
|
assert.equal(hasIncludeLegacyHint, true, "recommendation must mention include_legacy=true when array is suppressed and count > 0");
|
|
1020
1027
|
// Explicit include_legacy=true: array populated.
|
|
1021
|
-
const inclusiveReport = filterStore.sessionDoctor(20, true);
|
|
1028
|
+
const inclusiveReport = await filterStore.sessionDoctor(20, true);
|
|
1022
1029
|
assert.equal(inclusiveReport.totals.self_lead_metadata, 1, "totals must match between default and inclusive calls");
|
|
1023
1030
|
assert.equal(inclusiveReport.findings.self_lead_metadata.length, 1, "findings.self_lead_metadata must be populated when include_legacy=true");
|
|
1024
1031
|
assert.equal(inclusiveReport.findings.self_lead_metadata[0]?.lead_peer, "claude");
|
|
@@ -1034,7 +1041,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1034
1041
|
...config,
|
|
1035
1042
|
data_dir: smokeTmpDir("session-doctor-drilldown"),
|
|
1036
1043
|
});
|
|
1037
|
-
const driveSession = drillStore.init("evidence drill-down fixture", "operator", []);
|
|
1044
|
+
const driveSession = await drillStore.init("evidence drill-down fixture", "operator", []);
|
|
1038
1045
|
// Fabricate evidence_checklist directly via meta path: 3 open items
|
|
1039
1046
|
// (codex x1, gemini x2), one of them chronic (round_count=4).
|
|
1040
1047
|
const metaPath = drillStore.metaPath(driveSession.session_id);
|
|
@@ -1076,7 +1083,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1076
1083
|
},
|
|
1077
1084
|
];
|
|
1078
1085
|
fs.writeFileSync(metaPath, JSON.stringify(fabricatedMeta, null, 2));
|
|
1079
|
-
const drillReport = drillStore.sessionDoctor(20);
|
|
1086
|
+
const drillReport = await drillStore.sessionDoctor(20);
|
|
1080
1087
|
const entry = drillReport.findings.open_evidence_sessions.find((e) => e.session_id === driveSession.session_id);
|
|
1081
1088
|
assert.ok(entry, "open_evidence_sessions must include the fabricated session");
|
|
1082
1089
|
assert.equal(entry?.open_evidence_items, 3);
|
|
@@ -1097,7 +1104,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1097
1104
|
data_dir: smokeTmpDir("budget-warning"),
|
|
1098
1105
|
budget: { ...config.budget, max_session_cost_usd: 20 },
|
|
1099
1106
|
});
|
|
1100
|
-
const budgetSession = budgetStore.init("budget warning fixture", "operator", []);
|
|
1107
|
+
const budgetSession = await budgetStore.init("budget warning fixture", "operator", []);
|
|
1101
1108
|
// Verify init snapshotted the ceiling.
|
|
1102
1109
|
const initial = budgetStore.read(budgetSession.session_id);
|
|
1103
1110
|
assert.equal(initial.cost_ceiling_usd, 20, "cost_ceiling_usd must snapshot config at init");
|
|
@@ -1126,7 +1133,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1126
1133
|
const threshold = ceilingForCheck * 0.75;
|
|
1127
1134
|
assert.equal(cumulative1 >= threshold, true, "fixture must cross 75% threshold");
|
|
1128
1135
|
assert.equal(seededMeta.budget_warning_emitted, false, "warning must not have fired yet");
|
|
1129
|
-
budgetStore.markBudgetWarningEmitted(budgetSession.session_id);
|
|
1136
|
+
await budgetStore.markBudgetWarningEmitted(budgetSession.session_id);
|
|
1130
1137
|
const afterFirst = budgetStore.read(budgetSession.session_id);
|
|
1131
1138
|
assert.equal(afterFirst.budget_warning_emitted, true, "markBudgetWarningEmitted must persist the one-shot guard");
|
|
1132
1139
|
// Round 2: cumulative = 18 (still over threshold). Emit guard must
|
|
@@ -1149,7 +1156,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1149
1156
|
data_dir: smokeTmpDir("budget-warning-no-ceiling"),
|
|
1150
1157
|
budget: { ...config.budget, max_session_cost_usd: undefined },
|
|
1151
1158
|
});
|
|
1152
|
-
const noCeilingSession = noCeilingStore.init("no ceiling fixture", "operator", []);
|
|
1159
|
+
const noCeilingSession = await noCeilingStore.init("no ceiling fixture", "operator", []);
|
|
1153
1160
|
const noCeilingMeta = noCeilingStore.read(noCeilingSession.session_id);
|
|
1154
1161
|
assert.equal(noCeilingMeta.cost_ceiling_usd, null, "cost_ceiling_usd must be null when config.max_session_cost_usd is unset");
|
|
1155
1162
|
console.log("[smoke] budget_warning_emit_test: PASS");
|
|
@@ -1213,10 +1220,10 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1213
1220
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1214
1221
|
const fsModule = await import("node:fs");
|
|
1215
1222
|
const seqStoreA = new SessionStore(config);
|
|
1216
|
-
const seqMeta = seqStoreA.init("seq-durability-test", "operator", []);
|
|
1223
|
+
const seqMeta = await seqStoreA.init("seq-durability-test", "operator", []);
|
|
1217
1224
|
const seqId = seqMeta.session_id;
|
|
1218
1225
|
// Emit a normal event.
|
|
1219
|
-
seqStoreA.appendEvent({
|
|
1226
|
+
await seqStoreA.appendEvent({
|
|
1220
1227
|
type: "session.heartbeat",
|
|
1221
1228
|
session_id: seqId,
|
|
1222
1229
|
message: "first",
|
|
@@ -1231,7 +1238,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1231
1238
|
interceptorFired = true;
|
|
1232
1239
|
throw new Error("simulated EIO");
|
|
1233
1240
|
});
|
|
1234
|
-
seqStoreA.appendEvent({
|
|
1241
|
+
await seqStoreA.appendEvent({
|
|
1235
1242
|
type: "session.heartbeat",
|
|
1236
1243
|
session_id: seqId,
|
|
1237
1244
|
message: "should-fail",
|
|
@@ -1239,7 +1246,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1239
1246
|
// Restore fs and try again — the intended seq (2) must still be
|
|
1240
1247
|
// available, not skipped to 3.
|
|
1241
1248
|
fsModule.default.appendFileSync = realAppend;
|
|
1242
|
-
seqStoreA.appendEvent({
|
|
1249
|
+
await seqStoreA.appendEvent({
|
|
1243
1250
|
type: "session.heartbeat",
|
|
1244
1251
|
session_id: seqId,
|
|
1245
1252
|
message: "after-recovery",
|
|
@@ -1250,7 +1257,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1250
1257
|
// Restart simulation: fresh SessionStore reads from disk and the next
|
|
1251
1258
|
// seq should be 3 (current line count + 1).
|
|
1252
1259
|
const seqStoreB = new SessionStore(config);
|
|
1253
|
-
seqStoreB.appendEvent({
|
|
1260
|
+
await seqStoreB.appendEvent({
|
|
1254
1261
|
type: "session.heartbeat",
|
|
1255
1262
|
session_id: seqId,
|
|
1256
1263
|
message: "after-restart",
|
|
@@ -1269,9 +1276,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1269
1276
|
{
|
|
1270
1277
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1271
1278
|
const flightStore = new SessionStore(config);
|
|
1272
|
-
const flightMeta = flightStore.init("mark-in-flight-guard-test", "operator", []);
|
|
1279
|
+
const flightMeta = await flightStore.init("mark-in-flight-guard-test", "operator", []);
|
|
1273
1280
|
const flightId = flightMeta.session_id;
|
|
1274
|
-
flightStore.markInFlight(flightId, {
|
|
1281
|
+
await flightStore.markInFlight(flightId, {
|
|
1275
1282
|
round: 1,
|
|
1276
1283
|
peers: [...PEERS],
|
|
1277
1284
|
started_at: new Date().toISOString(),
|
|
@@ -1284,7 +1291,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1284
1291
|
});
|
|
1285
1292
|
let secondMarkRejected = false;
|
|
1286
1293
|
try {
|
|
1287
|
-
flightStore.markInFlight(flightId, {
|
|
1294
|
+
await flightStore.markInFlight(flightId, {
|
|
1288
1295
|
round: 2,
|
|
1289
1296
|
peers: [...PEERS],
|
|
1290
1297
|
started_at: new Date().toISOString(),
|
|
@@ -1358,13 +1365,13 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1358
1365
|
{
|
|
1359
1366
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1360
1367
|
const staleStore = new SessionStore(config);
|
|
1361
|
-
const staleMeta = staleStore.init("stale-session-abort-test", "operator", []);
|
|
1368
|
+
const staleMeta = await staleStore.init("stale-session-abort-test", "operator", []);
|
|
1362
1369
|
const staleId = staleMeta.session_id;
|
|
1363
1370
|
const staleMetaPath = staleStore.metaPath(staleId);
|
|
1364
1371
|
const staleRaw = JSON.parse(fs.readFileSync(staleMetaPath, "utf8"));
|
|
1365
1372
|
staleRaw.updated_at = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
|
1366
1373
|
fs.writeFileSync(staleMetaPath, JSON.stringify(staleRaw, null, 2), "utf8");
|
|
1367
|
-
const sweep = staleStore.abortStaleSessions();
|
|
1374
|
+
const sweep = await staleStore.abortStaleSessions();
|
|
1368
1375
|
assert.ok(sweep.aborted >= 1, `abortStaleSessions must abort ≥1 stale session, got ${sweep.aborted}`);
|
|
1369
1376
|
const after = staleStore.read(staleId);
|
|
1370
1377
|
assert.equal(after.outcome, "aborted");
|
|
@@ -1376,9 +1383,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1376
1383
|
{
|
|
1377
1384
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1378
1385
|
const inflightStore = new SessionStore(config);
|
|
1379
|
-
const inflightMeta = inflightStore.init("stale-session-skip-test", "operator", []);
|
|
1386
|
+
const inflightMeta = await inflightStore.init("stale-session-skip-test", "operator", []);
|
|
1380
1387
|
const inflightId = inflightMeta.session_id;
|
|
1381
|
-
inflightStore.markInFlight(inflightId, {
|
|
1388
|
+
await inflightStore.markInFlight(inflightId, {
|
|
1382
1389
|
round: 1,
|
|
1383
1390
|
peers: [...PEERS],
|
|
1384
1391
|
started_at: new Date().toISOString(),
|
|
@@ -1393,7 +1400,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1393
1400
|
const inflightRaw = JSON.parse(fs.readFileSync(inflightMetaPath, "utf8"));
|
|
1394
1401
|
inflightRaw.updated_at = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
|
1395
1402
|
fs.writeFileSync(inflightMetaPath, JSON.stringify(inflightRaw, null, 2), "utf8");
|
|
1396
|
-
const sweep = inflightStore.abortStaleSessions();
|
|
1403
|
+
const sweep = await inflightStore.abortStaleSessions();
|
|
1397
1404
|
const after = inflightStore.read(inflightId);
|
|
1398
1405
|
assert.equal(after.outcome, undefined, `in-flight session must NOT be aborted by stale sweep (got outcome=${after.outcome}, sweep=${JSON.stringify(sweep)})`);
|
|
1399
1406
|
console.log("[smoke] stale_session_skipped_when_running_test: PASS");
|
|
@@ -1831,7 +1838,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1831
1838
|
const meta = tpOrch.store.read(sessionId);
|
|
1832
1839
|
meta.evidence_checklist = fixtureItems;
|
|
1833
1840
|
fs.writeFileSync(path.join(tpConfig.data_dir, "sessions", sessionId, "meta.json"), JSON.stringify(meta, null, 2));
|
|
1834
|
-
const ad = tpOrch.store.runEvidenceChecklistAddressDetection(sessionId, FIXTURE_ROUND);
|
|
1841
|
+
const ad = await tpOrch.store.runEvidenceChecklistAddressDetection(sessionId, FIXTURE_ROUND);
|
|
1835
1842
|
// (1) The open item with last_round===currentRound MUST NOT appear under
|
|
1836
1843
|
// peer_resurfaced_terminal. This is the regression the buggy
|
|
1837
1844
|
// truthy-OR predicate would have triggered.
|
|
@@ -1934,7 +1941,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1934
1941
|
});
|
|
1935
1942
|
const item = opRound1.session.evidence_checklist?.[0];
|
|
1936
1943
|
assert.ok(item, "R1 must produce a checklist item");
|
|
1937
|
-
const result = opOrch.store.setEvidenceChecklistItemStatus(opRound1.session.session_id, item.id, "satisfied", { note: "smoke verified manually", by: "operator" });
|
|
1944
|
+
const result = await opOrch.store.setEvidenceChecklistItemStatus(opRound1.session.session_id, item.id, "satisfied", { note: "smoke verified manually", by: "operator" });
|
|
1938
1945
|
assert.equal(result.item.status, "satisfied", "mutator must set status to satisfied");
|
|
1939
1946
|
assert.equal(result.history_entry.from, "open");
|
|
1940
1947
|
assert.equal(result.history_entry.to, "satisfied");
|
|
@@ -1956,7 +1963,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1956
1963
|
// type-system level: the mutator's signature excludes "addressed". We
|
|
1957
1964
|
// assert that calling setEvidenceChecklistItemStatus with "deferred"
|
|
1958
1965
|
// works as a different terminal transition.
|
|
1959
|
-
const result2 = opOrch.store.setEvidenceChecklistItemStatus(opRound1.session.session_id, item.id, "deferred", { note: "retract satisfied, defer instead", by: "operator" });
|
|
1966
|
+
const result2 = await opOrch.store.setEvidenceChecklistItemStatus(opRound1.session.session_id, item.id, "deferred", { note: "retract satisfied, defer instead", by: "operator" });
|
|
1960
1967
|
assert.equal(result2.item.status, "deferred");
|
|
1961
1968
|
assert.equal(result2.history_entry.from, "satisfied");
|
|
1962
1969
|
assert.equal(result2.history_entry.to, "deferred");
|
|
@@ -1994,6 +2001,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1994
2001
|
caller: "operator",
|
|
1995
2002
|
peers: ["codex"],
|
|
1996
2003
|
});
|
|
2004
|
+
await phOrch.store.flushPendingEvents();
|
|
1997
2005
|
const metrics = phOrch.store.metrics();
|
|
1998
2006
|
const perPeer = metrics.per_peer_health;
|
|
1999
2007
|
assert.ok(perPeer, "metrics must include per_peer_health");
|
|
@@ -2835,7 +2843,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2835
2843
|
// would leave the durable log empty.
|
|
2836
2844
|
const holder = {};
|
|
2837
2845
|
const rollupOrch = new CrossReviewOrchestrator(cfg, (event) => {
|
|
2838
|
-
holder.orch?.store.appendEvent(event);
|
|
2846
|
+
void holder.orch?.store.appendEvent(event);
|
|
2839
2847
|
});
|
|
2840
2848
|
holder.orch = rollupOrch;
|
|
2841
2849
|
const r1 = await rollupOrch.askPeers({
|
|
@@ -2851,6 +2859,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2851
2859
|
caller: "operator",
|
|
2852
2860
|
peers: ["claude"],
|
|
2853
2861
|
});
|
|
2862
|
+
// v4.1.0: emit pipeline uses `void store.appendEvent(...)` (fire-
|
|
2863
|
+
// and-forget). Flush pending writes before reading the events file.
|
|
2864
|
+
await rollupOrch.store.flushPendingEvents();
|
|
2854
2865
|
const rollup = rollupOrch.store.aggregateShadowJudgments();
|
|
2855
2866
|
assert.ok(rollup.decisions_total >= 1, `aggregate must record at least 1 shadow decision (got ${rollup.decisions_total})`);
|
|
2856
2867
|
assert.ok(rollup.would_promote_total >= 1, `aggregate must record at least 1 would_promote (got ${rollup.would_promote_total})`);
|
|
@@ -2860,7 +2871,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2860
2871
|
assert.ok(claudeStats.would_promote >= 1);
|
|
2861
2872
|
assert.ok((claudeStats.by_confidence.verified ?? 0) >= 1);
|
|
2862
2873
|
assert.ok(claudeStats.first_seen_at && claudeStats.last_seen_at);
|
|
2863
|
-
const metrics = rollupOrch.store.metrics();
|
|
2874
|
+
const metrics = rollupOrch.store.metrics(); // already flushed above
|
|
2864
2875
|
assert.ok(metrics.shadow_judgment, "metrics().shadow_judgment must be present");
|
|
2865
2876
|
assert.equal(metrics.shadow_judgment.decisions_total, rollup.decisions_total);
|
|
2866
2877
|
console.log("[smoke] metrics_shadow_judgment_rollup_test: PASS");
|
|
@@ -2898,7 +2909,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2898
2909
|
const holder = {};
|
|
2899
2910
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
2900
2911
|
events.push({ type: e.type, data: e.data });
|
|
2901
|
-
holder.orch?.store.appendEvent(e);
|
|
2912
|
+
void holder.orch?.store.appendEvent(e);
|
|
2902
2913
|
});
|
|
2903
2914
|
holder.orch = orch;
|
|
2904
2915
|
// Drive runUntilUnanimous with FORCE_DRIFT (lead generation triggers
|
|
@@ -2942,7 +2953,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2942
2953
|
const holder = {};
|
|
2943
2954
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
2944
2955
|
events.push({ type: e.type, data: e.data });
|
|
2945
|
-
holder.orch?.store.appendEvent(e);
|
|
2956
|
+
void holder.orch?.store.appendEvent(e);
|
|
2946
2957
|
});
|
|
2947
2958
|
holder.orch = orch;
|
|
2948
2959
|
const result = await orch.runUntilUnanimous({
|
|
@@ -2980,7 +2991,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2980
2991
|
const holder = {};
|
|
2981
2992
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
2982
2993
|
events.push({ type: e.type, data: e.data });
|
|
2983
|
-
holder.orch?.store.appendEvent(e);
|
|
2994
|
+
void holder.orch?.store.appendEvent(e);
|
|
2984
2995
|
});
|
|
2985
2996
|
holder.orch = orch;
|
|
2986
2997
|
const result = await orch.runUntilUnanimous({
|
|
@@ -3017,7 +3028,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3017
3028
|
const holder = {};
|
|
3018
3029
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
3019
3030
|
events.push({ type: e.type, data: e.data });
|
|
3020
|
-
holder.orch?.store.appendEvent(e);
|
|
3031
|
+
void holder.orch?.store.appendEvent(e);
|
|
3021
3032
|
});
|
|
3022
3033
|
holder.orch = orch;
|
|
3023
3034
|
await orch.runUntilUnanimous({
|
|
@@ -3052,7 +3063,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3052
3063
|
};
|
|
3053
3064
|
const holder = {};
|
|
3054
3065
|
const aeOrch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
3055
|
-
holder.orch?.store.appendEvent(e);
|
|
3066
|
+
void holder.orch?.store.appendEvent(e);
|
|
3056
3067
|
});
|
|
3057
3068
|
holder.orch = aeOrch;
|
|
3058
3069
|
// Init session, attach 2 evidence files, run askPeers, read R1 prompt.
|
|
@@ -3071,12 +3082,12 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3071
3082
|
const sessionId = initial.session.session_id;
|
|
3072
3083
|
// The first askPeers above completed R1 already without attachments
|
|
3073
3084
|
// — that is the "before" baseline. Now attach files and run R2.
|
|
3074
|
-
aeOrch.store.attachEvidence(sessionId, {
|
|
3085
|
+
await aeOrch.store.attachEvidence(sessionId, {
|
|
3075
3086
|
label: "gates-output",
|
|
3076
3087
|
content: "EXIT 0 typecheck\nEXIT 0 lint\nEXIT 0 build\nEXIT 0 smoke 41/41 PASS\n",
|
|
3077
3088
|
extension: "log",
|
|
3078
3089
|
});
|
|
3079
|
-
aeOrch.store.attachEvidence(sessionId, {
|
|
3090
|
+
await aeOrch.store.attachEvidence(sessionId, {
|
|
3080
3091
|
label: "diff-stat",
|
|
3081
3092
|
content: " path/to/file.ts | +12/-3\n 1 file changed, 12 insertions, 3 deletions\n",
|
|
3082
3093
|
extension: "txt",
|
|
@@ -3132,7 +3143,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3132
3143
|
const sessionId = initial.session.session_id;
|
|
3133
3144
|
const big = "X".repeat(30_000);
|
|
3134
3145
|
for (let i = 0; i < 4; i++) {
|
|
3135
|
-
capOrch.store.attachEvidence(sessionId, {
|
|
3146
|
+
await capOrch.store.attachEvidence(sessionId, {
|
|
3136
3147
|
label: `att-${i}`,
|
|
3137
3148
|
content: big,
|
|
3138
3149
|
extension: "txt",
|
|
@@ -3316,7 +3327,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3316
3327
|
};
|
|
3317
3328
|
const holder = {};
|
|
3318
3329
|
const prOrch = new CrossReviewOrchestrator(cfg, (event) => {
|
|
3319
|
-
holder.orch?.store.appendEvent(event);
|
|
3330
|
+
void holder.orch?.store.appendEvent(event);
|
|
3320
3331
|
});
|
|
3321
3332
|
holder.orch = prOrch;
|
|
3322
3333
|
// R1: produce a NEEDS_EVIDENCE ask. R2: ask resurfaces (so far ground
|
|
@@ -3351,6 +3362,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3351
3362
|
caller: "operator",
|
|
3352
3363
|
peers: ["claude"],
|
|
3353
3364
|
});
|
|
3365
|
+
await prOrch.store.flushPendingEvents();
|
|
3354
3366
|
const report = prOrch.store.computeJudgmentPrecisionReport();
|
|
3355
3367
|
assert.ok(report.decisions_total >= 1, `at least 1 decision recorded`);
|
|
3356
3368
|
const claudeStats = report.by_judge_peer.claude;
|
|
@@ -3400,7 +3412,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3400
3412
|
const holder = {};
|
|
3401
3413
|
const acOrch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
3402
3414
|
events.push(e.type);
|
|
3403
|
-
holder.orch?.store.appendEvent(e);
|
|
3415
|
+
void holder.orch?.store.appendEvent(e);
|
|
3404
3416
|
});
|
|
3405
3417
|
holder.orch = acOrch;
|
|
3406
3418
|
// R1: produce a NEEDS_EVIDENCE ask via FORCE_NEEDS_EVIDENCE.
|
|
@@ -3470,9 +3482,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3470
3482
|
peers: ["claude"],
|
|
3471
3483
|
});
|
|
3472
3484
|
const originalId = initial.session.session_id;
|
|
3473
|
-
cvOrch.store.finalize(originalId, "max-rounds", "test_finalize");
|
|
3485
|
+
await cvOrch.store.finalize(originalId, "max-rounds", "test_finalize");
|
|
3474
3486
|
// Contest it.
|
|
3475
|
-
const contestation = cvOrch.store.contestVerdict({
|
|
3487
|
+
const contestation = await cvOrch.store.contestVerdict({
|
|
3476
3488
|
session_id: originalId,
|
|
3477
3489
|
reason: "Caller disagrees with the verdict; new evidence has surfaced.",
|
|
3478
3490
|
new_task: "Contest test re-deliberation",
|
|
@@ -3491,7 +3503,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3491
3503
|
// Double-contesting must throw.
|
|
3492
3504
|
let threw = null;
|
|
3493
3505
|
try {
|
|
3494
|
-
cvOrch.store.contestVerdict({
|
|
3506
|
+
await cvOrch.store.contestVerdict({
|
|
3495
3507
|
session_id: originalId,
|
|
3496
3508
|
reason: "Trying to contest twice",
|
|
3497
3509
|
new_task: "Should not happen",
|
|
@@ -3513,7 +3525,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3513
3525
|
// but typically the session still has no outcome until finalize.)
|
|
3514
3526
|
threw = null;
|
|
3515
3527
|
try {
|
|
3516
|
-
cvOrch.store.contestVerdict({
|
|
3528
|
+
await cvOrch.store.contestVerdict({
|
|
3517
3529
|
session_id: inFlight.session.session_id,
|
|
3518
3530
|
reason: "in-flight should reject",
|
|
3519
3531
|
new_task: "should not happen",
|
|
@@ -3919,8 +3931,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3919
3931
|
};
|
|
3920
3932
|
// Scenario A: finalize("converged") on a session whose latest round
|
|
3921
3933
|
// did NOT converge MUST be rejected with a structured error code.
|
|
3922
|
-
const sess = invariantStore.init("invariant-fixture", "operator", []);
|
|
3923
|
-
invariantStore.appendRound(sess.session_id, {
|
|
3934
|
+
const sess = await invariantStore.init("invariant-fixture", "operator", []);
|
|
3935
|
+
await invariantStore.appendRound(sess.session_id, {
|
|
3924
3936
|
caller_status: "READY",
|
|
3925
3937
|
prompt_file: "round-1-prompt.md",
|
|
3926
3938
|
peers: [],
|
|
@@ -3948,7 +3960,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3948
3960
|
});
|
|
3949
3961
|
let rejected = null;
|
|
3950
3962
|
try {
|
|
3951
|
-
invariantStore.finalize(sess.session_id, "converged", "unanimous_ready");
|
|
3963
|
+
await invariantStore.finalize(sess.session_id, "converged", "unanimous_ready");
|
|
3952
3964
|
}
|
|
3953
3965
|
catch (err) {
|
|
3954
3966
|
rejected = err;
|
|
@@ -3959,8 +3971,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3959
3971
|
assert.equal(invariantStore.read(sess.session_id).outcome, undefined, "finalize-reject MUST NOT mutate meta.outcome");
|
|
3960
3972
|
// Scenario B: finalize("converged") on a session whose latest round
|
|
3961
3973
|
// DID converge succeeds and leaves a consistent meta.
|
|
3962
|
-
const sess2 = invariantStore.init("invariant-fixture-2", "operator", []);
|
|
3963
|
-
invariantStore.appendRound(sess2.session_id, {
|
|
3974
|
+
const sess2 = await invariantStore.init("invariant-fixture-2", "operator", []);
|
|
3975
|
+
await invariantStore.appendRound(sess2.session_id, {
|
|
3964
3976
|
caller_status: "READY",
|
|
3965
3977
|
prompt_file: "round-1-prompt.md",
|
|
3966
3978
|
peers: [],
|
|
@@ -3986,14 +3998,14 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3986
3998
|
},
|
|
3987
3999
|
started_at: new Date().toISOString(),
|
|
3988
4000
|
});
|
|
3989
|
-
const finalized = invariantStore.finalize(sess2.session_id, "converged", "unanimous_ready");
|
|
4001
|
+
const finalized = await invariantStore.finalize(sess2.session_id, "converged", "unanimous_ready");
|
|
3990
4002
|
assert.equal(finalized.outcome, "converged");
|
|
3991
4003
|
assert.equal(finalized.convergence_health?.state, "converged");
|
|
3992
4004
|
// Scenario C: appendRound on a finalized session MUST throw with the
|
|
3993
4005
|
// structured code.
|
|
3994
4006
|
let appendRejected = null;
|
|
3995
4007
|
try {
|
|
3996
|
-
invariantStore.appendRound(sess2.session_id, {
|
|
4008
|
+
await invariantStore.appendRound(sess2.session_id, {
|
|
3997
4009
|
caller_status: "READY",
|
|
3998
4010
|
prompt_file: "round-2-prompt.md",
|
|
3999
4011
|
peers: [],
|
|
@@ -4932,9 +4944,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
4932
4944
|
entries: [],
|
|
4933
4945
|
};
|
|
4934
4946
|
fs.mkdirSync(path.join(config.data_dir, "sessions", manifestSession), { recursive: true });
|
|
4935
|
-
writeCacheManifest(config.data_dir, manifestSession, manifestData);
|
|
4947
|
+
await writeCacheManifest(config.data_dir, manifestSession, manifestData);
|
|
4936
4948
|
for (let i = 0; i < 5; i += 1) {
|
|
4937
|
-
appendCacheManifestEntry(config.data_dir, manifestSession, {
|
|
4949
|
+
await appendCacheManifestEntry(config.data_dir, manifestSession, {
|
|
4938
4950
|
ts: new Date().toISOString(),
|
|
4939
4951
|
round: i + 1,
|
|
4940
4952
|
peer: "codex",
|
|
@@ -5283,7 +5295,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
5283
5295
|
/this\.store\.finalize\([\s\S]{0,100}"max-rounds"[\s\S]{0,100}"circular_max_rotations_exceeded"/.test(orchSrc), "v2.25.0 / circular_mode: orchestrator finalizes with outcome=max-rounds + reason=circular_max_rotations_exceeded");
|
|
5284
5296
|
// (9) Meta carries circular_state with the expected shape.
|
|
5285
5297
|
assert.ok(/circular_state\?:\s*\{[\s\S]{0,300}rotation_order:\s*PeerId\[\][\s\S]{0,300}consecutive_no_change_count:\s*number[\s\S]{0,300}last_revision_round:\s*number\s*\|\s*null/.test(typesSrc), "v2.25.0 / circular_mode: SessionMeta.circular_state declares {rotation_order, consecutive_no_change_count, last_revision_round}");
|
|
5286
|
-
assert.ok(/setCircularState\(/.test(storeSrc) && /meta\.circular_state\s*=\s*state/.test(storeSrc), "v2.25.0 / circular_mode: SessionStore.setCircularState() persists circular_state under session lock");
|
|
5298
|
+
assert.ok(/setCircularState\(/.test(storeSrc) && /meta\.circular_state\s*=\s*state/.test(storeSrc), "v2.25.0 / circular_mode: await SessionStore.setCircularState() persists circular_state under session lock");
|
|
5287
5299
|
// (10) MCP tool schemas accept "circular".
|
|
5288
5300
|
const circularEnumOccurrences = (mcpSrc.match(/z\.enum\(\["ship",\s*"review",\s*"circular"\]\)/g) ?? []).length;
|
|
5289
5301
|
assert.ok(circularEnumOccurrences >= 2, `v2.25.0 / circular_mode: MCP schemas in mcp/server.ts must include z.enum(["ship","review","circular"]) for both run_until_unanimous + session_start_unanimous (found ${circularEnumOccurrences} occurrences)`);
|
|
@@ -5862,12 +5874,12 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
5862
5874
|
fs.mkdirSync(path.join(repairCfg.data_dir, "sessions", corruptId), { recursive: true });
|
|
5863
5875
|
fs.writeFileSync(path.join(repairCfg.data_dir, "sessions", corruptId, "meta.json"), JSON.stringify(corruptMeta, null, 2));
|
|
5864
5876
|
// repair=false (default) — read-only, the contradiction is NOT touched.
|
|
5865
|
-
const readOnly = repairStore.sessionDoctor(20, false, false);
|
|
5877
|
+
const readOnly = await repairStore.sessionDoctor(20, false, false);
|
|
5866
5878
|
assert.equal(readOnly.repaired, undefined, "v3.6.0 / C: repair=false must NOT include a `repaired` array (stays read-only)");
|
|
5867
5879
|
const afterReadOnly = repairStore.read(corruptId);
|
|
5868
5880
|
assert.equal(afterReadOnly.convergence_health?.state, "blocked", "v3.6.0 / C: repair=false must leave the corrupt health state untouched");
|
|
5869
5881
|
// repair=true — the contradiction is recomputed from the latest round.
|
|
5870
|
-
const repaired = repairStore.sessionDoctor(20, false, true);
|
|
5882
|
+
const repaired = await repairStore.sessionDoctor(20, false, true);
|
|
5871
5883
|
assert.ok(Array.isArray(repaired.repaired) && repaired.repaired.length === 1, `v3.6.0 / C: repair=true must report exactly 1 repaired session, got ${repaired.repaired?.length}`);
|
|
5872
5884
|
assert.equal(repaired.repaired?.[0]?.session_id, corruptId);
|
|
5873
5885
|
assert.equal(repaired.repaired?.[0]?.from_health_state, "blocked");
|
|
@@ -5876,7 +5888,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
5876
5888
|
assert.equal(afterRepair.convergence_health?.state, "converged", "v3.6.0 / C: repair=true must recompute health to converged");
|
|
5877
5889
|
assert.ok(/v3\.6\.0 doctor repair/.test(afterRepair.convergence_health?.detail ?? ""), "v3.6.0 / C: repaired health detail must record the repair provenance");
|
|
5878
5890
|
// Idempotent — a second repair pass finds nothing to fix.
|
|
5879
|
-
const secondPass = repairStore.sessionDoctor(20, false, true);
|
|
5891
|
+
const secondPass = await repairStore.sessionDoctor(20, false, true);
|
|
5880
5892
|
assert.equal(secondPass.repaired?.length, 0, "v3.6.0 / C: repair must be idempotent — second pass repairs nothing");
|
|
5881
5893
|
console.log("[smoke] session_doctor_repair_test: PASS");
|
|
5882
5894
|
}
|
|
@@ -6210,6 +6222,94 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
6210
6222
|
assert.ok(!verifyScript.includes("node:child_process"), "v4.0.6 / F1: verify-registry-dist.mjs must not spawn npm/npm.cmd; Windows Node hardening rejects npm.cmd spawn.");
|
|
6211
6223
|
assert.ok(verifyScript.includes("https://registry.npmjs.org") && verifyScript.includes("fetch("), "v4.0.6 / F1: verify-registry-dist.mjs must query npm registry metadata directly.");
|
|
6212
6224
|
assert.ok(verifyScript.includes("AbortSignal.timeout(") && verifyScript.includes("FETCH_TIMEOUT_MS"), "v4.0.7 / F2: verify-registry-dist.mjs must bound the npm registry fetch with an explicit AbortSignal.timeout so a slow registry surfaces as a deterministic abort instead of hanging the workflow.");
|
|
6225
|
+
assert.ok(!verifyScript.includes("readFileSync") &&
|
|
6226
|
+
!verifyScript.includes("readFile(") &&
|
|
6227
|
+
verifyScript.includes("npm_package_name") &&
|
|
6228
|
+
verifyScript.includes("npm_package_version"), "v4.0.8 / F3: verify-registry-dist.mjs must not read package.json from disk; PACKAGE_NAME/PACKAGE_VERSION come from env (or npm-script-injected npm_package_name/version). Removing the file-data → fetch flow kills the recurring js/file-access-to-http CodeQL false positive at the source.");
|
|
6229
|
+
// v4.1.0 / F4: redactPrivateKeyBlocks must handle UNTERMINATED -----BEGIN
|
|
6230
|
+
// PRIVATE KEY----- blocks by redacting from BEGIN to end-of-string. Pre-
|
|
6231
|
+
// v4.1.0 leaked partial keys to events.ndjson when logs were truncated
|
|
6232
|
+
// mid-key. Empirical regression test:
|
|
6233
|
+
{
|
|
6234
|
+
const { redact } = await import("../src/security/redact.js");
|
|
6235
|
+
const truncatedKey = `error: -----BEGIN PRIVATE KEY-----\nMIIBVQIBADANBgkqhkiG9w0BAQEF...[TRUNCATED`;
|
|
6236
|
+
const redacted = redact(truncatedKey);
|
|
6237
|
+
assert.ok(redacted.includes("[REDACTED]"), "v4.1.0 / F4: redact() must emit [REDACTED] for unterminated -----BEGIN PRIVATE KEY----- blocks.");
|
|
6238
|
+
assert.ok(!redacted.includes("MIIBVQIBADANBgkqhkiG9w0BAQEF"), "v4.1.0 / F4: redact() must NOT leak the partial key body when the BEGIN marker has no matching END.");
|
|
6239
|
+
assert.equal(redact("just a normal log line"), "just a normal log line", "v4.1.0 / F4: redact() must be passthrough when no PRIVATE KEY markers are present.");
|
|
6240
|
+
console.log("[smoke] redact_unterminated_private_key_test: PASS");
|
|
6241
|
+
}
|
|
6242
|
+
// v4.1.0 / F5: writeJson + cache-manifest writeJsonAtomic must be
|
|
6243
|
+
// async AND no source file under src/ may contain the
|
|
6244
|
+
// `while (Date.now() - start < wait)` CPU-burning busy-wait pattern.
|
|
6245
|
+
// Pre-v4.1.0 this pattern blocked the event loop for up to 310 ms
|
|
6246
|
+
// under Windows AV stress. Codex R1 NEEDS_EVIDENCE catch on session
|
|
6247
|
+
// 059b0093 asked for repo-wide grep (the original pin only covered
|
|
6248
|
+
// session-store.ts and missed cache-manifest.ts:47).
|
|
6249
|
+
{
|
|
6250
|
+
const storeSrc = fs.readFileSync(path.join(process.cwd(), "src", "core", "session-store.ts"), "utf8");
|
|
6251
|
+
assert.ok(/async\s+function\s+writeJson\b/.test(storeSrc), "v4.1.0 / F5: writeJson must be declared `async function writeJson` in src/core/session-store.ts.");
|
|
6252
|
+
assert.ok(/new\s+Promise[\s\S]{0,80}?setTimeout/.test(storeSrc), "v4.1.0 / F5: writeJson must use a Promise-based async delay (`new Promise(r => setTimeout(r, wait))`) for retry backoff so the event loop remains responsive.");
|
|
6253
|
+
const cacheManifestSrc = fs.readFileSync(path.join(process.cwd(), "src", "core", "cache-manifest.ts"), "utf8");
|
|
6254
|
+
assert.ok(/async\s+function\s+writeJsonAtomic\b/.test(cacheManifestSrc), "v4.1.0 / F5: writeJsonAtomic in cache-manifest.ts must be `async function writeJsonAtomic`.");
|
|
6255
|
+
// Repo-wide invariant: scan every .ts under src/ for executable
|
|
6256
|
+
// busy-wait patterns. Strip block comments so the pin only checks
|
|
6257
|
+
// executable code (comments may reference the removed pattern).
|
|
6258
|
+
function walkTs(dir, out) {
|
|
6259
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
6260
|
+
const full = path.join(dir, entry.name);
|
|
6261
|
+
if (entry.isDirectory()) {
|
|
6262
|
+
walkTs(full, out);
|
|
6263
|
+
}
|
|
6264
|
+
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
6265
|
+
out.push(full);
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
return out;
|
|
6269
|
+
}
|
|
6270
|
+
const tsFiles = walkTs(path.join(process.cwd(), "src"), []);
|
|
6271
|
+
for (const file of tsFiles) {
|
|
6272
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
6273
|
+
// Strip /* ... */ and // ... so the busy-wait grep doesn't trip
|
|
6274
|
+
// on documentation. The lint runs on EXECUTABLE source only.
|
|
6275
|
+
const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\n)\s*\/\/[^\n]*/g, "$1");
|
|
6276
|
+
assert.ok(!/while\s*\(\s*Date\.now\(\)\s*-\s*\w+\s*<\s*\w+\s*\)\s*\{[^}]*\}/.test(stripped), `v4.1.0 / F5: ${path.relative(process.cwd(), file)} contains a synchronous busy-wait (\`while (Date.now() - start < wait) {}\`). Replace with \`await new Promise(r => setTimeout(r, wait))\`.`);
|
|
6277
|
+
assert.ok(!/Atomics\.wait\s*\(/.test(stripped), `v4.1.0 / F5: ${path.relative(process.cwd(), file)} uses Atomics.wait — also a synchronous block. Replace with a Promise+setTimeout.`);
|
|
6278
|
+
}
|
|
6279
|
+
console.log("[smoke] writeJson_async_no_busy_wait_test: PASS");
|
|
6280
|
+
}
|
|
6281
|
+
// v4.1.0 / F6: withSessionLock in src/core/session-store.ts must use
|
|
6282
|
+
// `proper-lockfile` (mkdir-atomic locking) instead of the pre-v4.1.0
|
|
6283
|
+
// `fs.openSync(lockPath, "wx")` followed by separate writeFileSync. The
|
|
6284
|
+
// pre-v4.1.0 pattern had a multi-process TOCTOU race: between open()
|
|
6285
|
+
// (creates empty inode) and writeFileSync (populates content) another
|
|
6286
|
+
// process could observe the empty lock, JSON-parse-fail, and rmSync the
|
|
6287
|
+
// lock — letting both processes enter the critical section. proper-
|
|
6288
|
+
// lockfile uses fs.mkdir which is atomic across NTFS and POSIX so the
|
|
6289
|
+
// lock comes into existence as a directory in one syscall with no
|
|
6290
|
+
// empty-window race. Additionally — Codex 059b0093 R1..R4 catches —
|
|
6291
|
+
// v4.1.0 must NEVER auto-remove a pre-v4.1.0 legacy regular `.lock`
|
|
6292
|
+
// file: under live cross-version v4.0/v4.1 operation, every
|
|
6293
|
+
// auto-clean variant raced a concurrent v4.0.x process's own
|
|
6294
|
+
// stale-removal-and-recreate path. v4.1.0 fails closed with a
|
|
6295
|
+
// clear remediation error; operator manually cleans up at upgrade.
|
|
6296
|
+
{
|
|
6297
|
+
const storeSrc = fs.readFileSync(path.join(process.cwd(), "src", "core", "session-store.ts"), "utf8");
|
|
6298
|
+
assert.ok(/from\s+["']proper-lockfile["']/.test(storeSrc), "v4.1.0 / F6: session-store.ts must import from 'proper-lockfile'.");
|
|
6299
|
+
assert.ok(/lockfile\.lock\(/.test(storeSrc), "v4.1.0 / F6: withSessionLock must call lockfile.lock() to acquire the session lock.");
|
|
6300
|
+
assert.ok(!/fs\.openSync\([^,]+,\s*["']wx["']\)/.test(storeSrc), "v4.1.0 / F6: session-store.ts must not contain the pre-v4.1.0 `fs.openSync(..., 'wx')` lock-acquire pattern that had the TOCTOU race.");
|
|
6301
|
+
assert.ok(/async\s+withSessionLock|withSessionLock[^(]*\([^)]*\)\s*:\s*Promise/.test(storeSrc), "v4.1.0 / F6: withSessionLock must be declared async (returning Promise<T>).");
|
|
6302
|
+
// Fail-closed migration policy: surface a remediation error
|
|
6303
|
+
// instead of removing legacy regular `.lock` files. The string
|
|
6304
|
+
// "detected a pre-v4.1.0 lock file" is the contract every caller
|
|
6305
|
+
// sees; the F6 smoke pin asserts it remains pinned.
|
|
6306
|
+
assert.ok(/detected a pre-v4\.1\.0 lock file/.test(storeSrc), "v4.1.0 / F6: session-store.ts must throw the fail-closed `detected a pre-v4.1.0 lock file` error when a legacy regular `.lock` file is observed (NO auto-removal under live cross-version operation).");
|
|
6307
|
+
// Belt-and-suspenders: no rmSync(lockfilePath, ...) anywhere in
|
|
6308
|
+
// withSessionLock — fail-closed implies the legacy file is
|
|
6309
|
+
// NEVER deleted by v4.1.0.
|
|
6310
|
+
assert.ok(!/fs\.rmSync\(\s*lockfilePath\b/.test(storeSrc), "v4.1.0 / F6: session-store.ts must NOT contain `fs.rmSync(lockfilePath, ...)` — v4.1.0 fails closed on legacy locks and never auto-removes them.");
|
|
6311
|
+
console.log("[smoke] session_lock_proper_lockfile_test: PASS");
|
|
6312
|
+
}
|
|
6213
6313
|
for (const required of ["dist", "shasum", "integrity", "tarball"]) {
|
|
6214
6314
|
assert.ok(verifyScript.includes(required), `v4.0.5 / AUDIT-6: verify-registry-dist.mjs must validate npm registry dist.${required}.`);
|
|
6215
6315
|
}
|