instar 1.3.830 → 1.3.832

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.
Files changed (114) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +83 -12
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/AutonomousRealCheckAnnotator.d.ts +105 -0
  5. package/dist/core/AutonomousRealCheckAnnotator.d.ts.map +1 -0
  6. package/dist/core/AutonomousRealCheckAnnotator.js +122 -0
  7. package/dist/core/AutonomousRealCheckAnnotator.js.map +1 -0
  8. package/dist/core/AutonomousRunStore.d.ts +29 -0
  9. package/dist/core/AutonomousRunStore.d.ts.map +1 -1
  10. package/dist/core/AutonomousRunStore.js +38 -0
  11. package/dist/core/AutonomousRunStore.js.map +1 -1
  12. package/dist/core/BackupManager.d.ts.map +1 -1
  13. package/dist/core/BackupManager.js +47 -1
  14. package/dist/core/BackupManager.js.map +1 -1
  15. package/dist/core/CircuitBreakingIntelligenceProvider.d.ts +12 -0
  16. package/dist/core/CircuitBreakingIntelligenceProvider.d.ts.map +1 -1
  17. package/dist/core/CircuitBreakingIntelligenceProvider.js +56 -4
  18. package/dist/core/CircuitBreakingIntelligenceProvider.js.map +1 -1
  19. package/dist/core/CompletionEvaluator.d.ts +62 -2
  20. package/dist/core/CompletionEvaluator.d.ts.map +1 -1
  21. package/dist/core/CompletionEvaluator.js +124 -6
  22. package/dist/core/CompletionEvaluator.js.map +1 -1
  23. package/dist/core/DecisionQualityRecorderImpl.d.ts +192 -0
  24. package/dist/core/DecisionQualityRecorderImpl.d.ts.map +1 -0
  25. package/dist/core/DecisionQualityRecorderImpl.js +363 -0
  26. package/dist/core/DecisionQualityRecorderImpl.js.map +1 -0
  27. package/dist/core/IntelligenceRouter.d.ts +33 -0
  28. package/dist/core/IntelligenceRouter.d.ts.map +1 -1
  29. package/dist/core/IntelligenceRouter.js +240 -54
  30. package/dist/core/IntelligenceRouter.js.map +1 -1
  31. package/dist/core/JudgmentProvenanceLog.d.ts +102 -2
  32. package/dist/core/JudgmentProvenanceLog.d.ts.map +1 -1
  33. package/dist/core/JudgmentProvenanceLog.js +239 -11
  34. package/dist/core/JudgmentProvenanceLog.js.map +1 -1
  35. package/dist/core/MessagingToneGate.d.ts +30 -0
  36. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  37. package/dist/core/MessagingToneGate.js +63 -0
  38. package/dist/core/MessagingToneGate.js.map +1 -1
  39. package/dist/core/PostUpdateMigrator.d.ts +11 -0
  40. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  41. package/dist/core/PostUpdateMigrator.js +32 -0
  42. package/dist/core/PostUpdateMigrator.js.map +1 -1
  43. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  44. package/dist/core/WriteDomainRegistry.js +24 -0
  45. package/dist/core/WriteDomainRegistry.js.map +1 -1
  46. package/dist/core/decisionGradingPass.d.ts +89 -0
  47. package/dist/core/decisionGradingPass.d.ts.map +1 -0
  48. package/dist/core/decisionGradingPass.js +188 -0
  49. package/dist/core/decisionGradingPass.js.map +1 -0
  50. package/dist/core/decisionQualityTypes.d.ts +170 -0
  51. package/dist/core/decisionQualityTypes.d.ts.map +1 -0
  52. package/dist/core/decisionQualityTypes.js +85 -0
  53. package/dist/core/decisionQualityTypes.js.map +1 -0
  54. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  55. package/dist/core/devGatedFeatures.js +6 -0
  56. package/dist/core/devGatedFeatures.js.map +1 -1
  57. package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
  58. package/dist/core/machineCoherenceManifest.js +4 -0
  59. package/dist/core/machineCoherenceManifest.js.map +1 -1
  60. package/dist/core/types.d.ts +69 -4
  61. package/dist/core/types.d.ts.map +1 -1
  62. package/dist/core/types.js.map +1 -1
  63. package/dist/data/provenanceCoverage.d.ts +156 -0
  64. package/dist/data/provenanceCoverage.d.ts.map +1 -0
  65. package/dist/data/provenanceCoverage.js +674 -0
  66. package/dist/data/provenanceCoverage.js.map +1 -0
  67. package/dist/monitoring/ExternalHogDecisionStore.d.ts +256 -0
  68. package/dist/monitoring/ExternalHogDecisionStore.d.ts.map +1 -0
  69. package/dist/monitoring/ExternalHogDecisionStore.js +481 -0
  70. package/dist/monitoring/ExternalHogDecisionStore.js.map +1 -0
  71. package/dist/monitoring/ExternalHogRealAdapters.d.ts +6 -2
  72. package/dist/monitoring/ExternalHogRealAdapters.d.ts.map +1 -1
  73. package/dist/monitoring/ExternalHogRealAdapters.js +2 -2
  74. package/dist/monitoring/ExternalHogRealAdapters.js.map +1 -1
  75. package/dist/monitoring/ExternalHogScanTick.d.ts +95 -3
  76. package/dist/monitoring/ExternalHogScanTick.d.ts.map +1 -1
  77. package/dist/monitoring/ExternalHogScanTick.js +102 -9
  78. package/dist/monitoring/ExternalHogScanTick.js.map +1 -1
  79. package/dist/monitoring/ExternalHogSentinel.d.ts +71 -3
  80. package/dist/monitoring/ExternalHogSentinel.d.ts.map +1 -1
  81. package/dist/monitoring/ExternalHogSentinel.js +126 -2
  82. package/dist/monitoring/ExternalHogSentinel.js.map +1 -1
  83. package/dist/monitoring/ExternalHogServerPrimitives.d.ts +10 -2
  84. package/dist/monitoring/ExternalHogServerPrimitives.d.ts.map +1 -1
  85. package/dist/monitoring/ExternalHogServerPrimitives.js +1 -1
  86. package/dist/monitoring/ExternalHogServerPrimitives.js.map +1 -1
  87. package/dist/monitoring/FeatureMetricsLedger.d.ts +304 -0
  88. package/dist/monitoring/FeatureMetricsLedger.d.ts.map +1 -1
  89. package/dist/monitoring/FeatureMetricsLedger.js +731 -0
  90. package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
  91. package/dist/scaffold/templates.d.ts.map +1 -1
  92. package/dist/scaffold/templates.js +2 -1
  93. package/dist/scaffold/templates.js.map +1 -1
  94. package/dist/server/AgentServer.d.ts.map +1 -1
  95. package/dist/server/AgentServer.js +55 -1
  96. package/dist/server/AgentServer.js.map +1 -1
  97. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  98. package/dist/server/CapabilityIndex.js +15 -1
  99. package/dist/server/CapabilityIndex.js.map +1 -1
  100. package/dist/server/fileRoutes.d.ts.map +1 -1
  101. package/dist/server/fileRoutes.js +9 -0
  102. package/dist/server/fileRoutes.js.map +1 -1
  103. package/dist/server/routes.d.ts.map +1 -1
  104. package/dist/server/routes.js +334 -5
  105. package/dist/server/routes.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/data/builtin-manifest.json +65 -65
  108. package/src/data/provenanceCoverage.ts +850 -0
  109. package/src/scaffold/templates/jobs/instar/llm-decision-grading.md +32 -0
  110. package/src/scaffold/templates.ts +2 -1
  111. package/upgrades/1.3.831.md +30 -0
  112. package/upgrades/1.3.832.md +22 -0
  113. package/upgrades/side-effects/llm-decision-quality-meter.md +95 -0
  114. package/upgrades/side-effects/messaging-tone-gate-provenance-enrollment.md +68 -0
