From 49307a16e343be2dc925b74cd6d76bd9a4181312 Mon Sep 17 00:00:00 2001 From: besfeng23 <136943241+besfeng23@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:08:30 +0800 Subject: [PATCH] Fix Phase 3D verification gate checks --- app/admin/memory/verification/page.tsx | 46 ++++++++ docs/phase-3d-production-verification.md | 83 ++++++++++++++ .../admin-memory-route-guard-contract.ts | 31 +++++ .../admin-memory-verification-loader.ts | 96 ++++++++++++++++ tests/unit/admin-memory-verification.test.ts | 107 ++++++++++++++++++ 5 files changed, 363 insertions(+) create mode 100644 app/admin/memory/verification/page.tsx create mode 100644 docs/phase-3d-production-verification.md create mode 100644 lib/services/admin-memory-route-guard-contract.ts create mode 100644 lib/services/admin-memory-verification-loader.ts create mode 100644 tests/unit/admin-memory-verification.test.ts diff --git a/app/admin/memory/verification/page.tsx b/app/admin/memory/verification/page.tsx new file mode 100644 index 0000000..bc8558d --- /dev/null +++ b/app/admin/memory/verification/page.tsx @@ -0,0 +1,46 @@ +import Link from "next/link"; +import { AppShell } from "@/components/layout/app-shell"; +import { PageHeader } from "@/components/ui/page-header"; +import { SectionCard } from "@/components/ui/section-card"; +import { resolvePandoraServerSession, createRepositoryContextFromPandoraSession } from "@/lib/auth/pandora-server-session-resolver"; +import { createSupabaseServerClient } from "@/lib/supabase/server"; +import { SupabasePersistedMemoryReadRepository } from "@/lib/db/supabase-persisted-memory-read-repository"; +import { resolvePandoraRuntimeSafetyConfig } from "@/lib/config/pandora-runtime-safety-config"; +import { loadAdminMemoryVerification, type VerificationLine } from "@/lib/services/admin-memory-verification-loader"; + +export const dynamic = "force-dynamic"; + +const Badge = ({ line }: Readonly<{ line: VerificationLine }>) =>
  • {line.label}: {line.status} — {line.detail}
  • ; + +export default async function AdminMemoryVerificationPage() { + const namespace = "real_life"; + const session = await resolvePandoraServerSession(); + const context = createRepositoryContextFromPandoraSession({ sessionResult: session, namespace }); + const runtime = resolvePandoraRuntimeSafetyConfig(); + const supabase = await createSupabaseServerClient(); + const dto = await loadAdminMemoryVerification({ session, context: context.ok ? context.context : { namespace }, repository: new SupabasePersistedMemoryReadRepository(supabase as never), runtime }); + const loginPath = `/auth/login?next=${encodeURIComponent("/admin/memory/verification")}`; + return ( + + + {!session.ok ? Start operator session : null} + +
      + + + + + + + + +
    +
    +
      {dto.vercelEnvProof.map((line) => )}
    +
      {dto.checklist.map((line) => )}
    + +
      {dto.guardExpectations.map((guard) =>
    • {guard.route}: authenticated={String(guard.authenticatedSupabaseSessionRequired)}, adminOnly={String(guard.adminOnly)}, readOnly={String(guard.readOnly)}, namespaceScoped={String(guard.namespaceScoped)}, serverDerivedUserOnly={String(guard.serverDerivedUserOnly)}, publicReadAllowed={String(guard.publicReadAllowed)}, serviceRoleAllowed={String(guard.serviceRoleAllowed)}, mutationAllowed={String(guard.mutationAllowed)}
    • )}
    +
    +
    + ); +} diff --git a/docs/phase-3d-production-verification.md b/docs/phase-3d-production-verification.md new file mode 100644 index 0000000..d0c1802 --- /dev/null +++ b/docs/phase-3d-production-verification.md @@ -0,0 +1,83 @@ +# Phase 3D production verification + +Phase 3D adds a read-only production verification console for closure hardening. It does not enable public memory reads, public persistence, unsafe writes, model calls, embeddings, semantic retrieval, GPT Actions, or MCP. + +## Exact production URLs + +Replace `` with the deployed Vercel host. + +- `https:///admin/memory/verification` +- `https:///admin/memory/browser?namespace=real_life` +- `https:///admin/memory/audit?namespace=real_life` +- `https:///memory/browser` + +## Manual login test steps + +1. Open `https:///admin/memory/verification` in a private browser window. +2. Confirm unauthenticated access shows an authentication-required/login state and no persisted memory rows. +3. Log in with the reviewed Supabase operator account. +4. Re-open `/admin/memory/verification` and confirm the safety summary loads. +5. Open `/admin/memory/browser?namespace=real_life` and `/admin/memory/audit?namespace=real_life` from the same authenticated session. + +## Expected auth behavior + +- Admin memory routes require a Supabase-authenticated operator session. +- User identity must be server-derived from the session. +- Client-supplied `user_id`, `userId`, `client_user_id`, or `clientUserId` values must not be accepted. +- Unauthenticated sessions must see login/auth-required states, not memory rows. + +## Expected public redirect behavior + +- `https:///memory/browser` redirects to `/admin/memory/browser?namespace=real_life`. +- The public route must not instantiate a Supabase read repository. +- The public route must not render persisted rows, source proof, patch proof, or audit proof. +- No public proof or audit route should exist for memory browser data. + +## Expected read-only behavior + +The admin browser, audit viewer, and verification route must remain read-only. They must not expose edit, delete, persist, execute, patch-write, model, embedding, semantic retrieval, GPT Actions, MCP, or public-read controls. + +## Unsafe gate env variables to inspect + +These should be absent or not set to `true` unless a separate reviewed production operation explicitly requires them: + +- `PANDORA_ENABLE_MEMORY_INGEST_PRODUCTION_WRITES` +- `PANDORA_ENABLE_APPROVED_REVIEW_MEMORY_PERSISTENCE` +- `PANDORA_ENABLE_PUBLIC_MEMORY_READ` +- `PANDORA_ENABLE_PUBLIC_MEMORY_PERSISTENCE` +- `PANDORA_ENABLE_MODEL_CALLS` +- `PANDORA_ENABLE_EMBEDDINGS` +- `PANDORA_ENABLE_SEMANTIC_RETRIEVAL` +- `PANDORA_ENABLE_GPT_ACTIONS` +- `PANDORA_ENABLE_MCP` + +Useful proof-only values: + +- `VERCEL_GIT_COMMIT_SHA` or `GIT_COMMIT_SHA` +- `VERCEL_ENV` +- `VERCEL_URL` +- `PANDORA_SKILLS_COMMIT_SHA` +- `PANDORA_SKILLS_PROOF_STATUS` + +## Supabase/RLS proof expectations + +- `PANDORA_ENABLE_PERSISTED_MEMORY_READ` is the read-availability gate; verification must report disabled when it is false and must not force a database read proof. +- Reads are performed through the authenticated Supabase server client. +- Reads are scoped by server-derived user ID and namespace. +- Missing tables, missing proof fields, or RLS denial should display `unavailable` or `blocked`; the page must not crash. +- Browser proof should include source and patch fields where available. +- Audit proof should include audit route availability or an explicit unavailable state. + +## Close/no-close decision criteria + +Phase 3D can close after deployment only if: + +- The deployed commit SHA is visible or otherwise captured in release proof. +- `/admin/memory/verification` loads for an authenticated operator. +- `/admin/memory/browser?namespace=real_life` and `/admin/memory/audit?namespace=real_life` remain authenticated and read-only. +- `/memory/browser` redirects to the admin browser and does not render rows publicly. +- The persisted-memory read gate is enabled for the production proof pass and Supabase/RLS read proof is available. +- Public reads and unsafe mutation/integration gates are disabled. +- Source, patch, audit, and skills commit proof are available or explicitly recorded as not configured with a follow-up decision. + +Do not close if the persisted-memory read gate is disabled for the proof pass, public reads are enabled, unsafe mutation/integration gates are enabled without review, admin routes expose mutation controls, RLS blocks expected operator reads without explanation, or deployed proof cannot be tied to a commit. diff --git a/lib/services/admin-memory-route-guard-contract.ts b/lib/services/admin-memory-route-guard-contract.ts new file mode 100644 index 0000000..ebeab00 --- /dev/null +++ b/lib/services/admin-memory-route-guard-contract.ts @@ -0,0 +1,31 @@ +export type AdminMemoryRouteGuardExpectation = { + route: "/admin/memory/browser" | "/admin/memory/audit" | "/admin/memory/verification"; + authenticatedSupabaseSessionRequired: true; + adminOnly: true; + readOnly: true; + namespaceScoped: true; + serverDerivedUserOnly: true; + publicReadAllowed: false; + serviceRoleAllowed: false; + mutationAllowed: false; +}; + +export const adminMemoryRouteGuardExpectations: AdminMemoryRouteGuardExpectation[] = [ + "/admin/memory/browser", + "/admin/memory/audit", + "/admin/memory/verification", +].map((route) => ({ + route: route as AdminMemoryRouteGuardExpectation["route"], + authenticatedSupabaseSessionRequired: true, + adminOnly: true, + readOnly: true, + namespaceScoped: true, + serverDerivedUserOnly: true, + publicReadAllowed: false, + serviceRoleAllowed: false, + mutationAllowed: false, +})); + +export function getAdminMemoryRouteGuardExpectation(route: AdminMemoryRouteGuardExpectation["route"]) { + return adminMemoryRouteGuardExpectations.find((item) => item.route === route); +} diff --git a/lib/services/admin-memory-verification-loader.ts b/lib/services/admin-memory-verification-loader.ts new file mode 100644 index 0000000..273a5a1 --- /dev/null +++ b/lib/services/admin-memory-verification-loader.ts @@ -0,0 +1,96 @@ +import { resolvePandoraRuntimeSafetyConfig, type PandoraRuntimeGate, type PandoraRuntimeSafetyConfigResult } from "@/lib/config/pandora-runtime-safety-config"; +import { loadPersistedMemoryBrowserView } from "@/lib/services/persisted-memory-browser-loader"; +import type { PersistedMemoryReadRepository } from "@/lib/db/persisted-memory-read-repository-contract"; +import type { PersistedMemoryReadContext } from "@/lib/services/persisted-memory-read-contract"; +import type { PandoraServerSessionResult } from "@/lib/auth/pandora-server-session-contract"; +import { adminMemoryRouteGuardExpectations } from "@/lib/services/admin-memory-route-guard-contract"; + +export type VerificationStatus = "available" | "not configured" | "unavailable" | "blocked" | "disabled"; +export type VerificationLine = { label: string; status: VerificationStatus; detail: string }; +export type AdminMemoryVerificationDto = { + readOnly: true; + route: "/admin/memory/verification"; + commitSha: VerificationLine; + vercelEnvProof: VerificationLine[]; + persistedMemoryReadGateStatus: VerificationLine; + supabaseReadAvailability: VerificationLine; + memoryBrowserRouteStatus: VerificationLine; + auditRouteStatus: VerificationLine; + unsafeGateStatus: VerificationLine & { enabledDangerousGates: string[] }; + publicReadStatus: VerificationLine; + recommendation: VerificationLine & { closeRecommended: boolean }; + checklist: VerificationLine[]; + guardExpectations: typeof adminMemoryRouteGuardExpectations; +}; + +const dangerousGates: PandoraRuntimeGate[] = [ + "adminPersistenceConsoleEnabled", + "approvedReviewPersistenceEnabled", + "operatorQaFlowEnabled", + "ingestProductionWriteEnabled", + "publicMemoryReadEnabled", + "publicMemoryPersistenceEnabled", + "modelCallsEnabled", + "embeddingsEnabled", + "semanticRetrievalEnabled", + "gptActionsEnabled", + "mcpEnabled", +]; + +const value = (v?: string) => v && v.trim() ? v : undefined; +const configured = (label: string, envVar: string, env: Partial): VerificationLine => { + const v = value(env[envVar]); + return { label, status: v ? "available" : "not configured", detail: v ? `${envVar}=configured` : `${envVar}=not configured` }; +}; +const enabledDangerousGates = (runtime: PandoraRuntimeSafetyConfigResult) => dangerousGates.filter((gate) => runtime.config[gate]).map((gate) => `${runtime.gates[gate].envVar} (${gate})`); + +export async function loadAdminMemoryVerification(input: { session: PandoraServerSessionResult; context?: Partial; repository?: PersistedMemoryReadRepository; runtime?: PandoraRuntimeSafetyConfigResult; env?: Partial }): Promise { + const env = input.env ?? process.env; + const runtime = input.runtime ?? resolvePandoraRuntimeSafetyConfig(env); + const commit = value(env.VERCEL_GIT_COMMIT_SHA) ?? value(env.GIT_COMMIT_SHA) ?? value(env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA); + const persistedReadEnabled = runtime.config.persistedMemoryReadEnabled; + const persistedMemoryReadGateStatus: VerificationLine = { label: "Persisted memory read gate", status: persistedReadEnabled ? "available" : "disabled", detail: `${runtime.gates.persistedMemoryReadEnabled.envVar}=${persistedReadEnabled ? "true" : "false"}` }; + const browser = await loadPersistedMemoryBrowserView({ authenticated: input.session.ok, context: input.context, repository: input.repository, runtime, filters: { namespace: input.context?.namespace ?? "real_life" } }); + const authBlocked = !input.session.ok; + const dbBlocked = browser.blockers.find((b) => b.code !== "auth_required"); + const readGateBlocked = browser.blockers.find((b) => b.message.toLowerCase().includes("read gate is disabled")); + const supabaseReadAvailability: VerificationLine = authBlocked + ? { label: "Supabase read availability", status: "blocked", detail: "Authenticated admin/operator session is required before reading through RLS." } + : !persistedReadEnabled || readGateBlocked + ? { label: "Supabase read availability", status: "disabled", detail: "Persisted-memory read gate is disabled; no read proof was forced." } + : dbBlocked + ? { label: "Supabase read availability", status: "unavailable", detail: dbBlocked.message } + : { label: "Supabase read availability", status: "available", detail: browser.empty ? "Read path returned no rows for this user/namespace." : "Read path returned user-scoped rows." }; + const enabledUnsafe = enabledDangerousGates(runtime); + const unsafe = enabledUnsafe.length > 0; + const publicRead = runtime.config.publicMemoryReadEnabled; + const browserOk = input.session.ok && persistedReadEnabled ? "available" : "blocked"; + const auditOk = input.session.ok && persistedReadEnabled ? "available" : "blocked"; + const readProofAvailable = supabaseReadAvailability.status === "available"; + const closeRecommended = Boolean(commit) && !unsafe && input.session.ok && readProofAvailable; + return { + readOnly: true, + route: "/admin/memory/verification", + commitSha: { label: "Latest deployed commit SHA", status: commit ? "available" : "not configured", detail: commit ?? "VERCEL_GIT_COMMIT_SHA/GIT_COMMIT_SHA not configured" }, + vercelEnvProof: [configured("Vercel environment", "VERCEL_ENV", env), configured("Vercel URL", "VERCEL_URL", env), configured("Skills commit proof", "PANDORA_SKILLS_COMMIT_SHA", env), configured("Skills proof status", "PANDORA_SKILLS_PROOF_STATUS", env)], + persistedMemoryReadGateStatus, + supabaseReadAvailability, + memoryBrowserRouteStatus: { label: "Memory browser route", status: browserOk, detail: persistedReadEnabled ? "/admin/memory/browser is authenticated, namespace-scoped, and read-only by contract." : "/admin/memory/browser remains gated because persisted-memory reads are disabled." }, + auditRouteStatus: { label: "Audit route", status: auditOk, detail: persistedReadEnabled ? "/admin/memory/audit is authenticated, namespace-scoped, and read-only by contract." : "/admin/memory/audit remains gated because persisted-memory reads are disabled." }, + unsafeGateStatus: { label: "Unsafe mutation/integration gates", status: unsafe ? "blocked" : "disabled", detail: unsafe ? `Enabled dangerous gates: ${enabledUnsafe.join(", ")}. Do not close without review.` : "All dangerous mutation/integration gates are disabled.", enabledDangerousGates: enabledUnsafe }, + publicReadStatus: { label: "Public read status", status: publicRead ? "blocked" : "disabled", detail: publicRead ? "PANDORA_ENABLE_PUBLIC_MEMORY_READ=true; public reads are not closure-safe." : "Public memory reads are disabled; /memory/browser redirects to the admin route." }, + recommendation: { label: "Final recommendation", status: closeRecommended ? "available" : "blocked", detail: closeRecommended ? "Close after deployed manual checklist passes." : "Do not close until commit proof, auth/session, persisted read proof, and dangerous gate blockers are resolved.", closeRecommended }, + checklist: [ + { label: "Authenticated browser", status: input.session.ok && persistedReadEnabled ? "available" : "blocked", detail: "Open /admin/memory/browser?namespace=real_life while logged in." }, + { label: "Unauthenticated admin denial/login", status: "blocked", detail: "In a private browser, admin routes must show login/auth-required and no rows." }, + { label: "Public redirect", status: "disabled", detail: "/memory/browser must redirect to /admin/memory/browser?namespace=real_life." }, + { label: "Read-only behavior", status: "available", detail: "No edit, delete, persist, execute, model, embedding, retrieval, GPT Actions, or MCP controls." }, + { label: "Persisted read gate", status: persistedMemoryReadGateStatus.status, detail: persistedMemoryReadGateStatus.detail }, + { label: "Disabled unsafe gates", status: unsafe ? "blocked" : "disabled", detail: unsafe ? `Review enabled dangerous gates: ${enabledUnsafe.join(", ")}.` : "All dangerous gates are disabled." }, + { label: "Audit proof availability", status: auditOk, detail: "Audit route should show audit rows or an explicit unavailable state." }, + { label: "Source/patch proof availability", status: supabaseReadAvailability.status, detail: "Browser should show source and patch proof fields or an explicit unavailable state." }, + { label: "Skills commit proof availability", status: value(env.PANDORA_SKILLS_COMMIT_SHA) ? "available" : "not configured", detail: "PANDORA_SKILLS_COMMIT_SHA should be configured for closure proof." }, + ], + guardExpectations: adminMemoryRouteGuardExpectations, + }; +} diff --git a/tests/unit/admin-memory-verification.test.ts b/tests/unit/admin-memory-verification.test.ts new file mode 100644 index 0000000..ab66d59 --- /dev/null +++ b/tests/unit/admin-memory-verification.test.ts @@ -0,0 +1,107 @@ +import { readFileSync, existsSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { loadAdminMemoryVerification } from "@/lib/services/admin-memory-verification-loader"; +import { adminMemoryRouteGuardExpectations } from "@/lib/services/admin-memory-route-guard-contract"; +import type { PersistedMemoryReadRepository } from "@/lib/db/persisted-memory-read-repository-contract"; + +const root = process.cwd(); +const okList = { ok: true as const, items: [], page: 1, pageSize: 25 }; +let readCalls = 0; +const repo: PersistedMemoryReadRepository = { + listMemoryItems: async () => { readCalls += 1; return okList; }, + listMemorySources: async () => okList, + listMemoryPatches: async () => okList, + listMemoryAuditEvents: async () => okList, + getMemoryItemDetail: async () => ({ ok: false, blocker: { code: "not_found", message: "not found" } }), + getMemorySourceDetail: async () => ({ ok: false, blocker: { code: "not_found", message: "not found" } }), +}; +const session = { ok: true as const, session: { userId: "u1", authenticated: true, allowedNamespaces: ["real_life"], serverDerivedOnly: true, clientUserIdAccepted: false, serviceRoleUsed: false, publicReadAllowed: false, publicPersistenceAllowed: false }, blockers: [] }; +const safeEnv = { VERCEL_GIT_COMMIT_SHA: "abc123", PANDORA_SKILLS_COMMIT_SHA: "skills123", PANDORA_ENABLE_PERSISTED_MEMORY_READ: "true" }; + +describe("admin memory verification safety", () => { + it("builds a closure safety summary with public reads and unsafe gates disabled", async () => { + const dto = await loadAdminMemoryVerification({ session, context: { userId: "u1", namespace: "real_life" }, repository: repo, env: safeEnv }); + expect(dto.readOnly).toBe(true); + expect(dto.persistedMemoryReadGateStatus.status).toBe("available"); + expect(dto.publicReadStatus.status).toBe("disabled"); + expect(dto.unsafeGateStatus.status).toBe("disabled"); + expect(dto.unsafeGateStatus.enabledDangerousGates).toEqual([]); + expect(dto.recommendation.closeRecommended).toBe(true); + expect(dto.checklist.map((i) => i.label)).toEqual(expect.arrayContaining(["Authenticated browser", "Public redirect", "Read-only behavior", "Persisted read gate", "Audit proof availability", "Source/patch proof availability", "Skills commit proof availability"])); + }); + + it("does not override persistedMemoryReadEnabled when the deployed gate is false", async () => { + readCalls = 0; + const dto = await loadAdminMemoryVerification({ session, context: { userId: "u1", namespace: "real_life" }, repository: repo, env: { VERCEL_GIT_COMMIT_SHA: "abc123" } }); + expect(dto.persistedMemoryReadGateStatus.status).toBe("disabled"); + expect(dto.supabaseReadAvailability.status).toBe("disabled"); + expect(dto.supabaseReadAvailability.detail).toContain("no read proof was forced"); + expect(dto.recommendation.closeRecommended).toBe(false); + expect(readCalls).toBe(0); + }); + + it("blocks closure when public reads or unsafe writes are enabled and lists the enabled gates", async () => { + const dto = await loadAdminMemoryVerification({ session, context: { userId: "u1", namespace: "real_life" }, repository: repo, env: { ...safeEnv, PANDORA_ENABLE_PUBLIC_MEMORY_READ: "true", PANDORA_ENABLE_MEMORY_INGEST_PRODUCTION_WRITES: "true" } }); + expect(dto.publicReadStatus.status).toBe("blocked"); + expect(dto.unsafeGateStatus.status).toBe("blocked"); + expect(dto.unsafeGateStatus.detail).toContain("PANDORA_ENABLE_PUBLIC_MEMORY_READ"); + expect(dto.unsafeGateStatus.detail).toContain("PANDORA_ENABLE_MEMORY_INGEST_PRODUCTION_WRITES"); + expect(dto.recommendation.closeRecommended).toBe(false); + }); + + it("blocks closure for model, embedding, retrieval, GPT Actions, and MCP gates", async () => { + const dto = await loadAdminMemoryVerification({ session, context: { userId: "u1", namespace: "real_life" }, repository: repo, env: { ...safeEnv, PANDORA_ENABLE_MODEL_CALLS: "true", PANDORA_ENABLE_EMBEDDINGS: "true", PANDORA_ENABLE_SEMANTIC_RETRIEVAL: "true", PANDORA_ENABLE_GPT_ACTIONS: "true", PANDORA_ENABLE_MCP: "true" } }); + expect(dto.unsafeGateStatus.status).toBe("blocked"); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_MODEL_CALLS")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_EMBEDDINGS")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_SEMANTIC_RETRIEVAL")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_GPT_ACTIONS")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_MCP")); + expect(dto.recommendation.closeRecommended).toBe(false); + }); + + it("blocks closure when admin persistence console, approved persistence, operator QA, or public persistence gates are enabled", async () => { + const dto = await loadAdminMemoryVerification({ session, context: { userId: "u1", namespace: "real_life" }, repository: repo, env: { ...safeEnv, PANDORA_ENABLE_ADMIN_PERSISTENCE_CONSOLE: "true", PANDORA_ENABLE_APPROVED_REVIEW_MEMORY_PERSISTENCE: "true", PANDORA_ENABLE_OPERATOR_MEMORY_QA_FLOW: "true", PANDORA_ENABLE_PUBLIC_MEMORY_PERSISTENCE: "true" } }); + expect(dto.unsafeGateStatus.status).toBe("blocked"); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_ADMIN_PERSISTENCE_CONSOLE")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_APPROVED_REVIEW_MEMORY_PERSISTENCE")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_OPERATOR_MEMORY_QA_FLOW")); + expect(dto.unsafeGateStatus.enabledDangerousGates.join(" ")).toEqual(expect.stringContaining("PANDORA_ENABLE_PUBLIC_MEMORY_PERSISTENCE")); + expect(dto.recommendation.closeRecommended).toBe(false); + }); + + it("documents consistent read-only route guard expectations", () => { + expect(adminMemoryRouteGuardExpectations).toHaveLength(3); + for (const guard of adminMemoryRouteGuardExpectations) { + expect(guard.authenticatedSupabaseSessionRequired).toBe(true); + expect(guard.adminOnly).toBe(true); + expect(guard.readOnly).toBe(true); + expect(guard.namespaceScoped).toBe(true); + expect(guard.serverDerivedUserOnly).toBe(true); + expect(guard.publicReadAllowed).toBe(false); + expect(guard.serviceRoleAllowed).toBe(false); + expect(guard.mutationAllowed).toBe(false); + } + }); + + it("keeps public route as redirect-only and avoids public proof/audit routes", () => { + const publicPage = readFileSync(join(root, "app/memory/browser/page.tsx"), "utf8"); + expect(publicPage).toContain('redirect("/admin/memory/browser?namespace=real_life")'); + expect(publicPage).not.toMatch(/SupabasePersistedMemoryReadRepository|loadPersistedMemoryBrowserView|memory_items|audit_logs|listMemory/i); + expect(existsSync(join(root, "app/memory/audit/page.tsx"))).toBe(false); + expect(existsSync(join(root, "app/memory/proof/page.tsx"))).toBe(false); + expect(existsSync(join(root, "app/api/memory/browser/route.ts"))).toBe(false); + expect(existsSync(join(root, "app/api/memory/audit/route.ts"))).toBe(false); + }); + + it("admin browser, audit, and verification routes are read-only and service-role-free", () => { + const files = ["app/admin/memory/browser/page.tsx", "app/admin/memory/audit/page.tsx", "app/admin/memory/verification/page.tsx", "lib/services/admin-memory-verification-loader.ts", "lib/services/admin-memory-route-guard-contract.ts"]; + const text = files.map((f) => readFileSync(join(root, f), "utf8")).join("\n"); + expect(text).toMatch(/resolvePandoraServerSession/); + expect(text).not.toMatch(/SUPABASE_SERVICE_ROLE|service-role|createServiceRole|service_role_key/i); + expect(text).not.toMatch(/\.insert\(|\.update\(|\.delete\(|\.upsert\(|executeApproved|persistApproved|appendReviewDecision/i); + const imports = text.split("\n").filter((line) => line.startsWith("import ")).join("\n"); + expect(imports).not.toMatch(/openai|anthropic|embedding|pgvector|semantic|gpt-actions|mcp/i); + }); +});