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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +83 -12
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutonomousRealCheckAnnotator.d.ts +105 -0
- package/dist/core/AutonomousRealCheckAnnotator.d.ts.map +1 -0
- package/dist/core/AutonomousRealCheckAnnotator.js +122 -0
- package/dist/core/AutonomousRealCheckAnnotator.js.map +1 -0
- package/dist/core/AutonomousRunStore.d.ts +29 -0
- package/dist/core/AutonomousRunStore.d.ts.map +1 -1
- package/dist/core/AutonomousRunStore.js +38 -0
- package/dist/core/AutonomousRunStore.js.map +1 -1
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +47 -1
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts +12 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.js +56 -4
- package/dist/core/CircuitBreakingIntelligenceProvider.js.map +1 -1
- package/dist/core/CompletionEvaluator.d.ts +62 -2
- package/dist/core/CompletionEvaluator.d.ts.map +1 -1
- package/dist/core/CompletionEvaluator.js +124 -6
- package/dist/core/CompletionEvaluator.js.map +1 -1
- package/dist/core/DecisionQualityRecorderImpl.d.ts +192 -0
- package/dist/core/DecisionQualityRecorderImpl.d.ts.map +1 -0
- package/dist/core/DecisionQualityRecorderImpl.js +363 -0
- package/dist/core/DecisionQualityRecorderImpl.js.map +1 -0
- package/dist/core/IntelligenceRouter.d.ts +33 -0
- package/dist/core/IntelligenceRouter.d.ts.map +1 -1
- package/dist/core/IntelligenceRouter.js +240 -54
- package/dist/core/IntelligenceRouter.js.map +1 -1
- package/dist/core/JudgmentProvenanceLog.d.ts +102 -2
- package/dist/core/JudgmentProvenanceLog.d.ts.map +1 -1
- package/dist/core/JudgmentProvenanceLog.js +239 -11
- package/dist/core/JudgmentProvenanceLog.js.map +1 -1
- package/dist/core/MessagingToneGate.d.ts +30 -0
- package/dist/core/MessagingToneGate.d.ts.map +1 -1
- package/dist/core/MessagingToneGate.js +63 -0
- package/dist/core/MessagingToneGate.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +11 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +32 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +24 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/decisionGradingPass.d.ts +89 -0
- package/dist/core/decisionGradingPass.d.ts.map +1 -0
- package/dist/core/decisionGradingPass.js +188 -0
- package/dist/core/decisionGradingPass.js.map +1 -0
- package/dist/core/decisionQualityTypes.d.ts +170 -0
- package/dist/core/decisionQualityTypes.d.ts.map +1 -0
- package/dist/core/decisionQualityTypes.js +85 -0
- package/dist/core/decisionQualityTypes.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
- package/dist/core/machineCoherenceManifest.js +4 -0
- package/dist/core/machineCoherenceManifest.js.map +1 -1
- package/dist/core/types.d.ts +69 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/data/provenanceCoverage.d.ts +156 -0
- package/dist/data/provenanceCoverage.d.ts.map +1 -0
- package/dist/data/provenanceCoverage.js +674 -0
- package/dist/data/provenanceCoverage.js.map +1 -0
- package/dist/monitoring/ExternalHogDecisionStore.d.ts +256 -0
- package/dist/monitoring/ExternalHogDecisionStore.d.ts.map +1 -0
- package/dist/monitoring/ExternalHogDecisionStore.js +481 -0
- package/dist/monitoring/ExternalHogDecisionStore.js.map +1 -0
- package/dist/monitoring/ExternalHogRealAdapters.d.ts +6 -2
- package/dist/monitoring/ExternalHogRealAdapters.d.ts.map +1 -1
- package/dist/monitoring/ExternalHogRealAdapters.js +2 -2
- package/dist/monitoring/ExternalHogRealAdapters.js.map +1 -1
- package/dist/monitoring/ExternalHogScanTick.d.ts +95 -3
- package/dist/monitoring/ExternalHogScanTick.d.ts.map +1 -1
- package/dist/monitoring/ExternalHogScanTick.js +102 -9
- package/dist/monitoring/ExternalHogScanTick.js.map +1 -1
- package/dist/monitoring/ExternalHogSentinel.d.ts +71 -3
- package/dist/monitoring/ExternalHogSentinel.d.ts.map +1 -1
- package/dist/monitoring/ExternalHogSentinel.js +126 -2
- package/dist/monitoring/ExternalHogSentinel.js.map +1 -1
- package/dist/monitoring/ExternalHogServerPrimitives.d.ts +10 -2
- package/dist/monitoring/ExternalHogServerPrimitives.d.ts.map +1 -1
- package/dist/monitoring/ExternalHogServerPrimitives.js +1 -1
- package/dist/monitoring/ExternalHogServerPrimitives.js.map +1 -1
- package/dist/monitoring/FeatureMetricsLedger.d.ts +304 -0
- package/dist/monitoring/FeatureMetricsLedger.d.ts.map +1 -1
- package/dist/monitoring/FeatureMetricsLedger.js +731 -0
- package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +55 -1
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +15 -1
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/fileRoutes.d.ts.map +1 -1
- package/dist/server/fileRoutes.js +9 -0
- package/dist/server/fileRoutes.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +334 -5
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/data/provenanceCoverage.ts +850 -0
- package/src/scaffold/templates/jobs/instar/llm-decision-grading.md +32 -0
- package/src/scaffold/templates.ts +2 -1
- package/upgrades/1.3.831.md +30 -0
- package/upgrades/1.3.832.md +22 -0
- package/upgrades/side-effects/llm-decision-quality-meter.md +95 -0
- package/upgrades/side-effects/messaging-tone-gate-provenance-enrollment.md +68 -0
package/dist/server/routes.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|