Skip to content

Commit 7ed1f9e

Browse files
authored
Add Pandora Shadow Context Pack Lab
Merge shadow context pack lab.
1 parent 87857c9 commit 7ed1f9e

24 files changed

Lines changed: 207 additions & 15 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { NextResponse, type NextRequest } from "next/server";
2+
import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver";
3+
import { createSupabaseServerClient } from "@/lib/supabase/server";
4+
import { updateShadowContextPackStatus, type ShadowContextPackDbClient } from "@/lib/services/pandora-shadow-context-pack-service";
5+
export const dynamic = "force-dynamic";
6+
export async function POST(request: NextRequest, context: { params: Promise<{ id: string }> }) { const body = await request.json().catch(() => ({})); const rejected = await assertNoClientUserIdOverride(request, body); if (rejected) return NextResponse.json({ ok: false, blockers: rejected.blockers }, { status: 400 }); const session = await resolvePandoraServerSession({ request }); if (!session.ok) return NextResponse.json({ ok: false, blockers: session.blockers }, { status: 401 }); try { const { id } = await context.params; const supabase = await createSupabaseServerClient(); const pack = await updateShadowContextPackStatus(supabase as unknown as ShadowContextPackDbClient, { userId: session.session.userId, shadowPackId: id, status: "archived", reason: typeof body.reason === "string" ? body.reason : undefined }); return NextResponse.json({ ok: true, pack, no_core_memory_mutation_performed: true, no_promotion_performed: true }); } catch (e) { return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : "Shadow pack update failed" }, { status: 400 }); } }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { NextResponse, type NextRequest } from "next/server";
2+
import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver";
3+
import { createSupabaseServerClient } from "@/lib/supabase/server";
4+
import { updateShadowContextPackStatus, type ShadowContextPackDbClient } from "@/lib/services/pandora-shadow-context-pack-service";
5+
export const dynamic = "force-dynamic";
6+
export async function POST(request: NextRequest, context: { params: Promise<{ id: string }> }) { const body = await request.json().catch(() => ({})); const rejected = await assertNoClientUserIdOverride(request, body); if (rejected) return NextResponse.json({ ok: false, blockers: rejected.blockers }, { status: 400 }); const session = await resolvePandoraServerSession({ request }); if (!session.ok) return NextResponse.json({ ok: false, blockers: session.blockers }, { status: 401 }); try { const { id } = await context.params; const supabase = await createSupabaseServerClient(); const pack = await updateShadowContextPackStatus(supabase as unknown as ShadowContextPackDbClient, { userId: session.session.userId, shadowPackId: id, status: "rejected", reason: typeof body.reason === "string" ? body.reason : undefined }); return NextResponse.json({ ok: true, pack, no_core_memory_mutation_performed: true, no_promotion_performed: true }); } catch (e) { return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : "Shadow pack update failed" }, { status: 400 }); } }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { NextResponse, type NextRequest } from "next/server";
2+
import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver";
3+
import { createSupabaseServerClient } from "@/lib/supabase/server";
4+
import { updateShadowContextPackStatus, type ShadowContextPackDbClient } from "@/lib/services/pandora-shadow-context-pack-service";
5+
export const dynamic = "force-dynamic";
6+
export async function POST(request: NextRequest, context: { params: Promise<{ id: string }> }) { const body = await request.json().catch(() => ({})); const rejected = await assertNoClientUserIdOverride(request, body); if (rejected) return NextResponse.json({ ok: false, blockers: rejected.blockers }, { status: 400 }); const session = await resolvePandoraServerSession({ request }); if (!session.ok) return NextResponse.json({ ok: false, blockers: session.blockers }, { status: 401 }); try { const { id } = await context.params; const supabase = await createSupabaseServerClient(); const pack = await updateShadowContextPackStatus(supabase as unknown as ShadowContextPackDbClient, { userId: session.session.userId, shadowPackId: id, status: "reviewed", reason: typeof body.reason === "string" ? body.reason : undefined }); return NextResponse.json({ ok: true, pack, no_core_memory_mutation_performed: true, no_promotion_performed: true }); } catch (e) { return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : "Shadow pack update failed" }, { status: 400 }); } }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { NextResponse, type NextRequest } from "next/server";
2+
import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver";
3+
import { createSupabaseServerClient } from "@/lib/supabase/server";
4+
import { getShadowContextPack, type ShadowContextPackDbClient } from "@/lib/services/pandora-shadow-context-pack-service";
5+
export const dynamic = "force-dynamic";
6+
export async function GET(request: NextRequest, context: { params: Promise<{ id: string }> }) { const rejected = await assertNoClientUserIdOverride(request); if (rejected) return NextResponse.json({ ok: false, blockers: rejected.blockers }, { status: 400 }); const session = await resolvePandoraServerSession({ request }); if (!session.ok) return NextResponse.json({ ok: false, blockers: session.blockers }, { status: 401 }); try { const { id } = await context.params; const supabase = await createSupabaseServerClient(); const pack = await getShadowContextPack(supabase as unknown as ShadowContextPackDbClient, { userId: session.session.userId, shadowPackId: id }); return NextResponse.json({ ok: true, pack }); } catch (e) { return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : "Not found" }, { status: 404 }); } }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { NextResponse, type NextRequest } from "next/server";
2+
import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver";
3+
import { createSupabaseServerClient } from "@/lib/supabase/server";
4+
import { listShadowContextPacks, type ShadowContextPackDbClient } from "@/lib/services/pandora-shadow-context-pack-service";
5+
export const dynamic = "force-dynamic";
6+
export async function GET(request: NextRequest) { const rejected = await assertNoClientUserIdOverride(request); if (rejected) return NextResponse.json({ ok: false, blockers: rejected.blockers }, { status: 400 }); const session = await resolvePandoraServerSession({ request }); if (!session.ok) return NextResponse.json({ ok: false, blockers: session.blockers }, { status: 401 }); const url = new URL(request.url); const supabase = await createSupabaseServerClient(); const packs = await listShadowContextPacks(supabase as unknown as ShadowContextPackDbClient, { userId: session.session.userId, namespace: url.searchParams.get("namespace") ?? undefined, status: url.searchParams.get("status") ?? undefined, limit: 25 }); return NextResponse.json({ ok: true, packs }); }

