diff --git a/frontend/src/app/api/health/route.ts b/frontend/src/app/api/health/route.ts index e8cab627..fa8da833 100644 --- a/frontend/src/app/api/health/route.ts +++ b/frontend/src/app/api/health/route.ts @@ -1,5 +1,7 @@ import { NextResponse } from "next/server"; +import { getTruthSourceRuntimeIdentity } from "@/lib/truth-source-runtime-identity"; + type Check = { status: "ok" | "degraded" | "blocked"; message?: string; @@ -58,12 +60,17 @@ function aggregate(checks: Record) { } export async function GET() { + const truthSourceIdentity = getTruthSourceRuntimeIdentity(); const checks = { web: { status: "ok" } satisfies Check, supabasePublicConfig: envCheck(["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY"]), supabaseServiceRole: envCheck(["SUPABASE_SERVICE_ROLE_KEY"]), modelProvider: anyEnvCheck(["LLM_MODELS_JSON", "OPENAI_API_KEY", "LLM_API_KEY", "DEEPSEEK_API_KEY"]), jyotishApi: await jyotishApiCheck(), + researchTruthSource: { + status: truthSourceIdentity.status, + message: truthSourceIdentity.mountStatus === "mounted" ? undefined : truthSourceIdentity.mountStatus, + } satisfies Check, }; const status = aggregate(checks); return NextResponse.json( @@ -73,6 +80,7 @@ export async function GET() { deployment: { gitCommit, }, + truthSource: truthSourceIdentity, checks, }, { status: status === "ok" ? 200 : 503 }, diff --git a/frontend/src/lib/truth-source-runtime-identity.ts b/frontend/src/lib/truth-source-runtime-identity.ts new file mode 100644 index 00000000..3297b130 --- /dev/null +++ b/frontend/src/lib/truth-source-runtime-identity.ts @@ -0,0 +1,82 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; + +type OracleSummary = { + ready: string[]; + partial: string[]; + blocked: string[]; +}; + +export type TruthSourceRuntimeIdentity = { + status: "ok" | "blocked"; + path: string; + mountStatus: "mounted" | "not_mounted"; + commit: string; + skillVersion: string; + evidencePacketCount: number | null; + oracleSummary: OracleSummary; + claimGateStatus: "ready" | "partial_or_blocked_present" | "not_mounted"; +}; + +export const DEFAULT_RESEARCH_TRUTH_SOURCE_PATH = "/Users/wuyongnaren/Documents/印度占星"; + +function readJson(path: string): unknown { + return JSON.parse(readFileSync(path, "utf8")); +} + +function readEvidencePacketCount(researchPath: string): number | null { + const indexPath = join(researchPath, "references/oracle/evidence_packet_index_2026_07_19.json"); + if (!existsSync(indexPath)) return null; + const data = readJson(indexPath) as { summary?: { packet_count?: unknown } }; + return typeof data.summary?.packet_count === "number" ? data.summary.packet_count : null; +} + +function readSkillVersion(researchPath: string): string { + const skillPath = join(researchPath, "SKILL.md"); + if (!existsSync(skillPath)) return "unknown"; + const source = readFileSync(skillPath, "utf8"); + const version = source.match(/(?:version|Version)[::]\s*([^\n]+)/); + return version?.[1]?.trim() || "present_unversioned"; +} + +function readCommit(researchPath: string): string { + const headPath = join(researchPath, ".git/HEAD"); + if (!existsSync(headPath)) return "unknown"; + const head = readFileSync(headPath, "utf8").trim(); + if (!head.startsWith("ref: ")) return head.slice(0, 40); + const refPath = join(researchPath, ".git", head.slice(5)); + if (!existsSync(refPath)) return "unknown"; + return readFileSync(refPath, "utf8").trim().slice(0, 40); +} + +export function getTruthSourceRuntimeIdentity( + researchPath = process.env.JYOTISH_RESEARCH_TRUTH_SOURCE_PATH ?? DEFAULT_RESEARCH_TRUTH_SOURCE_PATH, +): TruthSourceRuntimeIdentity { + if (!existsSync(researchPath)) { + return { + status: "blocked", + path: researchPath, + mountStatus: "not_mounted", + commit: "unknown", + skillVersion: "unknown", + evidencePacketCount: null, + oracleSummary: { ready: [], partial: [], blocked: ["research_truth_source_not_mounted"] }, + claimGateStatus: "not_mounted", + }; + } + + const evidencePacketCount = readEvidencePacketCount(researchPath); + const blocked = evidencePacketCount === null ? ["evidence_packet_index_missing"] : []; + const partial = ["commercial_runtime_identity_only"]; + + return { + status: blocked.length ? "blocked" : "ok", + path: researchPath, + mountStatus: "mounted", + commit: readCommit(researchPath), + skillVersion: readSkillVersion(researchPath), + evidencePacketCount, + oracleSummary: { ready: [], partial, blocked }, + claimGateStatus: blocked.length ? "partial_or_blocked_present" : "partial_or_blocked_present", + }; +} diff --git a/frontend/tests/truth-source-runtime-identity.test.ts b/frontend/tests/truth-source-runtime-identity.test.ts new file mode 100644 index 00000000..d02a8933 --- /dev/null +++ b/frontend/tests/truth-source-runtime-identity.test.ts @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; + +const identitySource = readFileSync( + new URL("../src/lib/truth-source-runtime-identity.ts", import.meta.url), + "utf8", +); +const healthSource = readFileSync( + new URL("../src/app/api/health/route.ts", import.meta.url), + "utf8", +); + +test("truth source identity records research source without pretending local mount is always present", () => { + assert.match(identitySource, /DEFAULT_RESEARCH_TRUTH_SOURCE_PATH/); + assert.ok(identitySource.includes("/Users/wuyongnaren/Documents/印度占星")); + assert.match(identitySource, /not_mounted/); + assert.match(identitySource, /claimGateStatus/); + assert.match(identitySource, /evidencePacketCount/); + assert.match(identitySource, /oracleSummary/); +}); + +test("health endpoint exposes truth source identity beside deployment identity", () => { + assert.match(healthSource, /getTruthSourceRuntimeIdentity/); + assert.match(healthSource, /truthSource/); + assert.match(healthSource, /truthSource:\s*truthSourceIdentity/); + assert.match(healthSource, /researchTruthSource/); +});