@@ -63,6 +63,10 @@ import { GUARD_MANIFEST } from '../monitoring/guardManifest.js';
63
63
  import { GuardRegistry } from '../monitoring/GuardRegistry.js';
64
64
  import { isPeerUrlAllowedForCredentials } from './peerUrlGuard.js';
65
65
  import { classifyMachineEmptyState } from './poolEmptyState.js';
66
+ // LLM-Decision Quality Meter (llm-decision-quality-meter §5.5) — P9/P10 routes.
67
+ import { runDecisionGradingPass } from '../core/decisionGradingPass.js';
68
+ import { annotateDecisionOutcome, getDecisionAnnotationRejectionCounters } from '../core/DecisionQualityRecorderImpl.js';
69
+ import { PROVENANCE_COVERAGE } from '../data/provenanceCoverage.js';
66
70
  import { RemoteCloseAudit } from '../core/RemoteCloseAudit.js';
67
71
  import { RemoteAckStore } from '../core/RemoteAckStore.js';
68
72
  import { FailureLedger } from '../monitoring/FailureLedger.js';
@@ -745,6 +749,70 @@ function resolveAgentFingerprint(ctx) {
745
749
  catch { /* fall through to project name */ }
746
750
  return ctx.config.projectName ?? 'self';
747
751
  }
