Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions app/admin/memory/verification/page.tsx
Original file line number Diff line number Diff line change
@@ -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 }>) => <li><strong>{line.label}:</strong> {line.status} — {line.detail}</li>;

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 });
Comment on lines +17 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require operator capability before verification reads

This route treats any successful Supabase session as sufficient and then loads the verification DTO/repository, while the new guard contract and page copy describe the route as admin/operator-only. In this codebase resolvePandoraServerSession creates ordinary real sessions with empty adminCapabilities and isInternalOperator: false, and other admin APIs such as readiness explicitly require those capabilities, so a non-operator authenticated user can still access the closure proof for their namespace here.

Useful? React with 👍 / 👎.

const loginPath = `/auth/login?next=${encodeURIComponent("/admin/memory/verification")}`;
return (
<AppShell>
<PageHeader eyebrow="Internal admin" title="Production memory verification" description="Read-only, authenticated/admin-only verification route for Phase 3D closure safety. It reports deployment proof, read availability, route guard status, persisted read gate, unsafe mutation/integration gates, public read status, and a close/no-close recommendation without enabling public reads or writes." />
{!session.ok ? <SectionCard title="Authentication required" description="No persisted rows or proof data are exposed without a Supabase operator session."><Link className="button-link button-link--primary" href={loginPath}>Start operator session</Link></SectionCard> : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Hide verification proof until authenticated

When session.ok is false this branch only adds the login card, but the page still continues to render the safety summary and Vercel/environment proof sections below, including commit and gate status details. For an unauthenticated request to this admin-only route, that exposes the internal verification proof that the page copy says requires a Supabase operator session; return the auth-required view early or gate the remaining sections behind session.ok.

Useful? React with 👍 / 👎.

<SectionCard title="Safety summary" description="Missing values are shown as not configured, unavailable, or blocked instead of crashing.">
<ul>
<Badge line={dto.commitSha} />
<Badge line={dto.persistedMemoryReadGateStatus} />
<Badge line={dto.supabaseReadAvailability} />
<Badge line={dto.memoryBrowserRouteStatus} />
<Badge line={dto.auditRouteStatus} />
<Badge line={dto.unsafeGateStatus} />
<Badge line={dto.publicReadStatus} />
<Badge line={dto.recommendation} />
</ul>
</SectionCard>
<SectionCard title="Vercel/environment proof" description="Only configured/not-configured proof is displayed; secret values are never printed."><ul>{dto.vercelEnvProof.map((line) => <Badge key={line.label} line={line} />)}</ul></SectionCard>
<SectionCard title="Manual verification checklist" description="Compact production checklist for closure review."><ul>{dto.checklist.map((line) => <Badge key={line.label} line={line} />)}</ul></SectionCard>
<SectionCard title="Route guard consistency" description="All Phase 3D admin memory routes share these expectations: Supabase session, server-derived user context, namespace scope, and read-only behavior.">
<ul>{dto.guardExpectations.map((guard) => <li key={guard.route}><strong>{guard.route}</strong>: 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)}</li>)}</ul>
</SectionCard>
</AppShell>
);
}
83 changes: 83 additions & 0 deletions docs/phase-3d-production-verification.md
Original file line number Diff line number Diff line change
@@ -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 `<production-host>` with the deployed Vercel host.

- `https://<production-host>/admin/memory/verification`
- `https://<production-host>/admin/memory/browser?namespace=real_life`
- `https://<production-host>/admin/memory/audit?namespace=real_life`
- `https://<production-host>/memory/browser`

## Manual login test steps

1. Open `https://<production-host>/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://<production-host>/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.
31 changes: 31 additions & 0 deletions lib/services/admin-memory-route-guard-contract.ts
Original file line number Diff line number Diff line change
@@ -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);
}
96 changes: 96 additions & 0 deletions lib/services/admin-memory-verification-loader.ts
Original file line number Diff line number Diff line change
@@ -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<NodeJS.ProcessEnv>): 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<PersistedMemoryReadContext>; repository?: PersistedMemoryReadRepository; runtime?: PandoraRuntimeSafetyConfigResult; env?: Partial<NodeJS.ProcessEnv> }): Promise<AdminMemoryVerificationDto> {
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,
};
}
Loading
Loading