@lcv-ideas-software/cross-review 4.2.3 → 4.2.5
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 +57 -0
- package/README.md +10 -1
- package/dist/scripts/smoke.js +215 -0
- package/dist/scripts/smoke.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.d.ts +2 -0
- package/dist/src/core/orchestrator.js +140 -6
- package/dist/src/core/orchestrator.js.map +1 -1
- package/dist/src/core/reports.d.ts +6 -0
- package/dist/src/core/reports.js +74 -4
- package/dist/src/core/reports.js.map +1 -1
- package/dist/src/core/session-store.d.ts +3 -0
- package/dist/src/core/session-store.js +181 -16
- package/dist/src/core/session-store.js.map +1 -1
- package/dist/src/core/status.js +9 -3
- package/dist/src/core/status.js.map +1 -1
- package/dist/src/core/types.d.ts +15 -0
- package/dist/src/mcp/server.js +56 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/apresentacao-cross-review.md +30 -27
- package/docs/apresentacao.md +29 -18
- package/docs/architecture.md +17 -1
- package/docs/costs.md +6 -0
- package/docs/evidence-preflight.md +34 -1
- package/package.json +1 -1
package/dist/src/core/reports.js
CHANGED
|
@@ -3,9 +3,78 @@ 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;
|
|
9
78
|
}
|
|
10
79
|
export function sessionReportMarkdown(session, events = []) {
|
|
11
80
|
const latestRound = session.rounds.at(-1);
|
|
@@ -22,7 +91,7 @@ export function sessionReportMarkdown(session, events = []) {
|
|
|
22
91
|
`- Outcome reason: ${valueOrDash(session.outcome_reason)}`,
|
|
23
92
|
`- Health: ${valueOrDash(session.convergence_health?.state)} - ${valueOrDash(session.convergence_health?.detail)}`,
|
|
24
93
|
`- Rounds: ${session.rounds.length}`,
|
|
25
|
-
|
|
94
|
+
...costSummaryLines(session),
|
|
26
95
|
`- Total tokens: ${valueOrDash(session.totals.usage.total_tokens)}`,
|
|
27
96
|
"",
|
|
28
97
|
"## Task",
|
|
@@ -46,6 +115,7 @@ export function sessionReportMarkdown(session, events = []) {
|
|
|
46
115
|
"## Peer Decisions",
|
|
47
116
|
"",
|
|
48
117
|
];
|
|
118
|
+
lines.push(...evidenceChecklistLines(session));
|
|
49
119
|
if (session.generation_files?.length) {
|
|
50
120
|
lines.push("## Generations", "");
|
|
51
121
|
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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"}
|
|
@@ -22,8 +22,10 @@ export declare class SessionStore {
|
|
|
22
22
|
scope: ConvergenceScope;
|
|
23
23
|
}): Promise<SessionMeta>;
|
|
24
24
|
read(sessionId: string): SessionMeta;
|
|
25
|
+
readTextArtifact(sessionId: string, relativePath: string, maxChars: number): string;
|
|
25
26
|
private peekNextSeq;
|
|
26
27
|
private commitSeq;
|
|
28
|
+
private appendEventRecord;
|
|
27
29
|
appendEvent(event: RuntimeEvent): Promise<void>;
|
|
28
30
|
flushPendingEvents(): Promise<void>;
|
|
29
31
|
readEvents(sessionId: string, sinceSeq?: number): SessionEvent[];
|
|
@@ -49,6 +51,7 @@ export declare class SessionStore {
|
|
|
49
51
|
convergence_scope: ConvergenceScope;
|
|
50
52
|
started_at: string;
|
|
51
53
|
}): Promise<ReviewRound>;
|
|
54
|
+
recordPreflightFailure(sessionId: string, failures: PeerFailure[], round?: number): Promise<SessionMeta>;
|
|
52
55
|
markBudgetWarningEmitted(sessionId: string): Promise<SessionMeta>;
|
|
53
56
|
setCircularState(sessionId: string, state: NonNullable<SessionMeta["circular_state"]>): Promise<SessionMeta>;
|
|
54
57
|
setSessionTraceability(sessionId: string, traceability: {
|
|
@@ -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,
|
|
@@ -348,6 +387,15 @@ export class SessionStore {
|
|
|
348
387
|
read(sessionId) {
|
|
349
388
|
return readJson(this.metaPath(sessionId));
|
|
350
389
|
}
|
|
390
|
+
readTextArtifact(sessionId, relativePath, maxChars) {
|
|
391
|
+
const sessionDir = this.sessionDir(sessionId);
|
|
392
|
+
const absolutePath = path.resolve(sessionDir, relativePath);
|
|
393
|
+
if (!this.isPathContained(sessionDir, absolutePath)) {
|
|
394
|
+
throw new Error(`artifact path escapes session directory: ${relativePath}`);
|
|
395
|
+
}
|
|
396
|
+
const raw = fs.readFileSync(absolutePath, "utf8");
|
|
397
|
+
return raw.length > maxChars ? raw.slice(0, maxChars) : raw;
|
|
398
|
+
}
|
|
351
399
|
// v2.4.0 / audit closure (P3.13) — refined after cross-review R2 (codex
|
|
352
400
|
// caught a durability gap in the initial implementation).
|
|
353
401
|
//
|
|
@@ -383,6 +431,16 @@ export class SessionStore {
|
|
|
383
431
|
commitSeq(sessionId, committed) {
|
|
384
432
|
this.seqCache.set(sessionId, committed);
|
|
385
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
|
+
}
|
|
386
444
|
// v4.1.0: durable event persistence. withSessionLock became async
|
|
387
445
|
// with the proper-lockfile refactor; appendEvent awaits the lock so
|
|
388
446
|
// callers that read events after persisting get the expected
|
|
@@ -396,13 +454,11 @@ export class SessionStore {
|
|
|
396
454
|
const write = (async () => {
|
|
397
455
|
try {
|
|
398
456
|
await this.withSessionLock(sessionId, () => {
|
|
399
|
-
const file = this.eventsPath(sessionId);
|
|
400
|
-
const seq = this.peekNextSeq(sessionId, file);
|
|
401
|
-
fs.appendFileSync(file, `${JSON.stringify({ ...event, seq, ts: event.ts ?? now() })}\n`, "utf8");
|
|
402
457
|
// Only commit the cache AFTER the durable append succeeded.
|
|
403
|
-
// If appendFileSync threw
|
|
404
|
-
// last persisted seq and the next call
|
|
405
|
-
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);
|
|
406
462
|
});
|
|
407
463
|
}
|
|
408
464
|
catch {
|
|
@@ -614,6 +670,24 @@ export class SessionStore {
|
|
|
614
670
|
return round;
|
|
615
671
|
});
|
|
616
672
|
}
|
|
673
|
+
async recordPreflightFailure(sessionId, failures, round = 0) {
|
|
674
|
+
return this.withSessionLock(sessionId, async () => {
|
|
675
|
+
const meta = this.read(sessionId);
|
|
676
|
+
meta.failed_attempts = [
|
|
677
|
+
...(meta.failed_attempts ?? []),
|
|
678
|
+
...failures.map((failure) => ({ ...failure, round })),
|
|
679
|
+
];
|
|
680
|
+
meta.convergence_health = {
|
|
681
|
+
state: "blocked",
|
|
682
|
+
last_event_at: now(),
|
|
683
|
+
detail: failures[0]?.message ??
|
|
684
|
+
"truthfulness_preflight blocked the session before a provider round started.",
|
|
685
|
+
};
|
|
686
|
+
meta.updated_at = now();
|
|
687
|
+
await writeJson(this.metaPath(sessionId), meta);
|
|
688
|
+
return meta;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
617
691
|
// v2.22.0 (B.P3): one-shot guard for `session.budget_warning` emit
|
|
618
692
|
// idempotency. Persisted in meta.json so the warning fires at most
|
|
619
693
|
// once per session even across host restarts.
|
|
@@ -703,13 +777,26 @@ export class SessionStore {
|
|
|
703
777
|
if (reason)
|
|
704
778
|
meta.outcome_reason = reason;
|
|
705
779
|
delete meta.in_flight;
|
|
780
|
+
const ts = now();
|
|
706
781
|
meta.convergence_health = {
|
|
707
782
|
state: outcome === "converged" ? "converged" : outcome === "max-rounds" ? "blocked" : "stale",
|
|
708
|
-
last_event_at:
|
|
783
|
+
last_event_at: ts,
|
|
709
784
|
detail: reason ?? outcome,
|
|
710
785
|
};
|
|
711
|
-
meta.updated_at =
|
|
786
|
+
meta.updated_at = ts;
|
|
712
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
|
+
}
|
|
713
800
|
return meta;
|
|
714
801
|
});
|
|
715
802
|
}
|
|
@@ -736,6 +823,7 @@ export class SessionStore {
|
|
|
736
823
|
async markCancelled(sessionId, reason = "cancelled") {
|
|
737
824
|
return this.withSessionLock(sessionId, async () => {
|
|
738
825
|
const meta = this.read(sessionId);
|
|
826
|
+
const ts = now();
|
|
739
827
|
meta.outcome = "aborted";
|
|
740
828
|
meta.outcome_reason = reason;
|
|
741
829
|
delete meta.in_flight;
|
|
@@ -744,15 +832,27 @@ export class SessionStore {
|
|
|
744
832
|
reason,
|
|
745
833
|
job_id: meta.control?.job_id,
|
|
746
834
|
requested_at: meta.control?.requested_at,
|
|
747
|
-
updated_at:
|
|
835
|
+
updated_at: ts,
|
|
748
836
|
};
|
|
749
837
|
meta.convergence_health = {
|
|
750
838
|
state: "stale",
|
|
751
|
-
last_event_at:
|
|
839
|
+
last_event_at: ts,
|
|
752
840
|
detail: reason,
|
|
753
841
|
};
|
|
754
|
-
meta.updated_at =
|
|
842
|
+
meta.updated_at = ts;
|
|
755
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
|
+
}
|
|
756
856
|
return meta;
|
|
757
857
|
});
|
|
758
858
|
}
|
|
@@ -1322,11 +1422,19 @@ export class SessionStore {
|
|
|
1322
1422
|
const maxRoundsSessions = [];
|
|
1323
1423
|
const selfLeadMetadata = [];
|
|
1324
1424
|
const openEvidenceSessions = [];
|
|
1425
|
+
const notResurfacedEvidenceSessions = [];
|
|
1325
1426
|
const grokProviderErrorSessions = [];
|
|
1326
1427
|
const eventReadErrorSessions = [];
|
|
1428
|
+
const terminalEventMissingSessions = [];
|
|
1327
1429
|
let eventsTotal = 0;
|
|
1328
1430
|
let tokenDeltaEvents = 0;
|
|
1329
1431
|
let tokenCompletedEvents = 0;
|
|
1432
|
+
let realSessions = 0;
|
|
1433
|
+
let stubSessions = 0;
|
|
1434
|
+
let peerCallCostUsd = null;
|
|
1435
|
+
let generationCostUsd = null;
|
|
1436
|
+
let totalCostUsd = null;
|
|
1437
|
+
let terminalEventMissingCount = 0;
|
|
1330
1438
|
const pushLimited = (target, entry) => {
|
|
1331
1439
|
if (target.length < cappedLimit)
|
|
1332
1440
|
target.push(entry);
|
|
@@ -1338,7 +1446,18 @@ export class SessionStore {
|
|
|
1338
1446
|
const evidenceList = session.evidence_checklist ?? [];
|
|
1339
1447
|
const openEvidenceItemsList = evidenceList.filter((item) => (item.status ?? "open") === "open");
|
|
1340
1448
|
const openEvidenceItems = openEvidenceItemsList.length;
|
|
1449
|
+
const notResurfacedEvidenceItems = evidenceList.filter((item) => item.status === "not_resurfaced").length;
|
|
1341
1450
|
const grokProviderErrors = (session.failed_attempts ?? []).filter((failure) => failure.peer === "grok" && failure.failure_class === "provider_error").length;
|
|
1451
|
+
if (isStubSession(session))
|
|
1452
|
+
stubSessions += 1;
|
|
1453
|
+
else
|
|
1454
|
+
realSessions += 1;
|
|
1455
|
+
peerCallCostUsd = addNullableCost(peerCallCostUsd, sessionPeerCostTotal(session));
|
|
1456
|
+
generationCostUsd = addNullableCost(generationCostUsd, sessionGenerationCostTotal(session));
|
|
1457
|
+
const sessionTotalCost = session.totals.cost.total_cost;
|
|
1458
|
+
if (sessionTotalCost != null && Number.isFinite(sessionTotalCost)) {
|
|
1459
|
+
totalCostUsd = addNullableCost(totalCostUsd, sessionTotalCost);
|
|
1460
|
+
}
|
|
1342
1461
|
const entry = {
|
|
1343
1462
|
session_id: session.session_id,
|
|
1344
1463
|
version: session.version,
|
|
@@ -1352,6 +1471,9 @@ export class SessionStore {
|
|
|
1352
1471
|
rounds: session.rounds.length,
|
|
1353
1472
|
updated_at: session.updated_at,
|
|
1354
1473
|
...(openEvidenceItems > 0 ? { open_evidence_items: openEvidenceItems } : {}),
|
|
1474
|
+
...(notResurfacedEvidenceItems > 0
|
|
1475
|
+
? { not_resurfaced_evidence_items: notResurfacedEvidenceItems }
|
|
1476
|
+
: {}),
|
|
1355
1477
|
...(grokProviderErrors > 0 ? { grok_provider_errors: grokProviderErrors } : {}),
|
|
1356
1478
|
};
|
|
1357
1479
|
// v2.22.0 (B.P2): drill-down for open-evidence entries. Aggregate
|
|
@@ -1392,6 +1514,8 @@ export class SessionStore {
|
|
|
1392
1514
|
pushLimited(selfLeadMetadata, entry);
|
|
1393
1515
|
if (openEvidenceItems > 0)
|
|
1394
1516
|
pushLimited(openEvidenceSessions, entry);
|
|
1517
|
+
if (notResurfacedEvidenceItems > 0)
|
|
1518
|
+
pushLimited(notResurfacedEvidenceSessions, entry);
|
|
1395
1519
|
if (grokProviderErrors > 0)
|
|
1396
1520
|
pushLimited(grokProviderErrorSessions, entry);
|
|
1397
1521
|
let sessionEvents = [];
|
|
@@ -1402,6 +1526,18 @@ export class SessionStore {
|
|
|
1402
1526
|
entry.event_read_error = redact(error instanceof Error ? error.message : String(error));
|
|
1403
1527
|
pushLimited(eventReadErrorSessions, entry);
|
|
1404
1528
|
}
|
|
1529
|
+
if (session.outcome) {
|
|
1530
|
+
const expectedTerminalEvent = session.control?.status === "cancelled" || session.outcome_reason === "session_cancelled"
|
|
1531
|
+
? "session.cancelled"
|
|
1532
|
+
: "session.finalized";
|
|
1533
|
+
const hasExpectedTerminalEvent = sessionEvents.some((event) => event.type === expectedTerminalEvent);
|
|
1534
|
+
if (!hasExpectedTerminalEvent) {
|
|
1535
|
+
terminalEventMissingCount += 1;
|
|
1536
|
+
entry.terminal_event_missing = true;
|
|
1537
|
+
entry.terminal_event_expected = expectedTerminalEvent;
|
|
1538
|
+
pushLimited(terminalEventMissingSessions, entry);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1405
1541
|
for (const event of sessionEvents) {
|
|
1406
1542
|
eventsTotal += 1;
|
|
1407
1543
|
if (event.type === "peer.token.delta")
|
|
@@ -1437,6 +1573,9 @@ export class SessionStore {
|
|
|
1437
1573
|
if (openEvidenceSessions.length > 0) {
|
|
1438
1574
|
recommendations.push("Address or explicitly terminal-mark open evidence checklist items before expecting convergence.");
|
|
1439
1575
|
}
|
|
1576
|
+
if (notResurfacedEvidenceSessions.length > 0) {
|
|
1577
|
+
recommendations.push("`not_resurfaced` evidence items are inference-only; review them separately from satisfied/deferred/rejected items.");
|
|
1578
|
+
}
|
|
1440
1579
|
if (grokProviderErrorSessions.length > 0) {
|
|
1441
1580
|
recommendations.push("Run a Grok-specific smoke/probe for sessions with grok provider errors before relying on Grok in release gates.");
|
|
1442
1581
|
}
|
|
@@ -1446,21 +1585,32 @@ export class SessionStore {
|
|
|
1446
1585
|
if (eventsTotal > 0 && tokenDeltaEvents / eventsTotal > 0.5) {
|
|
1447
1586
|
recommendations.push("Token delta events dominate this corpus; increase CROSS_REVIEW_TOKEN_DELTA_CHARS_THRESHOLD or disable token streaming for low-noise audits.");
|
|
1448
1587
|
}
|
|
1588
|
+
if (terminalEventMissingCount > 0) {
|
|
1589
|
+
recommendations.push("Terminal outcome metadata exists without matching terminal events; treat as legacy/event-gap evidence and inspect before relying on event-only analytics.");
|
|
1590
|
+
}
|
|
1449
1591
|
return {
|
|
1450
1592
|
generated_at: now(),
|
|
1451
1593
|
scope: "all",
|
|
1452
1594
|
limit: cappedLimit,
|
|
1453
1595
|
totals: {
|
|
1454
1596
|
sessions: sessions.length,
|
|
1597
|
+
real_sessions: realSessions,
|
|
1598
|
+
stub_sessions: stubSessions,
|
|
1455
1599
|
open: sessions.filter((session) => !session.outcome).length,
|
|
1456
|
-
stale: sessions.filter((session) => session.convergence_health?.state === "stale").length,
|
|
1457
|
-
blocked: sessions.filter((session) => session.convergence_health?.state === "blocked")
|
|
1458
|
-
.length,
|
|
1600
|
+
stale: sessions.filter((session) => !session.outcome && session.convergence_health?.state === "stale").length,
|
|
1601
|
+
blocked: sessions.filter((session) => !session.outcome && session.convergence_health?.state === "blocked").length,
|
|
1459
1602
|
max_rounds: sessions.filter((session) => session.outcome === "max-rounds").length,
|
|
1460
1603
|
self_lead_metadata: selfLeadCount,
|
|
1461
1604
|
open_evidence_sessions: sessions.filter((session) => (session.evidence_checklist ?? []).some((item) => (item.status ?? "open") === "open")).length,
|
|
1605
|
+
not_resurfaced_evidence_sessions: sessions.filter((session) => (session.evidence_checklist ?? []).some((item) => item.status === "not_resurfaced")).length,
|
|
1462
1606
|
grok_provider_error_sessions: sessions.filter((session) => (session.failed_attempts ?? []).some((failure) => failure.peer === "grok" && failure.failure_class === "provider_error")).length,
|
|
1463
1607
|
event_read_error_sessions: eventReadErrorSessions.length,
|
|
1608
|
+
terminal_event_missing_sessions: terminalEventMissingCount,
|
|
1609
|
+
},
|
|
1610
|
+
cost_breakdown: {
|
|
1611
|
+
total_cost_usd: totalCostUsd,
|
|
1612
|
+
peer_call_cost_usd: peerCallCostUsd,
|
|
1613
|
+
generation_cost_usd: generationCostUsd,
|
|
1464
1614
|
},
|
|
1465
1615
|
findings: {
|
|
1466
1616
|
open_sessions: openSessions,
|
|
@@ -1472,8 +1622,10 @@ export class SessionStore {
|
|
|
1472
1622
|
// in `totals.self_lead_metadata`.
|
|
1473
1623
|
self_lead_metadata: includeLegacy ? selfLeadMetadata : [],
|
|
1474
1624
|
open_evidence_sessions: openEvidenceSessions,
|
|
1625
|
+
not_resurfaced_evidence_sessions: notResurfacedEvidenceSessions,
|
|
1475
1626
|
grok_provider_error_sessions: grokProviderErrorSessions,
|
|
1476
1627
|
event_read_error_sessions: eventReadErrorSessions,
|
|
1628
|
+
terminal_event_missing_sessions: terminalEventMissingSessions,
|
|
1477
1629
|
},
|
|
1478
1630
|
event_noise: {
|
|
1479
1631
|
events_total: eventsTotal,
|
|
@@ -1766,17 +1918,30 @@ export class SessionStore {
|
|
|
1766
1918
|
continue;
|
|
1767
1919
|
const finalized = await this.withSessionLock(session.session_id, async () => {
|
|
1768
1920
|
const current = this.read(session.session_id);
|
|
1921
|
+
const ts = now();
|
|
1769
1922
|
current.outcome = outcome;
|
|
1770
1923
|
current.outcome_reason = reason;
|
|
1771
1924
|
delete current.in_flight;
|
|
1772
1925
|
current.convergence_health = {
|
|
1773
1926
|
state: "stale",
|
|
1774
|
-
last_event_at:
|
|
1927
|
+
last_event_at: ts,
|
|
1775
1928
|
detail: reason,
|
|
1776
1929
|
idle_ms: idleFor,
|
|
1777
1930
|
};
|
|
1778
|
-
current.updated_at =
|
|
1931
|
+
current.updated_at = ts;
|
|
1779
1932
|
await writeJson(this.metaPath(session.session_id), current);
|
|
1933
|
+
try {
|
|
1934
|
+
this.appendEventRecord({
|
|
1935
|
+
type: "session.finalized",
|
|
1936
|
+
session_id: session.session_id,
|
|
1937
|
+
ts,
|
|
1938
|
+
message: `Session finalized as ${outcome}${reason ? `: ${reason}` : ""}`,
|
|
1939
|
+
data: { outcome, reason, idle_ms: idleFor },
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
catch {
|
|
1943
|
+
/* event persistence is best-effort; session_doctor will flag gaps */
|
|
1944
|
+
}
|
|
1780
1945
|
return current;
|
|
1781
1946
|
});
|
|
1782
1947
|
swept.push(finalized);
|