From c1b3a16244afb0f30e5dddfa8aaff9dacfc473be Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Sun, 26 Jul 2026 22:45:21 +0800 Subject: [PATCH] fix(review): register the /v1/internal/status route so handleInternalStatus is reachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleInternalStatus (src/review/ops.ts) is fully implemented, documented, and unit-tested, but its route was never added to routes.ts — unlike its two siblings handleInternalDecision and handleInternalCalibration, which are registered at /v1/internal/decision and /v1/internal/calibration. Register /v1/internal/status the same way (same INTERNAL_JOB_TOKEN gate, same internalOpsAgentConfig), so the operator agent-health endpoint is actually reachable. Closes #8904 Co-Authored-By: Claude Opus 4.8 --- src/api/routes.ts | 8 ++++- ...utes-internal-decision-calibration.test.ts | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/api/routes.ts b/src/api/routes.ts index 827ed51fdc..75ab2ce5b4 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -296,7 +296,7 @@ import { buildAutomationState } from "../services/automation-state"; import { loadGatePrecisionReport } from "../services/gate-precision"; import { computeOpsStats, isOpsEnabled, resolveOpsManifestOverride } from "../review/ops-wire"; import { deleteLiveOverride, listOverrideAudit, loadOverride, loadShadowOverride, sanitizeOverridePayload, authoritativeGateOverride, toLiveGateThresholdFields, type StorageEnv } from "../review/auto-apply"; -import { handleInternalCalibration, handleInternalDecision, type OpsAgentConfig } from "../review/ops"; +import { handleInternalCalibration, handleInternalDecision, handleInternalStatus, type OpsAgentConfig } from "../review/ops"; import { computeParityReadiness, isParityAuditEnabled } from "../review/parity-wire"; import { computePredictedGateAgreement } from "../review/predicted-gate-agreement"; import { computeContributorGateEval, contributorFairnessFlags, computeBlendedContributorGateEval, contributorGlobalFairnessFlags } from "../review/contributor-gate-eval"; @@ -4651,6 +4651,12 @@ export function createApp() { // Fails safe to an empty-but-shaped report when there is no review signal yet. Aggregate counts only. app.get("/v1/internal/calibration", (c) => handleInternalCalibration(c.req.raw, c.env, internalOpsAgentConfig(c.env))); + // Operator per-agent health/verdict breakdown, manual-rate, stuck targets, config-invariant violations, and + // recent decisions (#8904 — the handler existed and was unit-tested but its route was never registered, unlike + // its two siblings above). Bearer-gated by the `/v1/internal/*` middleware (INTERNAL_JOB_TOKEN); + // handleInternalStatus re-checks that same token. Aggregate review state only — no PR content. + app.get("/v1/internal/status", (c) => handleInternalStatus(c.req.raw, c.env, internalOpsAgentConfig(c.env))); + // Operator calibration trend (#8113): weekly per-rule fired/decided/precision plus backtest-run verdict // counts, re-bucketed live from audit_events (no cron rollup — see rule-calibration-trend.ts's header). Sibling // of /v1/internal/calibration above, same INTERNAL_JOB_TOKEN gate via the /v1/internal/* middleware. diff --git a/test/unit/routes-internal-decision-calibration.test.ts b/test/unit/routes-internal-decision-calibration.test.ts index 750b6a7a38..15672eb550 100644 --- a/test/unit/routes-internal-decision-calibration.test.ts +++ b/test/unit/routes-internal-decision-calibration.test.ts @@ -85,3 +85,32 @@ describe("GET /v1/internal/calibration — operator calibration endpoint", () => expect(((await res.json()) as { project: string }).project).toBe("loopover"); }); }); + +// #8904: handleInternalStatus was fully implemented + unit-tested but its route was never registered in +// routes.ts, unlike its two siblings above. These assert the newly-wired route end-to-end. +describe("GET /v1/internal/status — operator agent-health endpoint (#8904)", () => { + it("401s without the internal token (the /v1/internal/* middleware gate)", async () => { + const app = createApp(); + const env = createTestEnv(); + expect((await app.request("/v1/internal/status", {}, env)).status).toBe(401); + expect((await app.request("/v1/internal/status", { headers: { authorization: "Bearer nope" } }, env)).status).toBe(401); + }); + + it("200s with the agent-health report (fail-safe empty), scoped to the app slug", async () => { + const app = createApp(); + const env = createTestEnv(); // GITHUB_APP_SLUG defaults to "loopover-orb" + const res = await app.request("/v1/internal/status", { headers: bearer(env) }, env); + expect(res.status).toBe(200); + const body = (await res.json()) as { + project: string; + counts: { byStatus: unknown; byVerdict: unknown }; + health: { manualRate: number }; + }; + expect(body.project).toBe("loopover-orb"); + expect(body.counts).toBeTruthy(); + expect(body.counts.byStatus).toBeTruthy(); + expect(typeof body.health.manualRate).toBe("number"); + // Privacy: aggregate review state only — never actor logins / trust internals. + expect(JSON.stringify(body)).not.toMatch(/login|actor|reward|payout|trust|wallet|hotkey/i); + }); +});