752
+ /**
753
+ * Explicit FIELD ALLOWLIST for a REDACTED judgment-provenance row merged from a
754
+ * peer (llm-decision-quality-meter §5.5): the served RedactedProvenanceRow keys
755
+ * ONLY — never `contextFull`, never an arbitrary extra field a hostile peer
756
+ * might inject. Applied instead of a `{...row}` spread on every peer row.
757
+ */
758
+ const REDACTED_PROVENANCE_FIELDS = [
759
+ 'id', 'ts', 'kind', 'component', 'decisionPoint', 'contextRedacted', 'truncated',
760
+ 'optionsPresented', 'decision', 'reason', 'floor', 'fallbackRung', 'arbiter',
761
+ 'model', 'door', 'tokensIn', 'tokensOut', 'latencyMs', 'decisionId', 'outcome',
762
+ 'correlationId', 'promptId', 'contentClass', 'mintedBy', 'grade', 'gradedBy', 'ruleId',
763
+ ];
764
+ function pickRedactedProvenanceFields(row) {
765
+ const out = {};
766
+ for (const k of REDACTED_PROVENANCE_FIELDS) {
767
+ if (k in row)
768
+ out[k] = row[k];
769
+ }
770
+ return out;
771
+ }
772
+ /**
773
+ * Explicit FIELD ALLOWLIST for a per-decision-point row merged from a peer's
774
+ * GET /decision-quality?scope=pool (llm-decision-quality-meter §5.5): the local
775
+ * view's row shape ONLY — never a `{...row}` spread (a hostile peer cannot
776
+ * smuggle `contextFull` or any extra field back through the pool merge).
777
+ */
778
+ const DECISION_QUALITY_POINT_FIELDS = [
779
+ 'decisionPoint', 'component', 'status', 'volumeClass', 'contentClass',
780
+ 'decisions', 'outcomesKnown', 'outcomesKnownRatio', 'insufficientEvidence',
781
+ 'gradeDistribution', 'byStrength', 'byRule', 'byRung', 'attribution', 'counters', 'flags',
782
+ ];
783
+ function pickDecisionQualityPointFields(row) {
784
+ const out = {};
785
+ for (const k of DECISION_QUALITY_POINT_FIELDS) {
786
+ if (k in row)
787
+ out[k] = row[k];
788
+ }
789
+ return out;
790
+ }
791
+ /**
792
+ * Alive ACT ids (registered AND non-terminal) from the runtime evolution queue
793
+ * — the agent-side half of the §5.6 pending-ref-dead check. null when the queue
794
+ * file is absent/unreadable (⇒ the check is skipped honestly rather than
795
+ * false-flagging every pending ref on an agent that has no queue yet).
796
+ */
797
+ function readLiveEvolutionActs(stateDir) {
798
+ try {
799
+ const p = path.join(stateDir, 'state', 'evolution', 'action-queue.json');
800
+ if (!fs.existsSync(p))
801
+ return null;
802
+ const aq = JSON.parse(fs.readFileSync(p, 'utf-8'));
803
+ const alive = new Set();
804
+ for (const a of aq.actions ?? []) {
805
+ if (a.id && (a.status === 'pending' || a.status === 'in_progress'))
806
+ alive.add(a.id);
807
+ }
808
+ return alive;
809
+ }
810
+ catch {
811
+ // @silent-fallback-ok: an unreadable queue ⇒ skip pending-ref-dead (never a
812
+ // false "dead" flag); observe-only surface, never a throw.
813
+ return null;
814
+ }
815
+ }
748
816
  export function createRoutes(ctx) {
749
817
  const router = Router();
750
818
  /**
@@ -4209,7 +4277,7 @@ export function createRoutes(ctx) {
4209
4277
  const v = await ctx.completionEvaluator.evaluateStopRationale(tail, {
4210
4278
  ...(signals ?? {}),
4211
4279
  stopKind: 'hard-blocker',
4212
- });
4280
+ }, { topicId: armedRecord.topicId, runId: armedRecord.runId }); // §5.3 P8: persist the P13 correlation id
4213
4281
  p13Verdict = v.classifiedBlocker ?? (v.stopAllowed ? 'allowed' : 'blocked');
4214
4282
  }
4215
4283
  catch { /* @silent-fallback-ok — classification is display-only on the trip; the trip itself is already decided */ }
