@lcv-ideas-software/cross-review 4.0.8 → 4.1.1
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 +204 -0
- package/README.md +74 -72
- package/dist/scripts/runtime-smoke.js +10 -3
- package/dist/scripts/runtime-smoke.js.map +1 -1
- package/dist/scripts/smoke.js +200 -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 +268 -157
- 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]");
|
|
@@ -569,6 +574,8 @@ assert.equal(checkConvergence(["codex", "claude"], "READY", [fakeReady("codex"),
|
|
|
569
574
|
assert.ok(/assert\.equal\(\s*roundState\.outcome,\s*"converged"/.test(runtimeSmokeSrc), 'v3.7.4 / runtime-smoke: must assert roundState.outcome === "converged"');
|
|
570
575
|
assert.ok(/assert\.equal\(\s*unanimousState\.outcome,\s*"converged"/.test(runtimeSmokeSrc), 'v3.7.4 / runtime-smoke: must assert unanimousState.outcome === "converged"');
|
|
571
576
|
assert.ok(/assert\.equal\(\s*cancelState\.outcome,\s*"aborted"/.test(runtimeSmokeSrc), 'v3.7.4 / runtime-smoke: must assert cancelState.outcome === "aborted"');
|
|
577
|
+
assert.ok(/TERMINAL_OUTCOMES\s*=\s*new Set\(\["converged", "aborted", "max-rounds"\]\)/.test(runtimeSmokeSrc), "runtime-smoke: pollUntilDone must return on terminal outcome even when job status lags.");
|
|
578
|
+
assert.ok(/POLL_TIMEOUT_MS\s*=\s*60_000/.test(runtimeSmokeSrc), "runtime-smoke: pollUntilDone must allow a 60s deadline so a slow-but-converged stub flow is not reported as timeout.");
|
|
572
579
|
console.log("[smoke] runtime_smoke_outcome_assert_test: PASS");
|
|
573
580
|
}
|
|
574
581
|
const probes = await orchestrator.probeAll();
|
|
@@ -601,28 +608,28 @@ assert.match(reviewPrompt, /not as instructions that override/);
|
|
|
601
608
|
assert.match(reviewPrompt, /OUT OF SCOPE/);
|
|
602
609
|
assert.ok(reviewPrompt.indexOf("## Review Focus") < reviewPrompt.indexOf("## Original Task"), "Review Focus must be front-loaded before the task body");
|
|
603
610
|
assert.doesNotMatch(reviewPrompt, /\/focus\s+services\/billing/);
|
|
604
|
-
const evidence = orchestrator.store.attachEvidence(result.session.session_id, {
|
|
611
|
+
const evidence = await orchestrator.store.attachEvidence(result.session.session_id, {
|
|
605
612
|
label: "smoke evidence",
|
|
606
613
|
content: "smoke evidence body",
|
|
607
614
|
content_type: "text/markdown",
|
|
608
615
|
extension: "md",
|
|
609
616
|
});
|
|
610
617
|
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, {
|
|
618
|
+
const escalated = await orchestrator.store.escalateToOperator(result.session.session_id, {
|
|
612
619
|
reason: "smoke operator escalation",
|
|
613
620
|
severity: "info",
|
|
614
621
|
});
|
|
615
622
|
assert.equal(escalated.operator_escalations?.at(-1)?.severity, "info");
|
|
616
|
-
const fresh = orchestrator.store.init("fresh unfinished smoke session", "operator", probes);
|
|
623
|
+
const fresh = await orchestrator.store.init("fresh unfinished smoke session", "operator", probes);
|
|
617
624
|
assert.equal(SWEEP_MIN_IDLE_MS, 24 * 60 * 60 * 1000);
|
|
618
|
-
assert.equal(orchestrator.store.sweepIdle(0, "aborted", "fresh_smoke_stale").length, 0);
|
|
625
|
+
assert.equal((await orchestrator.store.sweepIdle(0, "aborted", "fresh_smoke_stale")).length, 0);
|
|
619
626
|
assert.equal(orchestrator.store.read(fresh.session_id).outcome, undefined);
|
|
620
|
-
const stale = orchestrator.store.init("old unfinished smoke session", "operator", probes);
|
|
627
|
+
const stale = await orchestrator.store.init("old unfinished smoke session", "operator", probes);
|
|
621
628
|
const staleMetaPath = orchestrator.store.metaPath(stale.session_id);
|
|
622
629
|
const staleMeta = JSON.parse(fs.readFileSync(staleMetaPath, "utf8"));
|
|
623
630
|
staleMeta.updated_at = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
|
624
631
|
fs.writeFileSync(staleMetaPath, `${JSON.stringify(staleMeta, null, 2)}\n`, "utf8");
|
|
625
|
-
const swept = orchestrator.store.sweepIdle(0, "aborted", "smoke_stale");
|
|
632
|
+
const swept = await orchestrator.store.sweepIdle(0, "aborted", "smoke_stale");
|
|
626
633
|
assert.equal(swept.some((session) => session.session_id === stale.session_id), true);
|
|
627
634
|
assert.equal(orchestrator.store.read(stale.session_id).outcome, "aborted");
|
|
628
635
|
assert.equal(orchestrator.store.read(fresh.session_id).outcome, undefined);
|
|
@@ -861,8 +868,8 @@ assert.equal(untilStoppedDefaultBudget.converged, false);
|
|
|
861
868
|
assert.equal(untilStoppedDefaultBudget.session.outcome, "max-rounds");
|
|
862
869
|
assert.equal(untilStoppedDefaultBudget.session.outcome_reason, "budget_exceeded");
|
|
863
870
|
assert.equal(untilStoppedDefaultBudget.rounds, 1);
|
|
864
|
-
const recoverySession = orchestrator.store.init("interrupted smoke session", "operator", probes);
|
|
865
|
-
orchestrator.store.markInFlight(recoverySession.session_id, {
|
|
871
|
+
const recoverySession = await orchestrator.store.init("interrupted smoke session", "operator", probes);
|
|
872
|
+
await orchestrator.store.markInFlight(recoverySession.session_id, {
|
|
866
873
|
round: 1,
|
|
867
874
|
peers: ["codex"],
|
|
868
875
|
started_at: new Date().toISOString(),
|
|
@@ -873,7 +880,7 @@ orchestrator.store.markInFlight(recoverySession.session_id, {
|
|
|
873
880
|
reviewer_peers: ["codex"],
|
|
874
881
|
},
|
|
875
882
|
});
|
|
876
|
-
const recoveredInterrupted = orchestrator.store.recoverInterruptedSessions();
|
|
883
|
+
const recoveredInterrupted = await orchestrator.store.recoverInterruptedSessions();
|
|
877
884
|
assert.equal(recoveredInterrupted.some((session) => session.session_id === recoverySession.session_id), true);
|
|
878
885
|
assert.equal(orchestrator.store.read(recoverySession.session_id).control?.status, "recovered_after_restart");
|
|
879
886
|
const abortController = new AbortController();
|
|
@@ -900,6 +907,7 @@ const preflightBlocked = await preflightOrchestrator.askPeers({
|
|
|
900
907
|
assert.equal(preflightBlocked.converged, false);
|
|
901
908
|
assert.equal(preflightBlocked.round.rejected.at(-1)?.failure_class, "budget_preflight");
|
|
902
909
|
assert.equal(preflightBlocked.session.outcome_reason, "budget_preflight");
|
|
910
|
+
await orchestrator.store.flushPendingEvents();
|
|
903
911
|
const eventful = orchestrator.store.readEvents(formatRecovered.session.session_id);
|
|
904
912
|
assert.equal(eventful.some((event) => event.type === "round.completed"), true);
|
|
905
913
|
assert.equal(eventful.some((event) => event.type === "peer.token.delta"), true);
|
|
@@ -927,6 +935,7 @@ const directStreamChars = directStreamEvents
|
|
|
927
935
|
.reduce((total, event) => total + Number(event.data?.chars ?? 0), 0);
|
|
928
936
|
assert.equal(directStreamChars, directStubResult.text.length);
|
|
929
937
|
assert.deepEqual(eventful.map((event) => event.seq), eventful.map((_, index) => index + 1));
|
|
938
|
+
await orchestrator.store.flushPendingEvents();
|
|
930
939
|
const metrics = orchestrator.store.metrics();
|
|
931
940
|
assert.equal(metrics.fallback_events, 1);
|
|
932
941
|
assert.equal((metrics.peer_failures.cancelled ?? 0) >= 1, true);
|
|
@@ -940,8 +949,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
940
949
|
...config,
|
|
941
950
|
data_dir: smokeTmpDir("session-doctor"),
|
|
942
951
|
});
|
|
943
|
-
const doctorSession = doctorStore.init("doctor self-lead legacy fixture", "claude", []);
|
|
944
|
-
doctorStore.markInFlight(doctorSession.session_id, {
|
|
952
|
+
const doctorSession = await doctorStore.init("doctor self-lead legacy fixture", "claude", []);
|
|
953
|
+
await doctorStore.markInFlight(doctorSession.session_id, {
|
|
945
954
|
round: 1,
|
|
946
955
|
peers: ["codex"],
|
|
947
956
|
started_at: new Date().toISOString(),
|
|
@@ -955,25 +964,25 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
955
964
|
lead_peer: "claude",
|
|
956
965
|
},
|
|
957
966
|
});
|
|
958
|
-
doctorStore.appendEvent({
|
|
967
|
+
await doctorStore.appendEvent({
|
|
959
968
|
type: "peer.token.delta",
|
|
960
969
|
session_id: doctorSession.session_id,
|
|
961
970
|
round: 1,
|
|
962
971
|
peer: "codex",
|
|
963
972
|
data: { chars: 12 },
|
|
964
973
|
});
|
|
965
|
-
doctorStore.appendEvent({
|
|
974
|
+
await doctorStore.appendEvent({
|
|
966
975
|
type: "peer.token.completed",
|
|
967
976
|
session_id: doctorSession.session_id,
|
|
968
977
|
round: 1,
|
|
969
978
|
peer: "codex",
|
|
970
979
|
data: { chars: 12 },
|
|
971
980
|
});
|
|
972
|
-
const malformedSession = doctorStore.init("doctor malformed events fixture", "operator", []);
|
|
981
|
+
const malformedSession = await doctorStore.init("doctor malformed events fixture", "operator", []);
|
|
973
982
|
fs.writeFileSync(doctorStore.eventsPath(malformedSession.session_id), "{bad-json\n", "utf8");
|
|
974
983
|
// v2.22.0 (A.P2): self_lead_metadata is hidden by default. Pass
|
|
975
984
|
// includeLegacy=true here to preserve the original behavior assertion.
|
|
976
|
-
const doctor = doctorStore.sessionDoctor(5, true);
|
|
985
|
+
const doctor = await doctorStore.sessionDoctor(5, true);
|
|
977
986
|
assert.equal(doctor.totals.sessions, 2);
|
|
978
987
|
assert.equal(doctor.totals.open, 2);
|
|
979
988
|
assert.equal(doctor.totals.self_lead_metadata, 1);
|
|
@@ -996,8 +1005,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
996
1005
|
data_dir: smokeTmpDir("session-doctor-legacy"),
|
|
997
1006
|
});
|
|
998
1007
|
// Fixture: legacy self-lead session (caller==lead_peer)
|
|
999
|
-
const legacySession = filterStore.init("legacy self-lead fixture", "claude", []);
|
|
1000
|
-
filterStore.markInFlight(legacySession.session_id, {
|
|
1008
|
+
const legacySession = await filterStore.init("legacy self-lead fixture", "claude", []);
|
|
1009
|
+
await filterStore.markInFlight(legacySession.session_id, {
|
|
1001
1010
|
round: 1,
|
|
1002
1011
|
peers: ["codex"],
|
|
1003
1012
|
started_at: new Date().toISOString(),
|
|
@@ -1012,13 +1021,13 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1012
1021
|
},
|
|
1013
1022
|
});
|
|
1014
1023
|
// Default call: array hidden, totals visible, recommendation mentions include_legacy.
|
|
1015
|
-
const defaultReport = filterStore.sessionDoctor(20);
|
|
1024
|
+
const defaultReport = await filterStore.sessionDoctor(20);
|
|
1016
1025
|
assert.equal(defaultReport.totals.self_lead_metadata, 1, "totals.self_lead_metadata count must remain visible when array is suppressed");
|
|
1017
1026
|
assert.equal(defaultReport.findings.self_lead_metadata.length, 0, "findings.self_lead_metadata must be empty by default (legacy noise suppression)");
|
|
1018
1027
|
const hasIncludeLegacyHint = defaultReport.recommendations.some((rec) => rec.includes("include_legacy=true"));
|
|
1019
1028
|
assert.equal(hasIncludeLegacyHint, true, "recommendation must mention include_legacy=true when array is suppressed and count > 0");
|
|
1020
1029
|
// Explicit include_legacy=true: array populated.
|
|
1021
|
-
const inclusiveReport = filterStore.sessionDoctor(20, true);
|
|
1030
|
+
const inclusiveReport = await filterStore.sessionDoctor(20, true);
|
|
1022
1031
|
assert.equal(inclusiveReport.totals.self_lead_metadata, 1, "totals must match between default and inclusive calls");
|
|
1023
1032
|
assert.equal(inclusiveReport.findings.self_lead_metadata.length, 1, "findings.self_lead_metadata must be populated when include_legacy=true");
|
|
1024
1033
|
assert.equal(inclusiveReport.findings.self_lead_metadata[0]?.lead_peer, "claude");
|
|
@@ -1034,7 +1043,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1034
1043
|
...config,
|
|
1035
1044
|
data_dir: smokeTmpDir("session-doctor-drilldown"),
|
|
1036
1045
|
});
|
|
1037
|
-
const driveSession = drillStore.init("evidence drill-down fixture", "operator", []);
|
|
1046
|
+
const driveSession = await drillStore.init("evidence drill-down fixture", "operator", []);
|
|
1038
1047
|
// Fabricate evidence_checklist directly via meta path: 3 open items
|
|
1039
1048
|
// (codex x1, gemini x2), one of them chronic (round_count=4).
|
|
1040
1049
|
const metaPath = drillStore.metaPath(driveSession.session_id);
|
|
@@ -1076,7 +1085,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1076
1085
|
},
|
|
1077
1086
|
];
|
|
1078
1087
|
fs.writeFileSync(metaPath, JSON.stringify(fabricatedMeta, null, 2));
|
|
1079
|
-
const drillReport = drillStore.sessionDoctor(20);
|
|
1088
|
+
const drillReport = await drillStore.sessionDoctor(20);
|
|
1080
1089
|
const entry = drillReport.findings.open_evidence_sessions.find((e) => e.session_id === driveSession.session_id);
|
|
1081
1090
|
assert.ok(entry, "open_evidence_sessions must include the fabricated session");
|
|
1082
1091
|
assert.equal(entry?.open_evidence_items, 3);
|
|
@@ -1097,7 +1106,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1097
1106
|
data_dir: smokeTmpDir("budget-warning"),
|
|
1098
1107
|
budget: { ...config.budget, max_session_cost_usd: 20 },
|
|
1099
1108
|
});
|
|
1100
|
-
const budgetSession = budgetStore.init("budget warning fixture", "operator", []);
|
|
1109
|
+
const budgetSession = await budgetStore.init("budget warning fixture", "operator", []);
|
|
1101
1110
|
// Verify init snapshotted the ceiling.
|
|
1102
1111
|
const initial = budgetStore.read(budgetSession.session_id);
|
|
1103
1112
|
assert.equal(initial.cost_ceiling_usd, 20, "cost_ceiling_usd must snapshot config at init");
|
|
@@ -1126,7 +1135,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1126
1135
|
const threshold = ceilingForCheck * 0.75;
|
|
1127
1136
|
assert.equal(cumulative1 >= threshold, true, "fixture must cross 75% threshold");
|
|
1128
1137
|
assert.equal(seededMeta.budget_warning_emitted, false, "warning must not have fired yet");
|
|
1129
|
-
budgetStore.markBudgetWarningEmitted(budgetSession.session_id);
|
|
1138
|
+
await budgetStore.markBudgetWarningEmitted(budgetSession.session_id);
|
|
1130
1139
|
const afterFirst = budgetStore.read(budgetSession.session_id);
|
|
1131
1140
|
assert.equal(afterFirst.budget_warning_emitted, true, "markBudgetWarningEmitted must persist the one-shot guard");
|
|
1132
1141
|
// Round 2: cumulative = 18 (still over threshold). Emit guard must
|
|
@@ -1149,7 +1158,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1149
1158
|
data_dir: smokeTmpDir("budget-warning-no-ceiling"),
|
|
1150
1159
|
budget: { ...config.budget, max_session_cost_usd: undefined },
|
|
1151
1160
|
});
|
|
1152
|
-
const noCeilingSession = noCeilingStore.init("no ceiling fixture", "operator", []);
|
|
1161
|
+
const noCeilingSession = await noCeilingStore.init("no ceiling fixture", "operator", []);
|
|
1153
1162
|
const noCeilingMeta = noCeilingStore.read(noCeilingSession.session_id);
|
|
1154
1163
|
assert.equal(noCeilingMeta.cost_ceiling_usd, null, "cost_ceiling_usd must be null when config.max_session_cost_usd is unset");
|
|
1155
1164
|
console.log("[smoke] budget_warning_emit_test: PASS");
|
|
@@ -1213,10 +1222,10 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1213
1222
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1214
1223
|
const fsModule = await import("node:fs");
|
|
1215
1224
|
const seqStoreA = new SessionStore(config);
|
|
1216
|
-
const seqMeta = seqStoreA.init("seq-durability-test", "operator", []);
|
|
1225
|
+
const seqMeta = await seqStoreA.init("seq-durability-test", "operator", []);
|
|
1217
1226
|
const seqId = seqMeta.session_id;
|
|
1218
1227
|
// Emit a normal event.
|
|
1219
|
-
seqStoreA.appendEvent({
|
|
1228
|
+
await seqStoreA.appendEvent({
|
|
1220
1229
|
type: "session.heartbeat",
|
|
1221
1230
|
session_id: seqId,
|
|
1222
1231
|
message: "first",
|
|
@@ -1231,7 +1240,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1231
1240
|
interceptorFired = true;
|
|
1232
1241
|
throw new Error("simulated EIO");
|
|
1233
1242
|
});
|
|
1234
|
-
seqStoreA.appendEvent({
|
|
1243
|
+
await seqStoreA.appendEvent({
|
|
1235
1244
|
type: "session.heartbeat",
|
|
1236
1245
|
session_id: seqId,
|
|
1237
1246
|
message: "should-fail",
|
|
@@ -1239,7 +1248,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1239
1248
|
// Restore fs and try again — the intended seq (2) must still be
|
|
1240
1249
|
// available, not skipped to 3.
|
|
1241
1250
|
fsModule.default.appendFileSync = realAppend;
|
|
1242
|
-
seqStoreA.appendEvent({
|
|
1251
|
+
await seqStoreA.appendEvent({
|
|
1243
1252
|
type: "session.heartbeat",
|
|
1244
1253
|
session_id: seqId,
|
|
1245
1254
|
message: "after-recovery",
|
|
@@ -1250,7 +1259,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1250
1259
|
// Restart simulation: fresh SessionStore reads from disk and the next
|
|
1251
1260
|
// seq should be 3 (current line count + 1).
|
|
1252
1261
|
const seqStoreB = new SessionStore(config);
|
|
1253
|
-
seqStoreB.appendEvent({
|
|
1262
|
+
await seqStoreB.appendEvent({
|
|
1254
1263
|
type: "session.heartbeat",
|
|
1255
1264
|
session_id: seqId,
|
|
1256
1265
|
message: "after-restart",
|
|
@@ -1269,9 +1278,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1269
1278
|
{
|
|
1270
1279
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1271
1280
|
const flightStore = new SessionStore(config);
|
|
1272
|
-
const flightMeta = flightStore.init("mark-in-flight-guard-test", "operator", []);
|
|
1281
|
+
const flightMeta = await flightStore.init("mark-in-flight-guard-test", "operator", []);
|
|
1273
1282
|
const flightId = flightMeta.session_id;
|
|
1274
|
-
flightStore.markInFlight(flightId, {
|
|
1283
|
+
await flightStore.markInFlight(flightId, {
|
|
1275
1284
|
round: 1,
|
|
1276
1285
|
peers: [...PEERS],
|
|
1277
1286
|
started_at: new Date().toISOString(),
|
|
@@ -1284,7 +1293,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1284
1293
|
});
|
|
1285
1294
|
let secondMarkRejected = false;
|
|
1286
1295
|
try {
|
|
1287
|
-
flightStore.markInFlight(flightId, {
|
|
1296
|
+
await flightStore.markInFlight(flightId, {
|
|
1288
1297
|
round: 2,
|
|
1289
1298
|
peers: [...PEERS],
|
|
1290
1299
|
started_at: new Date().toISOString(),
|
|
@@ -1358,13 +1367,13 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1358
1367
|
{
|
|
1359
1368
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1360
1369
|
const staleStore = new SessionStore(config);
|
|
1361
|
-
const staleMeta = staleStore.init("stale-session-abort-test", "operator", []);
|
|
1370
|
+
const staleMeta = await staleStore.init("stale-session-abort-test", "operator", []);
|
|
1362
1371
|
const staleId = staleMeta.session_id;
|
|
1363
1372
|
const staleMetaPath = staleStore.metaPath(staleId);
|
|
1364
1373
|
const staleRaw = JSON.parse(fs.readFileSync(staleMetaPath, "utf8"));
|
|
1365
1374
|
staleRaw.updated_at = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
|
1366
1375
|
fs.writeFileSync(staleMetaPath, JSON.stringify(staleRaw, null, 2), "utf8");
|
|
1367
|
-
const sweep = staleStore.abortStaleSessions();
|
|
1376
|
+
const sweep = await staleStore.abortStaleSessions();
|
|
1368
1377
|
assert.ok(sweep.aborted >= 1, `abortStaleSessions must abort ≥1 stale session, got ${sweep.aborted}`);
|
|
1369
1378
|
const after = staleStore.read(staleId);
|
|
1370
1379
|
assert.equal(after.outcome, "aborted");
|
|
@@ -1376,9 +1385,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1376
1385
|
{
|
|
1377
1386
|
const { SessionStore } = await import("../src/core/session-store.js");
|
|
1378
1387
|
const inflightStore = new SessionStore(config);
|
|
1379
|
-
const inflightMeta = inflightStore.init("stale-session-skip-test", "operator", []);
|
|
1388
|
+
const inflightMeta = await inflightStore.init("stale-session-skip-test", "operator", []);
|
|
1380
1389
|
const inflightId = inflightMeta.session_id;
|
|
1381
|
-
inflightStore.markInFlight(inflightId, {
|
|
1390
|
+
await inflightStore.markInFlight(inflightId, {
|
|
1382
1391
|
round: 1,
|
|
1383
1392
|
peers: [...PEERS],
|
|
1384
1393
|
started_at: new Date().toISOString(),
|
|
@@ -1393,7 +1402,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1393
1402
|
const inflightRaw = JSON.parse(fs.readFileSync(inflightMetaPath, "utf8"));
|
|
1394
1403
|
inflightRaw.updated_at = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
|
|
1395
1404
|
fs.writeFileSync(inflightMetaPath, JSON.stringify(inflightRaw, null, 2), "utf8");
|
|
1396
|
-
const sweep = inflightStore.abortStaleSessions();
|
|
1405
|
+
const sweep = await inflightStore.abortStaleSessions();
|
|
1397
1406
|
const after = inflightStore.read(inflightId);
|
|
1398
1407
|
assert.equal(after.outcome, undefined, `in-flight session must NOT be aborted by stale sweep (got outcome=${after.outcome}, sweep=${JSON.stringify(sweep)})`);
|
|
1399
1408
|
console.log("[smoke] stale_session_skipped_when_running_test: PASS");
|
|
@@ -1831,7 +1840,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1831
1840
|
const meta = tpOrch.store.read(sessionId);
|
|
1832
1841
|
meta.evidence_checklist = fixtureItems;
|
|
1833
1842
|
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);
|
|
1843
|
+
const ad = await tpOrch.store.runEvidenceChecklistAddressDetection(sessionId, FIXTURE_ROUND);
|
|
1835
1844
|
// (1) The open item with last_round===currentRound MUST NOT appear under
|
|
1836
1845
|
// peer_resurfaced_terminal. This is the regression the buggy
|
|
1837
1846
|
// truthy-OR predicate would have triggered.
|
|
@@ -1934,7 +1943,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1934
1943
|
});
|
|
1935
1944
|
const item = opRound1.session.evidence_checklist?.[0];
|
|
1936
1945
|
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" });
|
|
1946
|
+
const result = await opOrch.store.setEvidenceChecklistItemStatus(opRound1.session.session_id, item.id, "satisfied", { note: "smoke verified manually", by: "operator" });
|
|
1938
1947
|
assert.equal(result.item.status, "satisfied", "mutator must set status to satisfied");
|
|
1939
1948
|
assert.equal(result.history_entry.from, "open");
|
|
1940
1949
|
assert.equal(result.history_entry.to, "satisfied");
|
|
@@ -1956,7 +1965,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1956
1965
|
// type-system level: the mutator's signature excludes "addressed". We
|
|
1957
1966
|
// assert that calling setEvidenceChecklistItemStatus with "deferred"
|
|
1958
1967
|
// 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" });
|
|
1968
|
+
const result2 = await opOrch.store.setEvidenceChecklistItemStatus(opRound1.session.session_id, item.id, "deferred", { note: "retract satisfied, defer instead", by: "operator" });
|
|
1960
1969
|
assert.equal(result2.item.status, "deferred");
|
|
1961
1970
|
assert.equal(result2.history_entry.from, "satisfied");
|
|
1962
1971
|
assert.equal(result2.history_entry.to, "deferred");
|
|
@@ -1994,6 +2003,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
1994
2003
|
caller: "operator",
|
|
1995
2004
|
peers: ["codex"],
|
|
1996
2005
|
});
|
|
2006
|
+
await phOrch.store.flushPendingEvents();
|
|
1997
2007
|
const metrics = phOrch.store.metrics();
|
|
1998
2008
|
const perPeer = metrics.per_peer_health;
|
|
1999
2009
|
assert.ok(perPeer, "metrics must include per_peer_health");
|
|
@@ -2835,7 +2845,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2835
2845
|
// would leave the durable log empty.
|
|
2836
2846
|
const holder = {};
|
|
2837
2847
|
const rollupOrch = new CrossReviewOrchestrator(cfg, (event) => {
|
|
2838
|
-
holder.orch?.store.appendEvent(event);
|
|
2848
|
+
void holder.orch?.store.appendEvent(event);
|
|
2839
2849
|
});
|
|
2840
2850
|
holder.orch = rollupOrch;
|
|
2841
2851
|
const r1 = await rollupOrch.askPeers({
|
|
@@ -2851,6 +2861,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2851
2861
|
caller: "operator",
|
|
2852
2862
|
peers: ["claude"],
|
|
2853
2863
|
});
|
|
2864
|
+
// v4.1.0: emit pipeline uses `void store.appendEvent(...)` (fire-
|
|
2865
|
+
// and-forget). Flush pending writes before reading the events file.
|
|
2866
|
+
await rollupOrch.store.flushPendingEvents();
|
|
2854
2867
|
const rollup = rollupOrch.store.aggregateShadowJudgments();
|
|
2855
2868
|
assert.ok(rollup.decisions_total >= 1, `aggregate must record at least 1 shadow decision (got ${rollup.decisions_total})`);
|
|
2856
2869
|
assert.ok(rollup.would_promote_total >= 1, `aggregate must record at least 1 would_promote (got ${rollup.would_promote_total})`);
|
|
@@ -2860,7 +2873,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2860
2873
|
assert.ok(claudeStats.would_promote >= 1);
|
|
2861
2874
|
assert.ok((claudeStats.by_confidence.verified ?? 0) >= 1);
|
|
2862
2875
|
assert.ok(claudeStats.first_seen_at && claudeStats.last_seen_at);
|
|
2863
|
-
const metrics = rollupOrch.store.metrics();
|
|
2876
|
+
const metrics = rollupOrch.store.metrics(); // already flushed above
|
|
2864
2877
|
assert.ok(metrics.shadow_judgment, "metrics().shadow_judgment must be present");
|
|
2865
2878
|
assert.equal(metrics.shadow_judgment.decisions_total, rollup.decisions_total);
|
|
2866
2879
|
console.log("[smoke] metrics_shadow_judgment_rollup_test: PASS");
|
|
@@ -2898,7 +2911,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2898
2911
|
const holder = {};
|
|
2899
2912
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
2900
2913
|
events.push({ type: e.type, data: e.data });
|
|
2901
|
-
holder.orch?.store.appendEvent(e);
|
|
2914
|
+
void holder.orch?.store.appendEvent(e);
|
|
2902
2915
|
});
|
|
2903
2916
|
holder.orch = orch;
|
|
2904
2917
|
// Drive runUntilUnanimous with FORCE_DRIFT (lead generation triggers
|
|
@@ -2942,7 +2955,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2942
2955
|
const holder = {};
|
|
2943
2956
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
2944
2957
|
events.push({ type: e.type, data: e.data });
|
|
2945
|
-
holder.orch?.store.appendEvent(e);
|
|
2958
|
+
void holder.orch?.store.appendEvent(e);
|
|
2946
2959
|
});
|
|
2947
2960
|
holder.orch = orch;
|
|
2948
2961
|
const result = await orch.runUntilUnanimous({
|
|
@@ -2980,7 +2993,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
2980
2993
|
const holder = {};
|
|
2981
2994
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
2982
2995
|
events.push({ type: e.type, data: e.data });
|
|
2983
|
-
holder.orch?.store.appendEvent(e);
|
|
2996
|
+
void holder.orch?.store.appendEvent(e);
|
|
2984
2997
|
});
|
|
2985
2998
|
holder.orch = orch;
|
|
2986
2999
|
const result = await orch.runUntilUnanimous({
|
|
@@ -3017,7 +3030,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3017
3030
|
const holder = {};
|
|
3018
3031
|
const orch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
3019
3032
|
events.push({ type: e.type, data: e.data });
|
|
3020
|
-
holder.orch?.store.appendEvent(e);
|
|
3033
|
+
void holder.orch?.store.appendEvent(e);
|
|
3021
3034
|
});
|
|
3022
3035
|
holder.orch = orch;
|
|
3023
3036
|
await orch.runUntilUnanimous({
|
|
@@ -3052,7 +3065,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3052
3065
|
};
|
|
3053
3066
|
const holder = {};
|
|
3054
3067
|
const aeOrch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
3055
|
-
holder.orch?.store.appendEvent(e);
|
|
3068
|
+
void holder.orch?.store.appendEvent(e);
|
|
3056
3069
|
});
|
|
3057
3070
|
holder.orch = aeOrch;
|
|
3058
3071
|
// Init session, attach 2 evidence files, run askPeers, read R1 prompt.
|
|
@@ -3071,12 +3084,12 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3071
3084
|
const sessionId = initial.session.session_id;
|
|
3072
3085
|
// The first askPeers above completed R1 already without attachments
|
|
3073
3086
|
// — that is the "before" baseline. Now attach files and run R2.
|
|
3074
|
-
aeOrch.store.attachEvidence(sessionId, {
|
|
3087
|
+
await aeOrch.store.attachEvidence(sessionId, {
|
|
3075
3088
|
label: "gates-output",
|
|
3076
3089
|
content: "EXIT 0 typecheck\nEXIT 0 lint\nEXIT 0 build\nEXIT 0 smoke 41/41 PASS\n",
|
|
3077
3090
|
extension: "log",
|
|
3078
3091
|
});
|
|
3079
|
-
aeOrch.store.attachEvidence(sessionId, {
|
|
3092
|
+
await aeOrch.store.attachEvidence(sessionId, {
|
|
3080
3093
|
label: "diff-stat",
|
|
3081
3094
|
content: " path/to/file.ts | +12/-3\n 1 file changed, 12 insertions, 3 deletions\n",
|
|
3082
3095
|
extension: "txt",
|
|
@@ -3132,7 +3145,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3132
3145
|
const sessionId = initial.session.session_id;
|
|
3133
3146
|
const big = "X".repeat(30_000);
|
|
3134
3147
|
for (let i = 0; i < 4; i++) {
|
|
3135
|
-
capOrch.store.attachEvidence(sessionId, {
|
|
3148
|
+
await capOrch.store.attachEvidence(sessionId, {
|
|
3136
3149
|
label: `att-${i}`,
|
|
3137
3150
|
content: big,
|
|
3138
3151
|
extension: "txt",
|
|
@@ -3316,7 +3329,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3316
3329
|
};
|
|
3317
3330
|
const holder = {};
|
|
3318
3331
|
const prOrch = new CrossReviewOrchestrator(cfg, (event) => {
|
|
3319
|
-
holder.orch?.store.appendEvent(event);
|
|
3332
|
+
void holder.orch?.store.appendEvent(event);
|
|
3320
3333
|
});
|
|
3321
3334
|
holder.orch = prOrch;
|
|
3322
3335
|
// R1: produce a NEEDS_EVIDENCE ask. R2: ask resurfaces (so far ground
|
|
@@ -3351,6 +3364,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3351
3364
|
caller: "operator",
|
|
3352
3365
|
peers: ["claude"],
|
|
3353
3366
|
});
|
|
3367
|
+
await prOrch.store.flushPendingEvents();
|
|
3354
3368
|
const report = prOrch.store.computeJudgmentPrecisionReport();
|
|
3355
3369
|
assert.ok(report.decisions_total >= 1, `at least 1 decision recorded`);
|
|
3356
3370
|
const claudeStats = report.by_judge_peer.claude;
|
|
@@ -3400,7 +3414,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3400
3414
|
const holder = {};
|
|
3401
3415
|
const acOrch = new CrossReviewOrchestrator(cfg, (e) => {
|
|
3402
3416
|
events.push(e.type);
|
|
3403
|
-
holder.orch?.store.appendEvent(e);
|
|
3417
|
+
void holder.orch?.store.appendEvent(e);
|
|
3404
3418
|
});
|
|
3405
3419
|
holder.orch = acOrch;
|
|
3406
3420
|
// R1: produce a NEEDS_EVIDENCE ask via FORCE_NEEDS_EVIDENCE.
|
|
@@ -3470,9 +3484,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3470
3484
|
peers: ["claude"],
|
|
3471
3485
|
});
|
|
3472
3486
|
const originalId = initial.session.session_id;
|
|
3473
|
-
cvOrch.store.finalize(originalId, "max-rounds", "test_finalize");
|
|
3487
|
+
await cvOrch.store.finalize(originalId, "max-rounds", "test_finalize");
|
|
3474
3488
|
// Contest it.
|
|
3475
|
-
const contestation = cvOrch.store.contestVerdict({
|
|
3489
|
+
const contestation = await cvOrch.store.contestVerdict({
|
|
3476
3490
|
session_id: originalId,
|
|
3477
3491
|
reason: "Caller disagrees with the verdict; new evidence has surfaced.",
|
|
3478
3492
|
new_task: "Contest test re-deliberation",
|
|
@@ -3491,7 +3505,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3491
3505
|
// Double-contesting must throw.
|
|
3492
3506
|
let threw = null;
|
|
3493
3507
|
try {
|
|
3494
|
-
cvOrch.store.contestVerdict({
|
|
3508
|
+
await cvOrch.store.contestVerdict({
|
|
3495
3509
|
session_id: originalId,
|
|
3496
3510
|
reason: "Trying to contest twice",
|
|
3497
3511
|
new_task: "Should not happen",
|
|
@@ -3513,7 +3527,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3513
3527
|
// but typically the session still has no outcome until finalize.)
|
|
3514
3528
|
threw = null;
|
|
3515
3529
|
try {
|
|
3516
|
-
cvOrch.store.contestVerdict({
|
|
3530
|
+
await cvOrch.store.contestVerdict({
|
|
3517
3531
|
session_id: inFlight.session.session_id,
|
|
3518
3532
|
reason: "in-flight should reject",
|
|
3519
3533
|
new_task: "should not happen",
|
|
@@ -3919,8 +3933,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3919
3933
|
};
|
|
3920
3934
|
// Scenario A: finalize("converged") on a session whose latest round
|
|
3921
3935
|
// 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, {
|
|
3936
|
+
const sess = await invariantStore.init("invariant-fixture", "operator", []);
|
|
3937
|
+
await invariantStore.appendRound(sess.session_id, {
|
|
3924
3938
|
caller_status: "READY",
|
|
3925
3939
|
prompt_file: "round-1-prompt.md",
|
|
3926
3940
|
peers: [],
|
|
@@ -3948,7 +3962,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3948
3962
|
});
|
|
3949
3963
|
let rejected = null;
|
|
3950
3964
|
try {
|
|
3951
|
-
invariantStore.finalize(sess.session_id, "converged", "unanimous_ready");
|
|
3965
|
+
await invariantStore.finalize(sess.session_id, "converged", "unanimous_ready");
|
|
3952
3966
|
}
|
|
3953
3967
|
catch (err) {
|
|
3954
3968
|
rejected = err;
|
|
@@ -3959,8 +3973,8 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3959
3973
|
assert.equal(invariantStore.read(sess.session_id).outcome, undefined, "finalize-reject MUST NOT mutate meta.outcome");
|
|
3960
3974
|
// Scenario B: finalize("converged") on a session whose latest round
|
|
3961
3975
|
// DID converge succeeds and leaves a consistent meta.
|
|
3962
|
-
const sess2 = invariantStore.init("invariant-fixture-2", "operator", []);
|
|
3963
|
-
invariantStore.appendRound(sess2.session_id, {
|
|
3976
|
+
const sess2 = await invariantStore.init("invariant-fixture-2", "operator", []);
|
|
3977
|
+
await invariantStore.appendRound(sess2.session_id, {
|
|
3964
3978
|
caller_status: "READY",
|
|
3965
3979
|
prompt_file: "round-1-prompt.md",
|
|
3966
3980
|
peers: [],
|
|
@@ -3986,14 +4000,14 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
3986
4000
|
},
|
|
3987
4001
|
started_at: new Date().toISOString(),
|
|
3988
4002
|
});
|
|
3989
|
-
const finalized = invariantStore.finalize(sess2.session_id, "converged", "unanimous_ready");
|
|
4003
|
+
const finalized = await invariantStore.finalize(sess2.session_id, "converged", "unanimous_ready");
|
|
3990
4004
|
assert.equal(finalized.outcome, "converged");
|
|
3991
4005
|
assert.equal(finalized.convergence_health?.state, "converged");
|
|
3992
4006
|
// Scenario C: appendRound on a finalized session MUST throw with the
|
|
3993
4007
|
// structured code.
|
|
3994
4008
|
let appendRejected = null;
|
|
3995
4009
|
try {
|
|
3996
|
-
invariantStore.appendRound(sess2.session_id, {
|
|
4010
|
+
await invariantStore.appendRound(sess2.session_id, {
|
|
3997
4011
|
caller_status: "READY",
|
|
3998
4012
|
prompt_file: "round-2-prompt.md",
|
|
3999
4013
|
peers: [],
|
|
@@ -4932,9 +4946,9 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
4932
4946
|
entries: [],
|
|
4933
4947
|
};
|
|
4934
4948
|
fs.mkdirSync(path.join(config.data_dir, "sessions", manifestSession), { recursive: true });
|
|
4935
|
-
writeCacheManifest(config.data_dir, manifestSession, manifestData);
|
|
4949
|
+
await writeCacheManifest(config.data_dir, manifestSession, manifestData);
|
|
4936
4950
|
for (let i = 0; i < 5; i += 1) {
|
|
4937
|
-
appendCacheManifestEntry(config.data_dir, manifestSession, {
|
|
4951
|
+
await appendCacheManifestEntry(config.data_dir, manifestSession, {
|
|
4938
4952
|
ts: new Date().toISOString(),
|
|
4939
4953
|
round: i + 1,
|
|
4940
4954
|
peer: "codex",
|
|
@@ -5283,7 +5297,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
5283
5297
|
/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
5298
|
// (9) Meta carries circular_state with the expected shape.
|
|
5285
5299
|
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");
|
|
5300
|
+
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
5301
|
// (10) MCP tool schemas accept "circular".
|
|
5288
5302
|
const circularEnumOccurrences = (mcpSrc.match(/z\.enum\(\["ship",\s*"review",\s*"circular"\]\)/g) ?? []).length;
|
|
5289
5303
|
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 +5876,12 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
5862
5876
|
fs.mkdirSync(path.join(repairCfg.data_dir, "sessions", corruptId), { recursive: true });
|
|
5863
5877
|
fs.writeFileSync(path.join(repairCfg.data_dir, "sessions", corruptId, "meta.json"), JSON.stringify(corruptMeta, null, 2));
|
|
5864
5878
|
// repair=false (default) — read-only, the contradiction is NOT touched.
|
|
5865
|
-
const readOnly = repairStore.sessionDoctor(20, false, false);
|
|
5879
|
+
const readOnly = await repairStore.sessionDoctor(20, false, false);
|
|
5866
5880
|
assert.equal(readOnly.repaired, undefined, "v3.6.0 / C: repair=false must NOT include a `repaired` array (stays read-only)");
|
|
5867
5881
|
const afterReadOnly = repairStore.read(corruptId);
|
|
5868
5882
|
assert.equal(afterReadOnly.convergence_health?.state, "blocked", "v3.6.0 / C: repair=false must leave the corrupt health state untouched");
|
|
5869
5883
|
// repair=true — the contradiction is recomputed from the latest round.
|
|
5870
|
-
const repaired = repairStore.sessionDoctor(20, false, true);
|
|
5884
|
+
const repaired = await repairStore.sessionDoctor(20, false, true);
|
|
5871
5885
|
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
5886
|
assert.equal(repaired.repaired?.[0]?.session_id, corruptId);
|
|
5873
5887
|
assert.equal(repaired.repaired?.[0]?.from_health_state, "blocked");
|
|
@@ -5876,7 +5890,7 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
5876
5890
|
assert.equal(afterRepair.convergence_health?.state, "converged", "v3.6.0 / C: repair=true must recompute health to converged");
|
|
5877
5891
|
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
5892
|
// Idempotent — a second repair pass finds nothing to fix.
|
|
5879
|
-
const secondPass = repairStore.sessionDoctor(20, false, true);
|
|
5893
|
+
const secondPass = await repairStore.sessionDoctor(20, false, true);
|
|
5880
5894
|
assert.equal(secondPass.repaired?.length, 0, "v3.6.0 / C: repair must be idempotent — second pass repairs nothing");
|
|
5881
5895
|
console.log("[smoke] session_doctor_repair_test: PASS");
|
|
5882
5896
|
}
|
|
@@ -6214,6 +6228,98 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
6214
6228
|
!verifyScript.includes("readFile(") &&
|
|
6215
6229
|
verifyScript.includes("npm_package_name") &&
|
|
6216
6230
|
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.");
|
|
6231
|
+
// v4.1.0 / F4: redactPrivateKeyBlocks must handle UNTERMINATED -----BEGIN
|
|
6232
|
+
// PRIVATE KEY----- blocks by redacting from BEGIN to end-of-string. Pre-
|
|
6233
|
+
// v4.1.0 leaked partial keys to events.ndjson when logs were truncated
|
|
6234
|
+
// mid-key. Empirical regression test:
|
|
6235
|
+
{
|
|
6236
|
+
const { redact } = await import("../src/security/redact.js");
|
|
6237
|
+
const truncatedKey = `error: -----BEGIN PRIVATE KEY-----\nMIIBVQIBADANBgkqhkiG9w0BAQEF...[TRUNCATED`;
|
|
6238
|
+
const redacted = redact(truncatedKey);
|
|
6239
|
+
assert.ok(redacted.includes("[REDACTED]"), "v4.1.0 / F4: redact() must emit [REDACTED] for unterminated -----BEGIN PRIVATE KEY----- blocks.");
|
|
6240
|
+
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.");
|
|
6241
|
+
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.");
|
|
6242
|
+
console.log("[smoke] redact_unterminated_private_key_test: PASS");
|
|
6243
|
+
}
|
|
6244
|
+
// v4.1.0 / F5: writeJson + cache-manifest writeJsonAtomic must be
|
|
6245
|
+
// async AND no source file under src/ may contain the
|
|
6246
|
+
// `while (Date.now() - start < wait)` CPU-burning busy-wait pattern.
|
|
6247
|
+
// Pre-v4.1.0 this pattern blocked the event loop for up to 310 ms
|
|
6248
|
+
// under Windows AV stress. Codex R1 NEEDS_EVIDENCE catch on session
|
|
6249
|
+
// 059b0093 asked for repo-wide grep (the original pin only covered
|
|
6250
|
+
// session-store.ts and missed cache-manifest.ts:47).
|
|
6251
|
+
{
|
|
6252
|
+
const storeSrc = fs.readFileSync(path.join(process.cwd(), "src", "core", "session-store.ts"), "utf8");
|
|
6253
|
+
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.");
|
|
6254
|
+
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.");
|
|
6255
|
+
const cacheManifestSrc = fs.readFileSync(path.join(process.cwd(), "src", "core", "cache-manifest.ts"), "utf8");
|
|
6256
|
+
assert.ok(/async\s+function\s+writeJsonAtomic\b/.test(cacheManifestSrc), "v4.1.0 / F5: writeJsonAtomic in cache-manifest.ts must be `async function writeJsonAtomic`.");
|
|
6257
|
+
// Repo-wide invariant: scan every .ts under src/ for executable
|
|
6258
|
+
// busy-wait patterns. Strip block comments so the pin only checks
|
|
6259
|
+
// executable code (comments may reference the removed pattern).
|
|
6260
|
+
function walkTs(dir, out) {
|
|
6261
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
6262
|
+
const full = path.join(dir, entry.name);
|
|
6263
|
+
if (entry.isDirectory()) {
|
|
6264
|
+
walkTs(full, out);
|
|
6265
|
+
}
|
|
6266
|
+
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
6267
|
+
out.push(full);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
return out;
|
|
6271
|
+
}
|
|
6272
|
+
const tsFiles = walkTs(path.join(process.cwd(), "src"), []);
|
|
6273
|
+
for (const file of tsFiles) {
|
|
6274
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
6275
|
+
// Strip /* ... */ and // ... so the busy-wait grep doesn't trip
|
|
6276
|
+
// on documentation. The lint runs on EXECUTABLE source only.
|
|
6277
|
+
const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\n)\s*\/\/[^\n]*/g, "$1");
|
|
6278
|
+
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))\`.`);
|
|
6279
|
+
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.`);
|
|
6280
|
+
}
|
|
6281
|
+
console.log("[smoke] writeJson_async_no_busy_wait_test: PASS");
|
|
6282
|
+
}
|
|
6283
|
+
// v4.1.0 / F6: withSessionLock in src/core/session-store.ts must use
|
|
6284
|
+
// `proper-lockfile` (mkdir-atomic locking) instead of the pre-v4.1.0
|
|
6285
|
+
// `fs.openSync(lockPath, "wx")` followed by separate writeFileSync. The
|
|
6286
|
+
// pre-v4.1.0 pattern had a multi-process TOCTOU race: between open()
|
|
6287
|
+
// (creates empty inode) and writeFileSync (populates content) another
|
|
6288
|
+
// process could observe the empty lock, JSON-parse-fail, and rmSync the
|
|
6289
|
+
// lock — letting both processes enter the critical section. proper-
|
|
6290
|
+
// lockfile uses fs.mkdir which is atomic across NTFS and POSIX so the
|
|
6291
|
+
// lock comes into existence as a directory in one syscall with no
|
|
6292
|
+
// empty-window race. Additionally — Codex 059b0093 R1..R4 catches —
|
|
6293
|
+
// v4.1.0 must NEVER auto-remove a pre-v4.1.0 legacy regular `.lock`
|
|
6294
|
+
// file: under live cross-version v4.0/v4.1 operation, every
|
|
6295
|
+
// auto-clean variant raced a concurrent v4.0.x process's own
|
|
6296
|
+
// stale-removal-and-recreate path. v4.1.0 fails closed with a
|
|
6297
|
+
// clear remediation error; operator manually cleans up at upgrade.
|
|
6298
|
+
{
|
|
6299
|
+
const storeSrc = fs.readFileSync(path.join(process.cwd(), "src", "core", "session-store.ts"), "utf8");
|
|
6300
|
+
assert.ok(/from\s+["']proper-lockfile["']/.test(storeSrc), "v4.1.0 / F6: session-store.ts must import from 'proper-lockfile'.");
|
|
6301
|
+
assert.ok(/lockfile\.lock\(/.test(storeSrc), "v4.1.0 / F6: withSessionLock must call lockfile.lock() to acquire the session lock.");
|
|
6302
|
+
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.");
|
|
6303
|
+
assert.ok(/async\s+withSessionLock|withSessionLock[^(]*\([^)]*\)\s*:\s*Promise/.test(storeSrc), "v4.1.0 / F6: withSessionLock must be declared async (returning Promise<T>).");
|
|
6304
|
+
// Fail-closed migration policy: surface a remediation error
|
|
6305
|
+
// instead of removing legacy regular `.lock` files. The string
|
|
6306
|
+
// "detected a pre-v4.1.0 lock file" is the contract every caller
|
|
6307
|
+
// sees; the F6 smoke pin asserts it remains pinned.
|
|
6308
|
+
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).");
|
|
6309
|
+
// Belt-and-suspenders: no rmSync(lockfilePath, ...) anywhere in
|
|
6310
|
+
// withSessionLock — fail-closed implies the legacy file is
|
|
6311
|
+
// NEVER deleted by v4.1.0.
|
|
6312
|
+
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.");
|
|
6313
|
+
assert.ok(!/if\s*\(\s*!\s*fs\.existsSync\(\s*target\s*\)\s*\)\s*\{[\s\S]{0,300}fs\.writeFileSync\(\s*target/.test(storeSrc), "v4.1.1 / CodeQL: meta placeholder creation must not use existsSync(target) before writeFileSync(target); rely on writeFileSync(..., { flag: 'wx' }) and EEXIST handling instead.");
|
|
6314
|
+
console.log("[smoke] session_lock_proper_lockfile_test: PASS");
|
|
6315
|
+
}
|
|
6316
|
+
{
|
|
6317
|
+
const migrationRaceSrc = fs.readFileSync(path.join(process.cwd(), "scripts", "race-migration-toctou.mjs"), "utf8");
|
|
6318
|
+
assert.ok(/fs\.openSync\(\s*lockfilePath,\s*["']r["']\s*\)/.test(migrationRaceSrc), "v4.1.1 / CodeQL: race-migration-toctou must open lockfilePath and snapshot through the file descriptor.");
|
|
6319
|
+
assert.ok(/fs\.fstatSync\(\s*endFd\s*\)/.test(migrationRaceSrc), "v4.1.1 / CodeQL: race-migration-toctou must use fstatSync(endFd) rather than statSync(lockfilePath) before reading.");
|
|
6320
|
+
assert.ok(!/fs\.statSync\(\s*lockfilePath\s*\)[\s\S]{0,120}fs\.readFileSync\(\s*lockfilePath/.test(migrationRaceSrc), "v4.1.1 / CodeQL: race-migration-toctou must not statSync(lockfilePath) then readFileSync(lockfilePath).");
|
|
6321
|
+
console.log("[smoke] codeql_file_system_race_regression_test: PASS");
|
|
6322
|
+
}
|
|
6217
6323
|
for (const required of ["dist", "shasum", "integrity", "tarball"]) {
|
|
6218
6324
|
assert.ok(verifyScript.includes(required), `v4.0.5 / AUDIT-6: verify-registry-dist.mjs must validate npm registry dist.${required}.`);
|
|
6219
6325
|
}
|
|
@@ -6263,6 +6369,21 @@ assert.equal(Object.hasOwn(metrics.decision_quality, "undefined"), false);
|
|
|
6263
6369
|
}
|
|
6264
6370
|
console.log("[smoke] npm_registry_discipline_test: PASS");
|
|
6265
6371
|
}
|
|
6372
|
+
{
|
|
6373
|
+
const prettierIgnore = fs.readFileSync(path.join(process.cwd(), ".prettierignore"), "utf8");
|
|
6374
|
+
const ignoredPatterns = prettierIgnore
|
|
6375
|
+
.split(/\r?\n/)
|
|
6376
|
+
.map((line) => line.trim())
|
|
6377
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
6378
|
+
for (const forbidden of ["README.md", "**/README.md", "src", "src/**", "scripts", "scripts/**"]) {
|
|
6379
|
+
assert.ok(!ignoredPatterns.includes(forbidden), `hard-gate / no-mask: .prettierignore must not hide ${forbidden} from Prettier coverage.`);
|
|
6380
|
+
}
|
|
6381
|
+
const eslintConfig = fs.readFileSync(path.join(process.cwd(), "eslint.config.js"), "utf8");
|
|
6382
|
+
assert.ok(!/"@typescript-eslint\/no-explicit-any"\s*:\s*["']off["']/.test(eslintConfig), "hard-gate / no-mask: eslint.config.js must not disable @typescript-eslint/no-explicit-any globally.");
|
|
6383
|
+
assert.ok(!/"@typescript-eslint\/no-unused-vars"\s*:\s*["']off["']/.test(eslintConfig), "hard-gate / no-mask: eslint.config.js must not disable @typescript-eslint/no-unused-vars globally.");
|
|
6384
|
+
assert.ok(/"@typescript-eslint\/no-unused-vars"\s*:\s*\[\s*["']error["']/.test(eslintConfig), "hard-gate / no-mask: @typescript-eslint/no-unused-vars must remain an error.");
|
|
6385
|
+
console.log("[smoke] hard_gate_no_linter_formatter_masking_test: PASS");
|
|
6386
|
+
}
|
|
6266
6387
|
// v2.6.1 NOTE: smoke coverage for `peer.fallback.budget_blocked` and
|
|
6267
6388
|
// `peer.moderation_recovery.budget_blocked` is intentionally NOT
|
|
6268
6389
|
// included. These two gates use the same arithmetic shape as preflight
|