@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.
@@ -3,9 +3,78 @@ function valueOrDash(value) {
3
3
  return "-";
4
4
  return String(value);
5
5
  }
6
- function costText(session) {
7
- const total = session.totals.cost.total_cost;
8
- return total == null ? "unknown" : `$${total.toFixed(6)} ${session.totals.cost.currency}`;
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
- `- Cost: ${costText(session)}`,
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":"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"}
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 above, the cache still reflects the
404
- // last persisted seq and the next call reuses this seq number.
405
- this.commitSeq(sessionId, seq);
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: now(),
783
+ last_event_at: ts,
709
784
  detail: reason ?? outcome,
710
785
  };
711
- meta.updated_at = now();
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: now(),
835
+ updated_at: ts,
748
836
  };
749
837
  meta.convergence_health = {
750
838
  state: "stale",
751
- last_event_at: now(),
839
+ last_event_at: ts,
752
840
  detail: reason,
753
841
  };
754
- meta.updated_at = now();
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: now(),
1927
+ last_event_at: ts,
1775
1928
  detail: reason,
1776
1929
  idle_ms: idleFor,
1777
1930
  };
1778
- current.updated_at = now();
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);