components/pandora/OperatorActionComposer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ export function OperatorActionComposer() {
55
const [actionType, setActionType] = useState("verify_namespace_invariants");
66
const [namespace, setNamespace] = useState("real_life");
77
async function prepare() { await fetch("/api/pandora/operator-actions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action_type: actionType, namespace, mode: "dry_run", payload: { source: "operator_action_center" } }) }); window.location.reload(); }
8-
return <div className="pd-composer"><div className="pd-mini-grid"><label className="pd-mini"><span>Action type</span><select value={actionType} onChange={(e) => setActionType(e.target.value)}><option value="verify_namespace_invariants">verify_namespace_invariants</option><option value="verify_pack_supersession">verify_pack_supersession</option><option value="check_retrieval_eval_status">check_retrieval_eval_status</option><option value="refresh_dashboard_snapshot">refresh_dashboard_snapshot</option><option value="prepare_distill_smoke_plan">prepare_distill_smoke_plan</option></select></label><label className="pd-mini"><span>Namespace</span><select value={namespace} onChange={(e) => setNamespace(e.target.value)}><option value="real_life">real_life</option><option value="au">au</option></select></label></div><button className="button-link button-link--primary" type="button" onClick={prepare}>Prepare dry-run</button><p className="pd-muted">Only dry-run or queued-only proposals are available. No core memory mutation is available from this card.</p></div>;
8+
return <div className="pd-composer"><div className="pd-mini-grid"><label className="pd-mini"><span>Action type</span><select value={actionType} onChange={(e) => setActionType(e.target.value)}><option value="verify_namespace_invariants">verify_namespace_invariants</option><option value="verify_pack_supersession">verify_pack_supersession</option><option value="check_retrieval_eval_status">check_retrieval_eval_status</option><option value="refresh_dashboard_snapshot">refresh_dashboard_snapshot</option><option value="prepare_distill_smoke_plan">prepare_distill_smoke_plan</option><option value="prepare_shadow_context_pack">prepare_shadow_context_pack</option></select></label><label className="pd-mini"><span>Namespace</span><select value={namespace} onChange={(e) => setNamespace(e.target.value)}><option value="real_life">real_life</option><option value="au">au</option></select></label></div><button className="button-link button-link--primary" type="button" onClick={prepare}>Prepare dry-run</button><p className="pd-muted">For prepare_shadow_context_pack: Creates shadow candidate only after approval/execution. No promotion. Only dry-run or queued-only proposals are available. No core memory mutation is available from this card.</p></div>;
99
}

components/pandora/PandoraDashboard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { StatCard } from "./StatCard";
1212
import { TopBar } from "./TopBar";
1313
import { VerificationConsoleCard } from "./VerificationConsoleCard";
1414
import { OperatorActionCenterCard } from "./OperatorActionCenterCard";
15+
import { ShadowContextPackLabCard } from "./ShadowContextPackLabCard";
1516
import { WorkQueueCard } from "./WorkQueueCard";
1617
import type { PandoraDashboardData, StatItem } from "./types";
1718
import { useState } from "react";
@@ -45,6 +46,7 @@ export function PandoraDashboard({ dashboardData }: { dashboardData: PandoraDash
4546
</section>
4647
<VerificationConsoleCard verification={dashboardData.verification} />
4748
<OperatorActionCenterCard data={dashboardData.operatorActions} />
49+
<ShadowContextPackLabCard data={dashboardData.shadowContextPackLab} />
4850
<div className="pd-dashboard-grid">
4951
<div className="pd-dashboard-col pd-dashboard-col-wide">
5052
<MemorySpacesCard spaces={dashboardData.memorySpaces} />
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"use client";
2+
import type { ShadowContextPackSummary } from "./types";
3+
async function post(id: string, verb: "review" | "reject" | "archive") { await fetch(`/api/pandora/shadow-context-packs/${id}/${verb}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}) }); window.location.reload(); }
4+
export function ShadowContextPackControls({ pack }: { pack: ShadowContextPackSummary }) { return <div className="pd-action-controls"><button className="button-link button-link--primary" type="button" onClick={() => post(pack.id, "review")}>Mark reviewed</button><button className="button-link" type="button" onClick={() => post(pack.id, "reject")}>Reject</button><button className="button-link" type="button" onClick={() => post(pack.id, "archive")}>Archive</button></div>; }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { ShadowContextPackSummary } from "./types";
2+
import { ShadowContextPackEvidence } from "./ShadowContextPackEvidence";
3+
import { ShadowContextPackControls } from "./ShadowContextPackControls";
4+
export function ShadowContextPackDetail({ pack }: { pack: ShadowContextPackSummary }) { const payload = pack.candidate_payload; return <article className="pd-action-card"><div className="pd-action-head"><div><h4>{pack.title}</h4><p>{pack.summary}</p></div><span className="pd-pill pd-pill-amber">{pack.status}</span></div><div className="pd-mini-grid"><div className="pd-mini"><strong>{pack.namespace}</strong><span>namespace</span></div><div className="pd-mini"><strong>{String(payload.source_event_count ?? 0)}</strong><span>source events</span></div><div className="pd-mini"><strong>{String(payload.active_master_pack_id ?? "none")}</strong><span>active master pack</span></div><div className="pd-mini"><strong>{String(payload.latest_event_at ?? "none")}</strong><span>latest event</span></div></div><ShadowContextPackEvidence pack={pack} /><details><summary>Candidate payload</summary><pre>{JSON.stringify(payload, null, 2)}</pre></details><ShadowContextPackControls pack={pack} /></article>; }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import type { ShadowContextPackSummary } from "./types";
2+
export function ShadowContextPackEvidence({ pack }: { pack: ShadowContextPackSummary }) { return <div className="pd-warning-list"><p><strong>Evidence summary:</strong> {String(pack.candidate_payload.evidence_summary ?? pack.summary)}</p>{pack.warnings.map((w) => <p key={w}>{w}</p>)}</div>; }

0 commit comments

Comments
 (0)