@@ -4282,7 +4350,11 @@ export function createRoutes(ctx) {
4282
4350
  };
4283
4351
  }
4284
4352
  }
4285
- const verdict = await ctx.completionEvaluator.evaluate(effectiveCondition, tail, signals);
4353
+ const verdict = await ctx.completionEvaluator.evaluate(effectiveCondition, tail, signals,
4354
+ // §5.3 P8: persist the completion-judge correlation id into the durable
4355
+ // run record so the realcheck path can annotate it later. Undefined for
4356
+ // a legacy caller with no resolvable registered run (nothing to persist).
4357
+ armedRecord ? { topicId: armedRecord.topicId, runId: armedRecord.runId } : undefined);
4286
4358
  // R43: a met:true final verdict at the chokepoint marks the server-owned
4287
4359
  // run record TERMINAL (subject to the live-test veto below, which can
4288
4360
  // still flip met → false — terminality is recorded only on the verdict
@@ -4349,7 +4421,21 @@ export function createRoutes(ctx) {
4349
4421
  }
4350
4422
  const { transcriptTail } = req.body ?? {};
4351
4423
  try {
4352
- const verdict = await ctx.completionEvaluator.evaluateStopRationale(typeof transcriptTail === 'string' ? transcriptTail : '', parseStopSignals(req.body));
4424
+ // §5.3 P8: resolve the registered autonomous run (if any) so the P13
4425
+ // stop-rationale correlation id is persisted into the durable run record.
4426
+ // Server-resolved from the whitelisted identity fields, never trusted from
4427
+ // the client; only an ACTIVE record yields a runRef (else undefined).
4428
+ const sr = (req.body ?? {});
4429
+ const srTopicId = typeof sr.topicId === 'string' || typeof sr.topicId === 'number' ? String(sr.topicId) : undefined;
4430
+ const srSessionId = typeof sr.sessionId === 'string' ? sr.sessionId : undefined;
4431
+ const resolvedStopTopic = srTopicId ?? (srSessionId ? autonomousRunStore.resolveSession(srSessionId)?.topicId : undefined);
4432
+ let stopRunRef;
4433
+ if (resolvedStopTopic) {
4434
+ const rec = autonomousRunStore.getRecord(resolvedStopTopic);
4435
+ if (rec && autonomousRunStore.isActive(rec))
4436
+ stopRunRef = { topicId: rec.topicId, runId: rec.runId };
4437
+ }
4438
+ const verdict = await ctx.completionEvaluator.evaluateStopRationale(typeof transcriptTail === 'string' ? transcriptTail : '', parseStopSignals(req.body), stopRunRef);
4353
4439
  res.json({ ...verdict, p13ProtocolVersion: P13_PROTOCOL_VERSION });
4354
4440
  }
