@lcv-ideas-software/cross-review 4.2.4 → 4.3.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 +60 -0
- package/README.md +15 -1
- package/dist/scripts/eval-fixtures.d.ts +54 -0
- package/dist/scripts/eval-fixtures.js +216 -0
- package/dist/scripts/eval-fixtures.js.map +1 -0
- package/dist/scripts/smoke.js +349 -0
- package/dist/scripts/smoke.js.map +1 -1
- package/dist/src/core/config.d.ts +1 -1
- package/dist/src/core/config.js +1 -1
- package/dist/src/core/orchestrator.js +39 -4
- package/dist/src/core/orchestrator.js.map +1 -1
- package/dist/src/core/reports.d.ts +8 -1
- package/dist/src/core/reports.js +104 -4
- package/dist/src/core/reports.js.map +1 -1
- package/dist/src/core/session-store.d.ts +3 -1
- package/dist/src/core/session-store.js +295 -16
- package/dist/src/core/session-store.js.map +1 -1
- package/dist/src/core/types.d.ts +44 -0
- package/dist/src/mcp/server.js +15 -0
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/apresentacao-cross-review.md +36 -33
- package/docs/apresentacao.md +7 -3
- package/docs/architecture.md +22 -1
- package/docs/costs.md +6 -0
- package/docs/evidence-preflight.md +3 -2
- package/package.json +2 -1
package/dist/src/core/reports.js
CHANGED
|
@@ -3,9 +3,107 @@ function valueOrDash(value) {
|
|
|
3
3
|
return "-";
|
|
4
4
|
return String(value);
|
|
5
5
|
}
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
function moneyText(value, currency = "USD") {
|
|
7
|
+
return value == null ? "unknown" : `$${value.toFixed(6)} ${currency}`;
|
|
8
|
+
}
|
|
9
|
+
function moneyAmountText(value) {
|
|
10
|
+
return `$${value.toFixed(6)}`;
|
|
11
|
+
}
|
|
12
|
+
export function sessionCostBreakdown(session) {
|
|
13
|
+
const currency = session.totals.cost.currency ?? "USD";
|
|
14
|
+
let peerTotal = 0;
|
|
15
|
+
let peerSeen = false;
|
|
16
|
+
for (const round of session.rounds) {
|
|
17
|
+
for (const peer of round.peers) {
|
|
18
|
+
const value = peer.cost?.total_cost;
|
|
19
|
+
if (value == null || !Number.isFinite(value))
|
|
20
|
+
continue;
|
|
21
|
+
peerSeen = true;
|
|
22
|
+
peerTotal += value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
let generationTotal = 0;
|
|
26
|
+
let generationSeen = false;
|
|
27
|
+
for (const generation of session.generation_files ?? []) {
|
|
28
|
+
const value = generation.cost?.total_cost;
|
|
29
|
+
if (value == null || !Number.isFinite(value))
|
|
30
|
+
continue;
|
|
31
|
+
generationSeen = true;
|
|
32
|
+
generationTotal += value;
|
|
33
|
+
}
|
|
34
|
+
const total = session.totals.cost.total_cost ?? null;
|
|
35
|
+
return {
|
|
36
|
+
currency,
|
|
37
|
+
total,
|
|
38
|
+
peer_total: peerSeen ? peerTotal : null,
|
|
39
|
+
generation_total: generationSeen ? generationTotal : null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function costSummaryLines(session) {
|
|
43
|
+
const breakdown = sessionCostBreakdown(session);
|
|
44
|
+
const lines = [
|
|
45
|
+
`- Cost: ${moneyText(breakdown.total, breakdown.currency)}`,
|
|
46
|
+
`- Peer call cost: ${moneyText(breakdown.peer_total, breakdown.currency)}`,
|
|
47
|
+
`- Generation cost: ${moneyText(breakdown.generation_total, breakdown.currency)}`,
|
|
48
|
+
];
|
|
49
|
+
if (breakdown.total != null &&
|
|
50
|
+
breakdown.peer_total != null &&
|
|
51
|
+
breakdown.generation_total != null) {
|
|
52
|
+
lines.push(`- Cost reconciliation: ${moneyText(breakdown.total, breakdown.currency)} = ${moneyAmountText(breakdown.peer_total)} peer + ${moneyAmountText(breakdown.generation_total)} generation`);
|
|
53
|
+
}
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
function evidenceChecklistLines(session) {
|
|
57
|
+
const checklist = session.evidence_checklist ?? [];
|
|
58
|
+
if (!checklist.length)
|
|
59
|
+
return [];
|
|
60
|
+
const counts = new Map();
|
|
61
|
+
for (const item of checklist) {
|
|
62
|
+
const status = item.status ?? "open";
|
|
63
|
+
counts.set(status, (counts.get(status) ?? 0) + 1);
|
|
64
|
+
}
|
|
65
|
+
const lines = ["## Evidence Checklist", ""];
|
|
66
|
+
for (const [status, count] of [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
67
|
+
lines.push(`- ${status}: ${count}`);
|
|
68
|
+
}
|
|
69
|
+
const notResurfaced = checklist.filter((item) => item.status === "not_resurfaced");
|
|
70
|
+
if (notResurfaced.length) {
|
|
71
|
+
lines.push("- not_resurfaced means the ask was not repeated; it is not proof that evidence was satisfied.");
|
|
72
|
+
for (const item of notResurfaced.slice(0, 10)) {
|
|
73
|
+
lines.push(` - ${item.peer}/${item.id}: ${item.ask}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lines.push("");
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
export function unresolvedEvidenceItems(session) {
|
|
80
|
+
return (session.evidence_checklist ?? []).filter((item) => {
|
|
81
|
+
const status = item.status ?? "open";
|
|
82
|
+
return status === "open" || status === "not_resurfaced";
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function unresolvedEvidenceDispositionLines(session) {
|
|
86
|
+
const unresolved = unresolvedEvidenceItems(session);
|
|
87
|
+
if (!unresolved.length)
|
|
88
|
+
return [];
|
|
89
|
+
const lines = [
|
|
90
|
+
"## Unresolved Evidence Disposition",
|
|
91
|
+
"",
|
|
92
|
+
"- These items are not terminally satisfied, deferred, or rejected.",
|
|
93
|
+
"- `open` still requires a concrete response; `not_resurfaced` only means the peer did not repeat the ask in a later round.",
|
|
94
|
+
"- To close them explicitly, attach the requested evidence or use `session_evidence_checklist_update` with `satisfied`, `deferred`, or `rejected`.",
|
|
95
|
+
"",
|
|
96
|
+
];
|
|
97
|
+
for (const item of unresolved.slice(0, 20)) {
|
|
98
|
+
const status = item.status ?? "open";
|
|
99
|
+
const chronic = item.round_count >= 3 ? " chronic" : "";
|
|
100
|
+
lines.push(`- ${status}${chronic} ${item.peer}/${item.id}: ${item.ask}`);
|
|
101
|
+
}
|
|
102
|
+
if (unresolved.length > 20) {
|
|
103
|
+
lines.push(`- ... ${unresolved.length - 20} additional unresolved item(s) omitted.`);
|
|
104
|
+
}
|
|
105
|
+
lines.push("");
|
|
106
|
+
return lines;
|
|
9
107
|
}
|
|
10
108
|
export function sessionReportMarkdown(session, events = []) {
|
|
11
109
|
const latestRound = session.rounds.at(-1);
|
|
@@ -22,7 +120,7 @@ export function sessionReportMarkdown(session, events = []) {
|
|
|
22
120
|
`- Outcome reason: ${valueOrDash(session.outcome_reason)}`,
|
|
23
121
|
`- Health: ${valueOrDash(session.convergence_health?.state)} - ${valueOrDash(session.convergence_health?.detail)}`,
|
|
24
122
|
`- Rounds: ${session.rounds.length}`,
|
|
25
|
-
|
|
123
|
+
...costSummaryLines(session),
|
|
26
124
|
`- Total tokens: ${valueOrDash(session.totals.usage.total_tokens)}`,
|
|
27
125
|
"",
|
|
28
126
|
"## Task",
|
|
@@ -46,6 +144,8 @@ export function sessionReportMarkdown(session, events = []) {
|
|
|
46
144
|
"## Peer Decisions",
|
|
47
145
|
"",
|
|
48
146
|
];
|
|
147
|
+
lines.push(...evidenceChecklistLines(session));
|
|
148
|
+
lines.push(...unresolvedEvidenceDispositionLines(session));
|
|
49
149
|
if (session.generation_files?.length) {
|
|
50
150
|
lines.push("## Generations", "");
|
|
51
151
|
for (const generation of session.generation_files) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reports.js","sourceRoot":"","sources":["../../../src/core/reports.ts"],"names":[],"mappings":"AAEA,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC;IAC9C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"reports.js","sourceRoot":"","sources":["../../../src/core/reports.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppConfig, ConvergenceResult, ConvergenceScope, EvidenceChecklistItem, EvidenceChecklistStatus, EvidenceStatusHistoryEntry, GenerationResult, JudgmentPrecisionReport, PeerFailure, PeerId, PeerProbeResult, PeerResult, ReviewRound, ReviewStatus, RuntimeEvent, RuntimeMetrics, SessionDoctorReport, SessionEvent, SessionMeta, ShadowJudgmentRollup } from "./types.js";
|
|
1
|
+
import type { AppConfig, ConvergenceResult, ConvergenceScope, EvidenceChecklistItem, EvidenceChecklistStatus, EvidenceStatusHistoryEntry, GenerationResult, JudgmentPrecisionReport, PeerFailure, PeerId, PeerProbeResult, PeerReliabilityReport, PeerResult, ReviewRound, ReviewStatus, RuntimeEvent, RuntimeMetrics, SessionDoctorReport, SessionEvent, SessionMeta, ShadowJudgmentRollup } from "./types.js";
|
|
2
2
|
export declare const SWEEP_MIN_IDLE_MS: number;
|
|
3
3
|
export declare class SessionStore {
|
|
4
4
|
private readonly config;
|
|
@@ -25,6 +25,7 @@ export declare class SessionStore {
|
|
|
25
25
|
readTextArtifact(sessionId: string, relativePath: string, maxChars: number): string;
|
|
26
26
|
private peekNextSeq;
|
|
27
27
|
private commitSeq;
|
|
28
|
+
private appendEventRecord;
|
|
28
29
|
appendEvent(event: RuntimeEvent): Promise<void>;
|
|
29
30
|
flushPendingEvents(): Promise<void>;
|
|
30
31
|
readEvents(sessionId: string, sinceSeq?: number): SessionEvent[];
|
|
@@ -94,6 +95,7 @@ export declare class SessionStore {
|
|
|
94
95
|
recoverInterruptedSessions(activeSessionIds?: Set<string>): Promise<SessionMeta[]>;
|
|
95
96
|
aggregateShadowJudgments(sessionId?: string): ShadowJudgmentRollup;
|
|
96
97
|
metrics(sessionId?: string): RuntimeMetrics;
|
|
98
|
+
peerReliabilityReport(sessionId?: string): PeerReliabilityReport;
|
|
97
99
|
sessionDoctor(limit?: number, includeLegacy?: boolean, repair?: boolean): Promise<SessionDoctorReport>;
|
|
98
100
|
computeJudgmentPrecisionReport(opts?: {
|
|
99
101
|
peer?: PeerId | undefined;
|
|
@@ -9,6 +9,45 @@ export const SWEEP_MIN_IDLE_MS = 24 * 60 * 60 * 1000;
|
|
|
9
9
|
function now() {
|
|
10
10
|
return new Date().toISOString();
|
|
11
11
|
}
|
|
12
|
+
function isStubSession(session) {
|
|
13
|
+
const peerCosts = session.rounds.flatMap((round) => round.peers.map((peer) => peer.cost));
|
|
14
|
+
const generationCosts = (session.generation_files ?? []).map((generation) => generation.cost);
|
|
15
|
+
const costs = [...peerCosts, ...generationCosts].filter(Boolean);
|
|
16
|
+
if (costs.length > 0)
|
|
17
|
+
return costs.every((cost) => cost?.source === "stub");
|
|
18
|
+
return session.capability_snapshot.some((probe) => probe.provider.startsWith("stub-") || probe.model.startsWith("stub-"));
|
|
19
|
+
}
|
|
20
|
+
function sessionPeerCostTotal(session) {
|
|
21
|
+
let total = 0;
|
|
22
|
+
let seen = false;
|
|
23
|
+
for (const round of session.rounds) {
|
|
24
|
+
for (const peer of round.peers) {
|
|
25
|
+
const value = peer.cost?.total_cost;
|
|
26
|
+
if (value == null || !Number.isFinite(value))
|
|
27
|
+
continue;
|
|
28
|
+
seen = true;
|
|
29
|
+
total += value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return seen ? total : null;
|
|
33
|
+
}
|
|
34
|
+
function sessionGenerationCostTotal(session) {
|
|
35
|
+
let total = 0;
|
|
36
|
+
let seen = false;
|
|
37
|
+
for (const generation of session.generation_files ?? []) {
|
|
38
|
+
const value = generation.cost?.total_cost;
|
|
39
|
+
if (value == null || !Number.isFinite(value))
|
|
40
|
+
continue;
|
|
41
|
+
seen = true;
|
|
42
|
+
total += value;
|
|
43
|
+
}
|
|
44
|
+
return seen ? total : null;
|
|
45
|
+
}
|
|
46
|
+
function addNullableCost(a, b) {
|
|
47
|
+
if (a == null && b == null)
|
|
48
|
+
return null;
|
|
49
|
+
return (a ?? 0) + (b ?? 0);
|
|
50
|
+
}
|
|
12
51
|
// v2.4.0 / audit closure (P1.3): atomicWriteFile retry on Windows.
|
|
13
52
|
// `fs.renameSync` in Win32 fails with EPERM/EACCES/EBUSY when the
|
|
14
53
|
// destination is briefly held by another handle (AV scan, indexing,
|
|
@@ -392,6 +431,16 @@ export class SessionStore {
|
|
|
392
431
|
commitSeq(sessionId, committed) {
|
|
393
432
|
this.seqCache.set(sessionId, committed);
|
|
394
433
|
}
|
|
434
|
+
appendEventRecord(event) {
|
|
435
|
+
const sessionId = event.session_id;
|
|
436
|
+
if (!sessionId)
|
|
437
|
+
return;
|
|
438
|
+
const file = this.eventsPath(sessionId);
|
|
439
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
440
|
+
const seq = this.peekNextSeq(sessionId, file);
|
|
441
|
+
fs.appendFileSync(file, `${JSON.stringify({ ...event, seq, ts: event.ts ?? now() })}\n`, "utf8");
|
|
442
|
+
this.commitSeq(sessionId, seq);
|
|
443
|
+
}
|
|
395
444
|
// v4.1.0: durable event persistence. withSessionLock became async
|
|
396
445
|
// with the proper-lockfile refactor; appendEvent awaits the lock so
|
|
397
446
|
// callers that read events after persisting get the expected
|
|
@@ -405,13 +454,11 @@ export class SessionStore {
|
|
|
405
454
|
const write = (async () => {
|
|
406
455
|
try {
|
|
407
456
|
await this.withSessionLock(sessionId, () => {
|
|
408
|
-
const file = this.eventsPath(sessionId);
|
|
409
|
-
const seq = this.peekNextSeq(sessionId, file);
|
|
410
|
-
fs.appendFileSync(file, `${JSON.stringify({ ...event, seq, ts: event.ts ?? now() })}\n`, "utf8");
|
|
411
457
|
// Only commit the cache AFTER the durable append succeeded.
|
|
412
|
-
// If appendFileSync threw
|
|
413
|
-
// last persisted seq and the next call
|
|
414
|
-
this
|
|
458
|
+
// If appendFileSync threw inside appendEventRecord, the cache
|
|
459
|
+
// still reflects the last persisted seq and the next call
|
|
460
|
+
// reuses this seq number.
|
|
461
|
+
this.appendEventRecord(event);
|
|
415
462
|
});
|
|
416
463
|
}
|
|
417
464
|
catch {
|
|
@@ -730,13 +777,26 @@ export class SessionStore {
|
|
|
730
777
|
if (reason)
|
|
731
778
|
meta.outcome_reason = reason;
|
|
732
779
|
delete meta.in_flight;
|
|
780
|
+
const ts = now();
|
|
733
781
|
meta.convergence_health = {
|
|
734
782
|
state: outcome === "converged" ? "converged" : outcome === "max-rounds" ? "blocked" : "stale",
|
|
735
|
-
last_event_at:
|
|
783
|
+
last_event_at: ts,
|
|
736
784
|
detail: reason ?? outcome,
|
|
737
785
|
};
|
|
738
|
-
meta.updated_at =
|
|
786
|
+
meta.updated_at = ts;
|
|
739
787
|
await writeJson(this.metaPath(sessionId), meta);
|
|
788
|
+
try {
|
|
789
|
+
this.appendEventRecord({
|
|
790
|
+
type: "session.finalized",
|
|
791
|
+
session_id: sessionId,
|
|
792
|
+
ts,
|
|
793
|
+
message: `Session finalized as ${outcome}${reason ? `: ${reason}` : ""}`,
|
|
794
|
+
data: { outcome, reason: reason ?? null },
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
/* event persistence is best-effort; session_doctor will flag gaps */
|
|
799
|
+
}
|
|
740
800
|
return meta;
|
|
741
801
|
});
|
|
742
802
|
}
|
|
@@ -763,6 +823,7 @@ export class SessionStore {
|
|
|
763
823
|
async markCancelled(sessionId, reason = "cancelled") {
|
|
764
824
|
return this.withSessionLock(sessionId, async () => {
|
|
765
825
|
const meta = this.read(sessionId);
|
|
826
|
+
const ts = now();
|
|
766
827
|
meta.outcome = "aborted";
|
|
767
828
|
meta.outcome_reason = reason;
|
|
768
829
|
delete meta.in_flight;
|
|
@@ -771,15 +832,27 @@ export class SessionStore {
|
|
|
771
832
|
reason,
|
|
772
833
|
job_id: meta.control?.job_id,
|
|
773
834
|
requested_at: meta.control?.requested_at,
|
|
774
|
-
updated_at:
|
|
835
|
+
updated_at: ts,
|
|
775
836
|
};
|
|
776
837
|
meta.convergence_health = {
|
|
777
838
|
state: "stale",
|
|
778
|
-
last_event_at:
|
|
839
|
+
last_event_at: ts,
|
|
779
840
|
detail: reason,
|
|
780
841
|
};
|
|
781
|
-
meta.updated_at =
|
|
842
|
+
meta.updated_at = ts;
|
|
782
843
|
await writeJson(this.metaPath(sessionId), meta);
|
|
844
|
+
try {
|
|
845
|
+
this.appendEventRecord({
|
|
846
|
+
type: "session.cancelled",
|
|
847
|
+
session_id: sessionId,
|
|
848
|
+
ts,
|
|
849
|
+
message: `Session cancelled: ${reason}`,
|
|
850
|
+
data: { outcome: "aborted", reason },
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
/* event persistence is best-effort; session_doctor will flag gaps */
|
|
855
|
+
}
|
|
783
856
|
return meta;
|
|
784
857
|
});
|
|
785
858
|
}
|
|
@@ -1274,6 +1347,147 @@ export class SessionStore {
|
|
|
1274
1347
|
shadow_judgment: this.aggregateShadowJudgments(sessionId),
|
|
1275
1348
|
};
|
|
1276
1349
|
}
|
|
1350
|
+
peerReliabilityReport(sessionId) {
|
|
1351
|
+
const sessions = sessionId ? [this.read(sessionId)] : this.list();
|
|
1352
|
+
const peerSet = new Set(PEERS);
|
|
1353
|
+
const byPeer = {};
|
|
1354
|
+
const acc = (peer) => {
|
|
1355
|
+
let entry = byPeer[peer];
|
|
1356
|
+
if (!entry) {
|
|
1357
|
+
entry = {
|
|
1358
|
+
peer,
|
|
1359
|
+
session_ids: new Set(),
|
|
1360
|
+
results_total: 0,
|
|
1361
|
+
ready: 0,
|
|
1362
|
+
not_ready: 0,
|
|
1363
|
+
needs_evidence: 0,
|
|
1364
|
+
unresolved_status: 0,
|
|
1365
|
+
parser_warnings_total: 0,
|
|
1366
|
+
parser_warnings_by_type: {},
|
|
1367
|
+
decision_quality: {},
|
|
1368
|
+
rejected_total: 0,
|
|
1369
|
+
provider_errors: 0,
|
|
1370
|
+
failures_by_class: {},
|
|
1371
|
+
open_asks: 0,
|
|
1372
|
+
not_resurfaced_asks: 0,
|
|
1373
|
+
addressed_asks: 0,
|
|
1374
|
+
satisfied_asks: 0,
|
|
1375
|
+
deferred_asks: 0,
|
|
1376
|
+
rejected_asks: 0,
|
|
1377
|
+
fabrication_events: 0,
|
|
1378
|
+
latency_sum: 0,
|
|
1379
|
+
latency_count: 0,
|
|
1380
|
+
cost_sum: 0,
|
|
1381
|
+
cost_count: 0,
|
|
1382
|
+
};
|
|
1383
|
+
byPeer[peer] = entry;
|
|
1384
|
+
}
|
|
1385
|
+
return entry;
|
|
1386
|
+
};
|
|
1387
|
+
for (const session of sessions) {
|
|
1388
|
+
for (const round of session.rounds) {
|
|
1389
|
+
for (const peerResult of round.peers) {
|
|
1390
|
+
const entry = acc(peerResult.peer);
|
|
1391
|
+
entry.session_ids.add(session.session_id);
|
|
1392
|
+
entry.results_total += 1;
|
|
1393
|
+
if (peerResult.status === "READY")
|
|
1394
|
+
entry.ready += 1;
|
|
1395
|
+
else if (peerResult.status === "NOT_READY")
|
|
1396
|
+
entry.not_ready += 1;
|
|
1397
|
+
else if (peerResult.status === "NEEDS_EVIDENCE")
|
|
1398
|
+
entry.needs_evidence += 1;
|
|
1399
|
+
else
|
|
1400
|
+
entry.unresolved_status += 1;
|
|
1401
|
+
const quality = peerResult.decision_quality ?? "failed";
|
|
1402
|
+
entry.decision_quality[quality] = (entry.decision_quality[quality] ?? 0) + 1;
|
|
1403
|
+
for (const warning of peerResult.parser_warnings) {
|
|
1404
|
+
entry.parser_warnings_total += 1;
|
|
1405
|
+
entry.parser_warnings_by_type[warning] =
|
|
1406
|
+
(entry.parser_warnings_by_type[warning] ?? 0) + 1;
|
|
1407
|
+
}
|
|
1408
|
+
if (Number.isFinite(peerResult.latency_ms)) {
|
|
1409
|
+
entry.latency_sum += peerResult.latency_ms;
|
|
1410
|
+
entry.latency_count += 1;
|
|
1411
|
+
}
|
|
1412
|
+
if (peerResult.cost?.total_cost != null &&
|
|
1413
|
+
Number.isFinite(peerResult.cost.total_cost) &&
|
|
1414
|
+
peerResult.cost.source !== "stub") {
|
|
1415
|
+
entry.cost_sum += peerResult.cost.total_cost;
|
|
1416
|
+
entry.cost_count += 1;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
for (const failure of round.rejected) {
|
|
1420
|
+
const entry = acc(failure.peer);
|
|
1421
|
+
entry.session_ids.add(session.session_id);
|
|
1422
|
+
entry.rejected_total += 1;
|
|
1423
|
+
if (failure.failure_class === "provider_error")
|
|
1424
|
+
entry.provider_errors += 1;
|
|
1425
|
+
entry.failures_by_class[failure.failure_class] =
|
|
1426
|
+
(entry.failures_by_class[failure.failure_class] ?? 0) + 1;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
for (const item of session.evidence_checklist ?? []) {
|
|
1430
|
+
const entry = acc(item.peer);
|
|
1431
|
+
entry.session_ids.add(session.session_id);
|
|
1432
|
+
const status = item.status ?? "open";
|
|
1433
|
+
if (status === "open")
|
|
1434
|
+
entry.open_asks += 1;
|
|
1435
|
+
else if (status === "not_resurfaced")
|
|
1436
|
+
entry.not_resurfaced_asks += 1;
|
|
1437
|
+
else if (status === "addressed")
|
|
1438
|
+
entry.addressed_asks += 1;
|
|
1439
|
+
else if (status === "satisfied")
|
|
1440
|
+
entry.satisfied_asks += 1;
|
|
1441
|
+
else if (status === "deferred")
|
|
1442
|
+
entry.deferred_asks += 1;
|
|
1443
|
+
else if (status === "rejected")
|
|
1444
|
+
entry.rejected_asks += 1;
|
|
1445
|
+
}
|
|
1446
|
+
for (const event of this.readEvents(session.session_id)) {
|
|
1447
|
+
if (!event.type.includes("fabrication"))
|
|
1448
|
+
continue;
|
|
1449
|
+
const dataPeer = (event.data?.peer ?? event.peer);
|
|
1450
|
+
if (!dataPeer || !peerSet.has(dataPeer))
|
|
1451
|
+
continue;
|
|
1452
|
+
const entry = acc(dataPeer);
|
|
1453
|
+
entry.session_ids.add(session.session_id);
|
|
1454
|
+
entry.fabrication_events += 1;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
const reportByPeer = {};
|
|
1458
|
+
for (const [peer, entry] of Object.entries(byPeer)) {
|
|
1459
|
+
reportByPeer[peer] = {
|
|
1460
|
+
peer,
|
|
1461
|
+
sessions_seen: entry.session_ids.size,
|
|
1462
|
+
results_total: entry.results_total,
|
|
1463
|
+
ready: entry.ready,
|
|
1464
|
+
not_ready: entry.not_ready,
|
|
1465
|
+
needs_evidence: entry.needs_evidence,
|
|
1466
|
+
unresolved_status: entry.unresolved_status,
|
|
1467
|
+
parser_warnings_total: entry.parser_warnings_total,
|
|
1468
|
+
parser_warnings_by_type: entry.parser_warnings_by_type,
|
|
1469
|
+
decision_quality: entry.decision_quality,
|
|
1470
|
+
rejected_total: entry.rejected_total,
|
|
1471
|
+
provider_errors: entry.provider_errors,
|
|
1472
|
+
failures_by_class: entry.failures_by_class,
|
|
1473
|
+
open_asks: entry.open_asks,
|
|
1474
|
+
not_resurfaced_asks: entry.not_resurfaced_asks,
|
|
1475
|
+
addressed_asks: entry.addressed_asks,
|
|
1476
|
+
satisfied_asks: entry.satisfied_asks,
|
|
1477
|
+
deferred_asks: entry.deferred_asks,
|
|
1478
|
+
rejected_asks: entry.rejected_asks,
|
|
1479
|
+
fabrication_events: entry.fabrication_events,
|
|
1480
|
+
avg_latency_ms: entry.latency_count > 0 ? entry.latency_sum / entry.latency_count : null,
|
|
1481
|
+
total_cost_usd: entry.cost_count > 0 ? entry.cost_sum : null,
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
return {
|
|
1485
|
+
generated_at: now(),
|
|
1486
|
+
scope: sessionId ? "session" : "all",
|
|
1487
|
+
session_id: sessionId,
|
|
1488
|
+
by_peer: reportByPeer,
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1277
1491
|
// v2.16.0: read-only operational doctor. This is intentionally a
|
|
1278
1492
|
// reporting surface, not a cleanup tool: it never finalizes, rewrites
|
|
1279
1493
|
// or deletes sessions. Operators use it after audits to see which
|
|
@@ -1349,11 +1563,19 @@ export class SessionStore {
|
|
|
1349
1563
|
const maxRoundsSessions = [];
|
|
1350
1564
|
const selfLeadMetadata = [];
|
|
1351
1565
|
const openEvidenceSessions = [];
|
|
1566
|
+
const notResurfacedEvidenceSessions = [];
|
|
1352
1567
|
const grokProviderErrorSessions = [];
|
|
1353
1568
|
const eventReadErrorSessions = [];
|
|
1569
|
+
const terminalEventMissingSessions = [];
|
|
1354
1570
|
let eventsTotal = 0;
|
|
1355
1571
|
let tokenDeltaEvents = 0;
|
|
1356
1572
|
let tokenCompletedEvents = 0;
|
|
1573
|
+
let realSessions = 0;
|
|
1574
|
+
let stubSessions = 0;
|
|
1575
|
+
let peerCallCostUsd = null;
|
|
1576
|
+
let generationCostUsd = null;
|
|
1577
|
+
let totalCostUsd = null;
|
|
1578
|
+
let terminalEventMissingCount = 0;
|
|
1357
1579
|
const pushLimited = (target, entry) => {
|
|
1358
1580
|
if (target.length < cappedLimit)
|
|
1359
1581
|
target.push(entry);
|
|
@@ -1365,7 +1587,18 @@ export class SessionStore {
|
|
|
1365
1587
|
const evidenceList = session.evidence_checklist ?? [];
|
|
1366
1588
|
const openEvidenceItemsList = evidenceList.filter((item) => (item.status ?? "open") === "open");
|
|
1367
1589
|
const openEvidenceItems = openEvidenceItemsList.length;
|
|
1590
|
+
const notResurfacedEvidenceItems = evidenceList.filter((item) => item.status === "not_resurfaced").length;
|
|
1368
1591
|
const grokProviderErrors = (session.failed_attempts ?? []).filter((failure) => failure.peer === "grok" && failure.failure_class === "provider_error").length;
|
|
1592
|
+
if (isStubSession(session))
|
|
1593
|
+
stubSessions += 1;
|
|
1594
|
+
else
|
|
1595
|
+
realSessions += 1;
|
|
1596
|
+
peerCallCostUsd = addNullableCost(peerCallCostUsd, sessionPeerCostTotal(session));
|
|
1597
|
+
generationCostUsd = addNullableCost(generationCostUsd, sessionGenerationCostTotal(session));
|
|
1598
|
+
const sessionTotalCost = session.totals.cost.total_cost;
|
|
1599
|
+
if (sessionTotalCost != null && Number.isFinite(sessionTotalCost)) {
|
|
1600
|
+
totalCostUsd = addNullableCost(totalCostUsd, sessionTotalCost);
|
|
1601
|
+
}
|
|
1369
1602
|
const entry = {
|
|
1370
1603
|
session_id: session.session_id,
|
|
1371
1604
|
version: session.version,
|
|
@@ -1379,6 +1612,9 @@ export class SessionStore {
|
|
|
1379
1612
|
rounds: session.rounds.length,
|
|
1380
1613
|
updated_at: session.updated_at,
|
|
1381
1614
|
...(openEvidenceItems > 0 ? { open_evidence_items: openEvidenceItems } : {}),
|
|
1615
|
+
...(notResurfacedEvidenceItems > 0
|
|
1616
|
+
? { not_resurfaced_evidence_items: notResurfacedEvidenceItems }
|
|
1617
|
+
: {}),
|
|
1382
1618
|
...(grokProviderErrors > 0 ? { grok_provider_errors: grokProviderErrors } : {}),
|
|
1383
1619
|
};
|
|
1384
1620
|
// v2.22.0 (B.P2): drill-down for open-evidence entries. Aggregate
|
|
@@ -1419,6 +1655,8 @@ export class SessionStore {
|
|
|
1419
1655
|
pushLimited(selfLeadMetadata, entry);
|
|
1420
1656
|
if (openEvidenceItems > 0)
|
|
1421
1657
|
pushLimited(openEvidenceSessions, entry);
|
|
1658
|
+
if (notResurfacedEvidenceItems > 0)
|
|
1659
|
+
pushLimited(notResurfacedEvidenceSessions, entry);
|
|
1422
1660
|
if (grokProviderErrors > 0)
|
|
1423
1661
|
pushLimited(grokProviderErrorSessions, entry);
|
|
1424
1662
|
let sessionEvents = [];
|
|
@@ -1429,6 +1667,18 @@ export class SessionStore {
|
|
|
1429
1667
|
entry.event_read_error = redact(error instanceof Error ? error.message : String(error));
|
|
1430
1668
|
pushLimited(eventReadErrorSessions, entry);
|
|
1431
1669
|
}
|
|
1670
|
+
if (session.outcome) {
|
|
1671
|
+
const expectedTerminalEvent = session.control?.status === "cancelled" || session.outcome_reason === "session_cancelled"
|
|
1672
|
+
? "session.cancelled"
|
|
1673
|
+
: "session.finalized";
|
|
1674
|
+
const hasExpectedTerminalEvent = sessionEvents.some((event) => event.type === expectedTerminalEvent);
|
|
1675
|
+
if (!hasExpectedTerminalEvent) {
|
|
1676
|
+
terminalEventMissingCount += 1;
|
|
1677
|
+
entry.terminal_event_missing = true;
|
|
1678
|
+
entry.terminal_event_expected = expectedTerminalEvent;
|
|
1679
|
+
pushLimited(terminalEventMissingSessions, entry);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1432
1682
|
for (const event of sessionEvents) {
|
|
1433
1683
|
eventsTotal += 1;
|
|
1434
1684
|
if (event.type === "peer.token.delta")
|
|
@@ -1464,6 +1714,9 @@ export class SessionStore {
|
|
|
1464
1714
|
if (openEvidenceSessions.length > 0) {
|
|
1465
1715
|
recommendations.push("Address or explicitly terminal-mark open evidence checklist items before expecting convergence.");
|
|
1466
1716
|
}
|
|
1717
|
+
if (notResurfacedEvidenceSessions.length > 0) {
|
|
1718
|
+
recommendations.push("`not_resurfaced` evidence items are inference-only; review them separately from satisfied/deferred/rejected items.");
|
|
1719
|
+
}
|
|
1467
1720
|
if (grokProviderErrorSessions.length > 0) {
|
|
1468
1721
|
recommendations.push("Run a Grok-specific smoke/probe for sessions with grok provider errors before relying on Grok in release gates.");
|
|
1469
1722
|
}
|
|
@@ -1473,21 +1726,32 @@ export class SessionStore {
|
|
|
1473
1726
|
if (eventsTotal > 0 && tokenDeltaEvents / eventsTotal > 0.5) {
|
|
1474
1727
|
recommendations.push("Token delta events dominate this corpus; increase CROSS_REVIEW_TOKEN_DELTA_CHARS_THRESHOLD or disable token streaming for low-noise audits.");
|
|
1475
1728
|
}
|
|
1729
|
+
if (terminalEventMissingCount > 0) {
|
|
1730
|
+
recommendations.push("Terminal outcome metadata exists without matching terminal events; treat as legacy/event-gap evidence and inspect before relying on event-only analytics.");
|
|
1731
|
+
}
|
|
1476
1732
|
return {
|
|
1477
1733
|
generated_at: now(),
|
|
1478
1734
|
scope: "all",
|
|
1479
1735
|
limit: cappedLimit,
|
|
1480
1736
|
totals: {
|
|
1481
1737
|
sessions: sessions.length,
|
|
1738
|
+
real_sessions: realSessions,
|
|
1739
|
+
stub_sessions: stubSessions,
|
|
1482
1740
|
open: sessions.filter((session) => !session.outcome).length,
|
|
1483
|
-
stale: sessions.filter((session) => session.convergence_health?.state === "stale").length,
|
|
1484
|
-
blocked: sessions.filter((session) => session.convergence_health?.state === "blocked")
|
|
1485
|
-
.length,
|
|
1741
|
+
stale: sessions.filter((session) => !session.outcome && session.convergence_health?.state === "stale").length,
|
|
1742
|
+
blocked: sessions.filter((session) => !session.outcome && session.convergence_health?.state === "blocked").length,
|
|
1486
1743
|
max_rounds: sessions.filter((session) => session.outcome === "max-rounds").length,
|
|
1487
1744
|
self_lead_metadata: selfLeadCount,
|
|
1488
1745
|
open_evidence_sessions: sessions.filter((session) => (session.evidence_checklist ?? []).some((item) => (item.status ?? "open") === "open")).length,
|
|
1746
|
+
not_resurfaced_evidence_sessions: sessions.filter((session) => (session.evidence_checklist ?? []).some((item) => item.status === "not_resurfaced")).length,
|
|
1489
1747
|
grok_provider_error_sessions: sessions.filter((session) => (session.failed_attempts ?? []).some((failure) => failure.peer === "grok" && failure.failure_class === "provider_error")).length,
|
|
1490
1748
|
event_read_error_sessions: eventReadErrorSessions.length,
|
|
1749
|
+
terminal_event_missing_sessions: terminalEventMissingCount,
|
|
1750
|
+
},
|
|
1751
|
+
cost_breakdown: {
|
|
1752
|
+
total_cost_usd: totalCostUsd,
|
|
1753
|
+
peer_call_cost_usd: peerCallCostUsd,
|
|
1754
|
+
generation_cost_usd: generationCostUsd,
|
|
1491
1755
|
},
|
|
1492
1756
|
findings: {
|
|
1493
1757
|
open_sessions: openSessions,
|
|
@@ -1499,8 +1763,10 @@ export class SessionStore {
|
|
|
1499
1763
|
// in `totals.self_lead_metadata`.
|
|
1500
1764
|
self_lead_metadata: includeLegacy ? selfLeadMetadata : [],
|
|
1501
1765
|
open_evidence_sessions: openEvidenceSessions,
|
|
1766
|
+
not_resurfaced_evidence_sessions: notResurfacedEvidenceSessions,
|
|
1502
1767
|
grok_provider_error_sessions: grokProviderErrorSessions,
|
|
1503
1768
|
event_read_error_sessions: eventReadErrorSessions,
|
|
1769
|
+
terminal_event_missing_sessions: terminalEventMissingSessions,
|
|
1504
1770
|
},
|
|
1505
1771
|
event_noise: {
|
|
1506
1772
|
events_total: eventsTotal,
|
|
@@ -1793,17 +2059,30 @@ export class SessionStore {
|
|
|
1793
2059
|
continue;
|
|
1794
2060
|
const finalized = await this.withSessionLock(session.session_id, async () => {
|
|
1795
2061
|
const current = this.read(session.session_id);
|
|
2062
|
+
const ts = now();
|
|
1796
2063
|
current.outcome = outcome;
|
|
1797
2064
|
current.outcome_reason = reason;
|
|
1798
2065
|
delete current.in_flight;
|
|
1799
2066
|
current.convergence_health = {
|
|
1800
2067
|
state: "stale",
|
|
1801
|
-
last_event_at:
|
|
2068
|
+
last_event_at: ts,
|
|
1802
2069
|
detail: reason,
|
|
1803
2070
|
idle_ms: idleFor,
|
|
1804
2071
|
};
|
|
1805
|
-
current.updated_at =
|
|
2072
|
+
current.updated_at = ts;
|
|
1806
2073
|
await writeJson(this.metaPath(session.session_id), current);
|
|
2074
|
+
try {
|
|
2075
|
+
this.appendEventRecord({
|
|
2076
|
+
type: "session.finalized",
|
|
2077
|
+
session_id: session.session_id,
|
|
2078
|
+
ts,
|
|
2079
|
+
message: `Session finalized as ${outcome}${reason ? `: ${reason}` : ""}`,
|
|
2080
|
+
data: { outcome, reason, idle_ms: idleFor },
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
catch {
|
|
2084
|
+
/* event persistence is best-effort; session_doctor will flag gaps */
|
|
2085
|
+
}
|
|
1807
2086
|
return current;
|
|
1808
2087
|
});
|
|
1809
2088
|
swept.push(finalized);
|