4355
4441
  catch (err) {
@@ -13538,7 +13624,7 @@ document.getElementById('mcpForm').addEventListener('submit', async function (e)
13538
13624
  // peers' redacted rows as type/length-clamped untrusted data.
13539
13625
  router.get('/judgment-provenance', async (req, res) => {
13540
13626
  if (!ctx.judgmentProvenance) {
13541
- res.status(503).json({ error: 'judgment-provenance log not constructed (single-machine / pool dark)' });
13627
+ res.status(503).json({ error: 'judgment-provenance log unavailable (constructed unconditionally at boot — FD9; a 503 means its construction failed, see server log)' });
13542
13628
  return;
13543
13629
  }
13544
13630
  try {
@@ -13552,10 +13638,20 @@ document.getElementById('mcpForm').addEventListener('submit', async function (e)
13552
13638
  }
13553
13639
  // Pool scope: merge peers' REDACTED rows (each peer redacts on its own
13554
13640
  // serving machine) as clamped untrusted data — bounded per row + total.
13641
+ // Hardened (llm-decision-quality-meter §5.5): the pool Bearer NEVER travels
13642
+ // to a non-allowlisted peer URL (isPeerUrlAllowedForCredentials), and each
13643
+ // merged peer row passes an explicit FIELD ALLOWLIST — never a {...row}
13644
+ // spread — so a hostile peer cannot smuggle contextFull (or any extra
13645
+ // field) back through this surface.
13555
13646
  const peers = ctx.resolvePeerUrls?.() ?? [];
13647
+ const jpExtraAllowlist = ctx.config.multiMachine?.peerUrlAllowlist;
13556
13648
  const failed = [];
13557
13649
  const remote = [];
13558
13650
  await Promise.all(peers.map(async (p) => {
13651
+ if (!isPeerUrlAllowedForCredentials(p.url, jpExtraAllowlist).ok) {
13652
+ failed.push({ machineId: p.machineId, error: 'url-rejected' });
13653
+ return;
13654
+ }
13559
13655
  try {
13560
13656
  const r = await fetch(`${p.url}/judgment-provenance?limit=${limit}${sinceHours ? `&sinceHours=${sinceHours}` : ''}`, {
13561
13657
  headers: { Authorization: `Bearer ${ctx.config.authToken}` },
@@ -13570,7 +13666,7 @@ document.getElementById('mcpForm').addEventListener('submit', async function (e)
13570
13666
  const serialized = JSON.stringify(row);
13571
13667
  if (serialized.length > 8_192)
13572
13668
  continue; // clamp: oversized peer rows dropped
13573
- remote.push({ ...row, machineId: p.machineId, remote: true });
13669
+ remote.push({ ...pickRedactedProvenanceFields(row), machineId: p.machineId, remote: true });
13574
13670
  }
13575
13671
  }
13576
13672
  catch (err) {
@@ -13583,6 +13679,239 @@ document.getElementById('mcpForm').addEventListener('submit', async function (e)
13583
13679
  res.status(500).json({ error: `provenance read failed: ${err instanceof Error ? err.message : String(err)}` });
13584
13680
  }
13585
13681
  });
13682
+ // ── LLM-Decision Quality Meter routes (llm-decision-quality-meter §5.5) ──────
13683
+ // Both routes 503 when the seam resolves DARK (resolveDevAgentGate on
13684
+ // provenance.uniformSeam.enabled — LIVE on a dev agent, dark on the fleet), so
13685
+ // a plain install never surfaces a half-built meter.
13686
+ const decisionQualitySeamOn = () => resolveDevAgentGate(ctx.config.provenance?.uniformSeam?.enabled, ctx.config);
13687
+ /** Assemble the local (this-machine) GET /decision-quality view over a window
13688
+ * (readLiveEvolutionActs + the pure pickers live at module scope). */
13689
+ const buildDecisionQualityView = (sinceHours) => {
13690
+ const ledger = ctx.featureMetricsLedger;
13691
+ const q = ctx.config.provenance?.quality ?? {};
13692
+ const posNum = (v, d) => (typeof v === 'number' && v > 0 ? v : d);
13693
+ const minSample = posNum(q.minSampleForRates, 20);
13694
+ const wiredSilentMin = posNum(q.wiredSilentMinCalls, 20);
13695
+ const evidenceWindowHours = posNum(q.evidenceWindowHours, 6);
13696
+ const gradingSlackHours = posNum(q.gradingSlackHours, 2);
13697
+ const expiryCutoffMs = Date.now() - (evidenceWindowHours + gradingSlackHours) * 3_600_000;
13698
+ const stats = ledger.decisionPointStats(sinceHours > 0 ? { sinceHours } : {});
13699
+ const breakdown = ledger.decisionGradeBreakdown(sinceHours > 0 ? { sinceHours } : {});
13700
+ const expired = ledger.countExpiredByPoint({ ...(sinceHours > 0 ? { sinceHours } : {}), expiryCutoffMs });
13701
+ const rollupDays = sinceHours > 0 ? Math.max(1, Math.ceil(sinceHours / 24)) : 0;
13702
+ const rollup = ledger.decisionQualityRollupDaily(rollupDays > 0 ? { sinceDays: rollupDays } : {});
13703
+ const featureRollup = ledger.byFeature(sinceHours > 0 ? { sinceHours } : {});
13704
+ const statByPoint = new Map(stats.map((s) => [s.decisionPoint, s]));
13705
+ const expiredByPoint = new Map(expired.map((e) => [e.decisionPoint, e.expired]));
13706
+ const llmCallsByComponent = new Map(featureRollup.map((f) => [f.feature, f.llmCalls]));
13707
+ const brByPoint = new Map();
13708
+ for (const r of breakdown) {
13709
+ const a = brByPoint.get(r.decisionPoint) ?? [];
13710
+ a.push(r);
13711
+ brByPoint.set(r.decisionPoint, a);
13712
+ }
13713
+ const countersByPoint = new Map();
13714
+ for (const r of rollup) {
13715
+ const c = countersByPoint.get(r.decisionPoint) ?? { orphanOutcomes: 0, joinMiss: 0, droppedByBudget: 0 };
13716
+ c.orphanOutcomes += r.orphanOutcomes;
13717
+ c.joinMiss += r.joinMiss;
13718
+ c.droppedByBudget += r.droppedByBudget;
13719
+ countersByPoint.set(r.decisionPoint, c);
13720
+ }
13721
+ const bump = (m, k, grade, n) => {
13722
+ const b = m[k] ?? { right: 0, wrong: 0, unknown: 0 };
13723
+ if (grade === 'right')
13724
+ b.right += n;
13725
+ else if (grade === 'wrong')
13726
+ b.wrong += n;
13727
+ else if (grade === 'unknown')
13728
+ b.unknown += n;
13729
+ m[k] = b;
13730
+ };
13731
+ const points = [];
13732
+ for (const entry of PROVENANCE_COVERAGE) {
13733
+ const st = statByPoint.get(entry.decisionPoint);
13734
+ const isWired = entry.status === 'wired';
13735
+ const decisions = st?.decisions ?? 0;
13736
+ const llmCalls = llmCallsByComponent.get(entry.component) ?? 0;
13737
+ const wiredButSilent = isWired && llmCalls >= wiredSilentMin && decisions === 0;
13738
+ const exemptButActive = entry.status === 'exempt:deterministic-only' && llmCalls > 0;
13739
+ // Keep the surface bounded: wired points, points with actual decisions, or
13740
+ // a live contradiction flag — never a wall of empty pending rows.
13741
+ if (!isWired && decisions === 0 && !exemptButActive)
13742
+ continue;
13743
+ const rows = brByPoint.get(entry.decisionPoint) ?? [];
13744
+ let right = 0;
13745
+ let wrong = 0;
13746
+ let unknown = 0;
13747
+ const byRule = {};
13748
+ const byRung = {};
13749
+ const byStrength = {};
13750
+ for (const r of rows) {
13751
+ if (r.grade === 'right')
13752
+ right += r.n;
13753
+ else if (r.grade === 'wrong')
13754
+ wrong += r.n;
13755
+ else if (r.grade === 'unknown')
13756
+ unknown += r.n;
13757
+ bump(byRule, r.ruleId, r.grade, r.n);
13758
+ bump(byRung, r.rung, r.grade, r.n);
13759
+ bump(byStrength, r.evidenceStrength, r.grade, r.n);
13760
+ }
13761
+ const outcomesKnown = right + wrong + unknown;
13762
+ points.push({
13763
+ decisionPoint: entry.decisionPoint,
13764
+ component: entry.component,
13765
+ status: entry.status,
13766
+ volumeClass: entry.volumeClass ?? null,
13767
+ contentClass: entry.contentClass,
13768
+ decisions,
13769
+ outcomesKnown,
13770
+ outcomesKnownRatio: decisions > 0 ? outcomesKnown / decisions : 0,
13771
+ // Below the minimum sample an aggregate rate is not actionable (§5.5 codex r5).
13772
+ insufficientEvidence: outcomesKnown < minSample,
13773
+ gradeDistribution: { right, wrong, unknown, expired: expiredByPoint.get(entry.decisionPoint) ?? 0 },
13774
+ // Strength FIRST (default aggregate) — proof-like and heuristic grades are never conflated.
13775
+ byStrength,
13776
+ byRule,
13777
+ byRung,
13778
+ attribution: { models: st?.models ?? [], frameworks: st?.frameworks ?? [], promptIds: st?.promptIds ?? [] },
13779
+ counters: countersByPoint.get(entry.decisionPoint) ?? { orphanOutcomes: 0, joinMiss: 0, droppedByBudget: 0 },
13780
+ flags: { wiredButSilent, exemptButActive },
13781
+ });
13782
+ }
13783
+ // Census debt + the runtime pending-ref-dead half (§5.6).
13784
+ let wired = 0;
13785
+ let pending = 0;
13786
+ let exempt = 0;
13787
+ const pendingRefDead = [];
13788
+ const liveActs = readLiveEvolutionActs(ctx.config.stateDir);
13789
+ for (const entry of PROVENANCE_COVERAGE) {
13790
+ if (entry.status === 'wired')
13791
+ wired++;
13792
+ else if (entry.status.startsWith('pending:')) {
13793
+ pending++;
13794
+ if (liveActs !== null) {
13795
+ const act = entry.status.slice('pending:'.length);
13796
+ if (!liveActs.has(act))
13797
+ pendingRefDead.push(`${entry.decisionPoint}:${act}`);
13798
+ }
13799
+ }
13800
+ else if (entry.status.startsWith('exempt:'))
13801
+ exempt++;
13802
+ }
13803
+ const totalCounters = { orphanOutcomes: 0, joinMiss: 0, droppedByBudget: 0 };
13804
+ for (const c of countersByPoint.values()) {
13805
+ totalCounters.orphanOutcomes += c.orphanOutcomes;
13806
+ totalCounters.joinMiss += c.joinMiss;
13807
+ totalCounters.droppedByBudget += c.droppedByBudget;
13808
+ }
13809
+ return {
13810
+ sinceHours,
13811
+ gate: { enabled: true, dryRun: ctx.config.provenance?.uniformSeam?.dryRun !== false },
13812
+ points,
13813
+ censusDebt: { wired, pending, exempt, pendingRefDead },
13814
+ counters: totalCounters,
13815
+ // The four §5.5 annotation-rejection counters, by class.
13816
+ rejections: getDecisionAnnotationRejectionCounters(),
13817
+ };
13818
+ };
13819
+ // GET /decision-quality — the operator read surface (§5.5/FD2). Per
13820
+ // decision-point over ?sinceHours: grade distribution (right/wrong/unknown/
13821
+ // expired) grouped by evidence STRENGTH first, grade-by-rule/rung/strength,
13822
+ // attribution columns, census debt (+ pending-ref-dead / wired-but-silent /
13823
+ // exempt-but-active), orphan/joinMiss/droppedByBudget + the annotation-
13824
+ // rejection counters. Pure indexed SQLite reads — NEVER a JSONL scan. Bearer-
13825
+ // authed; 503 when the seam is dark. ?scope=pool merges MACHINE-TAGGED peer
13826
+ // rows (per-machine framework routing = genuinely distinct data) with the
13827
+ // sibling-route hygiene: peer-URL credential guard, per-row 8KB clamp,
13828
+ // pool.failed classified rows, explicit FIELD ALLOWLIST (never {...row}).
13829
+ router.get('/decision-quality', async (req, res) => {
13830
+ if (!decisionQualitySeamOn()) {
13831
+ res.status(503).json({ error: 'decision-quality seam is off (provenance.uniformSeam resolves dark on this agent)' });
13832
+ return;
13833
+ }
13834
+ if (!ctx.featureMetricsLedger) {
13835
+ res.status(503).json({ error: 'decision-quality substrate unavailable (feature-metrics ledger not constructed)' });
13836
+ return;
13837
+ }
13838
+ try {
13839
+ const sinceHours = parseFloat(String(req.query.sinceHours ?? '')) || 0;
13840
+ const local = buildDecisionQualityView(sinceHours);
13841
+ if (req.query.scope !== 'pool') {
13842
+ res.json(local);
13843
+ return;
13844
+ }
13845
+ const peers = ctx.resolvePeerUrls?.() ?? [];
13846
+ const dqExtraAllowlist = ctx.config.multiMachine?.peerUrlAllowlist;
13847
+ const failed = [];
13848
+ const remotePoints = [];
13849
+ await Promise.all(peers.map(async (p) => {
13850
+ if (!isPeerUrlAllowedForCredentials(p.url, dqExtraAllowlist).ok) {
13851
+ failed.push({ machineId: p.machineId, error: 'url-rejected' });
13852
+ return;
13853
+ }
13854
+ try {
13855
+ const r = await fetch(`${p.url}/decision-quality${sinceHours ? `?sinceHours=${sinceHours}` : ''}`, {
13856
+ headers: { Authorization: `Bearer ${ctx.config.authToken}` },
13857
+ signal: AbortSignal.timeout(5000),
13858
+ });
13859
+ if (!r.ok) {
13860
+ failed.push({ machineId: p.machineId, error: `HTTP ${r.status}` });
13861
+ return;
13862
+ }
13863
+ const j = (await r.json());
13864
+ for (const row of j.points ?? []) {
13865
+ if (JSON.stringify(row).length > 8_192)
13866
+ continue; // clamp oversized peer rows
13867
+ remotePoints.push({ ...pickDecisionQualityPointFields(row), machineId: p.machineId, remote: true });
13868
+ }
13869
+ }
13870
+ catch (err) {
13871
+ failed.push({ machineId: p.machineId, error: err instanceof Error ? err.name : 'unreachable' });
13872
+ }
13873
+ }));
13874
+ res.json({ ...local, scope: 'pool', pool: { peersQueried: peers.length, failed }, remotePoints });
13875
+ }
13876
+ catch (err) {
13877
+ res.status(500).json({ error: `decision-quality read failed: ${err instanceof Error ? err.message : String(err)}` });
13878
+ }
13879
+ });
13880
+ // POST /decision-quality/grade-pass — the deterministic grading pass the
13881
+ // hourly llm-decision-grading job triggers (§5.5). Body `{}` (all knobs come
13882
+ // from config). Walks NEW decision rows since the durable per-decision-point
13883
+ // cursor (keyset (ts, correlation_id)), applies the registered DETERMINISTIC
13884
+ // window-close rule (hog-sustained-right-v1 — ZERO LLM spend, FD11), upserts
13885
+ // grades idempotently through the annotate chokepoint (re-runs converge),
13886
+ // with P19 backoff. Returns { graded, byRule, cursors }. Bearer-authed; 503
13887
+ // when the seam is dark. The injected clock lives in runDecisionGradingPass.
13888
+ router.post('/decision-quality/grade-pass', (_req, res) => {
13889
+ if (!decisionQualitySeamOn()) {
13890
+ res.status(503).json({ error: 'decision-quality seam is off (provenance.uniformSeam resolves dark on this agent)' });
13891
+ return;
13892
+ }
13893
+ if (!ctx.featureMetricsLedger) {
13894
+ res.status(503).json({ error: 'decision-quality substrate unavailable (feature-metrics ledger not constructed)' });
13895
+ return;
13896
+ }
13897
+ try {
13898
+ const q = ctx.config.provenance?.quality ?? {};
13899
+ const maxDecisionsPerPass = typeof q.maxDecisionsPerPass === 'number' && q.maxDecisionsPerPass > 0 ? q.maxDecisionsPerPass : 200;
13900
+ const evidenceWindowHours = typeof q.evidenceWindowHours === 'number' && q.evidenceWindowHours > 0 ? q.evidenceWindowHours : 6;
13901
+ const result = runDecisionGradingPass({
13902
+ ledger: ctx.featureMetricsLedger,
13903
+ hogStore: ctx.externalHogSentinel?.decisionStoreRef?.() ?? null,
13904
+ annotate: annotateDecisionOutcome,
13905
+ maxDecisionsPerPass,
13906
+ evidenceWindowMs: evidenceWindowHours * 3_600_000,
13907
+ now: Date.now,
13908
+ });
13909
+ res.json(result);
13910
+ }
13911
+ catch (err) {
13912
+ res.status(500).json({ error: `grade-pass failed: ${err instanceof Error ? err.message : String(err)}` });
13913
+ }
13914
+ });
13586
13915
  // GET /pool/lease-handback — U4.4 (docs/specs/u4-4-lease-handback.md §4): the
13587
13916
  // hand-back reconciler's status (state machine position, hysteresis window,
13588
13917
  // latch visibility — "hand-back suppressed until <t> — operator flip", last