-
Notifications
You must be signed in to change notification settings - Fork 1
Add Pandora shadow pack preflight v1 (deterministic diff, risk, review, preflight tables) #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextResponse, type NextRequest } from "next/server"; | ||
| import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver"; | ||
| import { createSupabaseServerClient } from "@/lib/supabase/server"; | ||
| import { createOrRefreshShadowPackPreflight, type ShadowPackPreflightDbClient } from "@/lib/services/pandora-shadow-pack-preflight-service"; | ||
| export const dynamic = "force-dynamic"; | ||
| 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 preflight = await createOrRefreshShadowPackPreflight(supabase as unknown as ShadowPackPreflightDbClient, { userId: session.session.userId, shadowPackId: id, requestId: typeof body.request_id === "string" ? body.request_id : undefined }); return NextResponse.json({ ok:true, preflight, no_core_memory_mutation_performed:true, no_promotion_performed:true }); } catch(e) { return NextResponse.json({ ok:false, error: e instanceof Error ? e.message : "Preflight failed" }, { status: 400 }); } } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextResponse, type NextRequest } from "next/server"; | ||
| import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver"; | ||
| import { createSupabaseServerClient } from "@/lib/supabase/server"; | ||
| import { archiveShadowPackPreflight, type ShadowPackPreflightDbClient } from "@/lib/services/pandora-shadow-pack-preflight-service"; | ||
| export const dynamic = "force-dynamic"; | ||
| 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 preflight = await archiveShadowPackPreflight(supabase as unknown as ShadowPackPreflightDbClient, { userId: session.session.userId, preflightId: id, reason: typeof body.reason === "string" ? body.reason : undefined }); return NextResponse.json({ ok:true, preflight, no_core_memory_mutation_performed:true, no_promotion_performed:true }); } catch(e) { return NextResponse.json({ ok:false, error: e instanceof Error ? e.message : "Archive failed" }, { status: 400 }); } } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextResponse, type NextRequest } from "next/server"; | ||
| import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver"; | ||
| import { createSupabaseServerClient } from "@/lib/supabase/server"; | ||
| import { updateShadowPackPreflightReview, type ShadowPackPreflightDbClient } from "@/lib/services/pandora-shadow-pack-preflight-service"; | ||
| export const dynamic = "force-dynamic"; | ||
| 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 input = body && typeof body === "object" ? body as Record<string, unknown> : {}; const supabase = await createSupabaseServerClient(); const preflight = await updateShadowPackPreflightReview(supabase as unknown as ShadowPackPreflightDbClient, { userId: session.session.userId, preflightId: id, reviewerNotes: String(input.reviewer_notes ?? ""), reviewerDecision: String(input.reviewer_decision ?? "") }); return NextResponse.json({ ok:true, preflight, no_core_memory_mutation_performed:true, no_promotion_performed:true }); } catch(e) { return NextResponse.json({ ok:false, error: e instanceof Error ? e.message : "Review failed" }, { status: 400 }); } } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextResponse, type NextRequest } from "next/server"; | ||
| import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver"; | ||
| import { createSupabaseServerClient } from "@/lib/supabase/server"; | ||
| import { getShadowPackPreflight, type ShadowPackPreflightDbClient } from "@/lib/services/pandora-shadow-pack-preflight-service"; | ||
| export const dynamic = "force-dynamic"; | ||
| 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 preflight = await getShadowPackPreflight(supabase as unknown as ShadowPackPreflightDbClient, { userId: session.session.userId, preflightId: id }); return NextResponse.json({ ok:true, preflight }); } catch(e) { return NextResponse.json({ ok:false, error: e instanceof Error ? e.message : "Not found" }, { status: 404 }); } } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { NextResponse, type NextRequest } from "next/server"; | ||
| import { assertNoClientUserIdOverride, resolvePandoraServerSession } from "@/lib/auth/pandora-server-session-resolver"; | ||
| import { createSupabaseServerClient } from "@/lib/supabase/server"; | ||
| import { listShadowPackPreflights, type ShadowPackPreflightDbClient } from "@/lib/services/pandora-shadow-pack-preflight-service"; | ||
| export const dynamic = "force-dynamic"; | ||
| 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 preflights = await listShadowPackPreflights(supabase as unknown as ShadowPackPreflightDbClient, { userId: session.session.userId, namespace: url.searchParams.get("namespace") ?? undefined, status: url.searchParams.get("status") ?? undefined, limit: 25 }); return NextResponse.json({ ok:true, preflights }); } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,5 +5,5 @@ export function OperatorActionComposer() { | |||||||||||||||||||||
| const [actionType, setActionType] = useState("verify_namespace_invariants"); | ||||||||||||||||||||||
| const [namespace, setNamespace] = useState("real_life"); | ||||||||||||||||||||||
| 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(); } | ||||||||||||||||||||||
| 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>; | ||||||||||||||||||||||
| 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><option value="prepare_shadow_pack_preflight">prepare_shadow_pack_preflight</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. For prepare_shadow_pack_preflight, payload.shadow_pack_id is required by the API/service; use a direct action payload only when the target shadow pack is known. No promotion. Only dry-run or queued-only proposals are available. No core memory mutation is available from this card.</p></div>; | ||||||||||||||||||||||
|
Comment on lines
7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win New The service throws if But Add a conditional input for 🛠️ Proposed fix export function OperatorActionComposer() {
const [actionType, setActionType] = useState("verify_namespace_invariants");
const [namespace, setNamespace] = useState("real_life");
- 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(); }
- 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)}>...</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">...</p></div>;
+ const [shadowPackId, setShadowPackId] = useState("");
+ async function prepare() {
+ const payload = actionType === "prepare_shadow_pack_preflight" ? { shadow_pack_id: shadowPackId } : { source: "operator_action_center" };
+ await fetch("/api/pandora/operator-actions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action_type: actionType, namespace, mode: "dry_run", payload }) });
+ window.location.reload();
+ }
+ 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)}>...</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>{actionType === "prepare_shadow_pack_preflight" && <label className="pd-mini"><span>Shadow pack id</span><input value={shadowPackId} onChange={(e) => setShadowPackId(e.target.value)} /></label>}</div><button className="button-link button-link--primary" type="button" onClick={prepare}>Prepare dry-run</button><p className="pd-muted">...</p></div>;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| "use client"; | ||
| import type { ShadowContextPackSummary } from "./types"; | ||
| 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(); } | ||
| 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>; } | ||
| async function post(id: string, verb: "review" | "reject" | "archive" | "preflight") { await fetch(`/api/pandora/shadow-context-packs/${id}/${verb}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}) }); window.location.reload(); } | ||
| 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, "preflight")}>Create/refresh preflight</button><button className="button-link" 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>; } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import type { ShadowPackDiffSummary } from "./types"; | ||
| function Keys({ label, keys }: { label: string; keys: string[] }) { return <div className="pd-mini"><strong>{keys.length}</strong><span>{label}: {keys.length ? keys.join(", ") : "none"}</span></div>; } | ||
| export function ShadowPackDiffViewer({ diff }: { diff: ShadowPackDiffSummary }) { return <div><p className="pd-muted">Active master: {diff.active_master_pack_id ?? "none"} • Shadow: {diff.shadow_pack_id}</p><div className="pd-mini-grid"><Keys label="added" keys={diff.added_keys} /><Keys label="removed" keys={diff.removed_keys} /><Keys label="changed" keys={diff.changed_keys} /><Keys label="unchanged" keys={diff.unchanged_keys} /></div><p className="pd-muted">Deltas — source events: {diff.source_event_count_delta}, open loops: {diff.open_loop_count_delta}, needs review: {diff.needs_review_count_delta}, recent summaries: {diff.top_recent_summary_count}, evidence changed: {diff.evidence_summary_changed ? "yes" : "no"}</p></div>; } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import type { ShadowPackPreflightData } from "./types"; | ||
| import { ShadowPackPreflightList } from "./ShadowPackPreflightList"; | ||
| const empty: ShadowPackPreflightData = { preflights: [], warnings: [], countsByStatus: { draft:0, ready_for_review:0, reviewed:0, approved_for_promotion:0, blocked:0, archived:0 }, riskDistribution: { low:0, medium:0, high:0, blocked:0 } }; | ||
| export function ShadowPackPreflightCard({ data }: { data?: ShadowPackPreflightData }) { const d=data??empty; return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Shadow Pack Preflight</p><h3>Diff + promotion readiness review</h3><p>Preflight only — no production context pack mutation</p></div><span className="pd-pill pd-pill-slate">No promotion route</span></div><div className="pd-mini-grid">{Object.entries(d.countsByStatus).map(([s,c])=><div className="pd-mini" key={s}><strong>{c}</strong><span>{s}</span></div>)}</div><div className="pd-mini-grid">{Object.entries(d.riskDistribution).map(([s,c])=><div className="pd-mini" key={s}><strong>{c}</strong><span>{s} risk</span></div>)}</div><ShadowPackPreflightList preflights={d.preflights}/>{d.warnings.length ? <div className="pd-warning-list">{d.warnings.map((w)=><p key={w}>⚠ {w}</p>)}</div> : null}</section>; } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,4 @@ | ||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||
| import type { ShadowPackPreflightSummary } from "./types"; | ||||||||||||||||||||||
| async function archive(id: string) { await fetch(`/api/pandora/shadow-pack-preflights/${id}/archive`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}) }); window.location.reload(); } | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Same silent-failure pattern as the review form.
🛠️ Proposed fix-async function archive(id: string) { await fetch(`/api/pandora/shadow-pack-preflights/${id}/archive`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}) }); window.location.reload(); }
+async function archive(id: string) {
+ const res = await fetch(`/api/pandora/shadow-pack-preflights/${id}/archive`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}) });
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ alert(body.error ?? "Failed to archive preflight.");
+ return;
+ }
+ window.location.reload();
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| export function ShadowPackPreflightControls({ preflight }: { preflight: ShadowPackPreflightSummary }) { return <div className="pd-action-controls"><button className="button-link" type="button" onClick={() => archive(preflight.id)}>Archive</button></div>; } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import type { ShadowPackPreflightSummary } from "./types"; | ||
| import { ShadowPackDiffViewer } from "./ShadowPackDiffViewer"; | ||
| import { ShadowPackRiskSummary } from "./ShadowPackRiskSummary"; | ||
| import { ShadowPackPreflightReviewForm } from "./ShadowPackPreflightReviewForm"; | ||
| import { ShadowPackPreflightControls } from "./ShadowPackPreflightControls"; | ||
| export function ShadowPackPreflightList({ preflights }: { preflights: ShadowPackPreflightSummary[] }) { if (!preflights.length) return <p className="pd-muted">No shadow pack promotion preflights yet.</p>; return <div className="pd-action-list">{preflights.map((p)=><article className="pd-action-item" key={p.id}><div><p className="pd-label">{p.namespace} • {p.status}</p><h4>Shadow pack {p.shadow_pack_id}</h4><p>Reviewer decision: {p.reviewer_decision ?? "not reviewed"}</p></div><ShadowPackRiskSummary risk={p.risk_summary}/><ShadowPackDiffViewer diff={p.diff_summary}/><p className="pd-muted">Reviewer notes: {p.reviewer_notes || "none"}</p><ShadowPackPreflightReviewForm preflight={p}/><ShadowPackPreflightControls preflight={p}/></article>)}</div>; } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||
| import type { ShadowPackPreflightSummary } from "./types"; | ||||||||||||||||||||||
| async function save(id: string, reviewer_notes: string, reviewer_decision: string) { await fetch(`/api/pandora/shadow-pack-preflights/${id}/review`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ reviewer_notes, reviewer_decision }) }); window.location.reload(); } | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win No error handling around the review submission.
🛠️ Proposed fix-async function save(id: string, reviewer_notes: string, reviewer_decision: string) { await fetch(`/api/pandora/shadow-pack-preflights/${id}/review`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ reviewer_notes, reviewer_decision }) }); window.location.reload(); }
+async function save(id: string, reviewer_notes: string, reviewer_decision: string) {
+ const res = await fetch(`/api/pandora/shadow-pack-preflights/${id}/review`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ reviewer_notes, reviewer_decision }) });
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ alert(body.error ?? "Failed to save reviewer decision.");
+ return;
+ }
+ window.location.reload();
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| export function ShadowPackPreflightReviewForm({ preflight }: { preflight: ShadowPackPreflightSummary }) { const [notes, setNotes] = useState(preflight.reviewer_notes ?? ""); return <div className="pd-action-controls"><textarea aria-label="Reviewer notes" value={notes} onChange={(e)=>setNotes(e.target.value)} placeholder="Reviewer notes" /><button className="button-link" onClick={()=>save(preflight.id, notes, "needs_changes")}>Mark needs changes</button><button className="button-link button-link--primary" onClick={()=>save(preflight.id, notes, "approved_for_promotion")}>Approve for future promotion (NOT promotion)</button><button className="button-link" onClick={()=>save(preflight.id, notes, "blocked")}>Block</button></div>; } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import type { ShadowPackRiskSummary as Risk } from "./types"; | ||
| const tone = { low: "pd-pill-emerald", medium: "pd-pill-amber", high: "pd-pill-red", blocked: "pd-pill-red" } as const; | ||
| export function ShadowPackRiskSummary({ risk }: { risk: Risk }) { return <div><span className={`pd-pill ${tone[risk.status]}`}>{risk.status} risk • {risk.score}/100</span>{risk.blockers.length ? <div className="pd-warning-list">{risk.blockers.map((b) => <p key={b}>⛔ {b}</p>)}</div> : null}{risk.warnings.length ? <div className="pd-warning-list">{risk.warnings.map((w) => <p key={w}>⚠ {w}</p>)}</div> : null}<ul>{risk.reasons.map((r) => <li key={r}>{r}</li>)}</ul></div>; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Invalid
namespace/statusquery params return 500 instead of 400.listShadowPackPreflightsrunsassertNamespace/assertStatus, which throw on unrecognized values sourced directly fromurl.searchParams. With notry/catchhere, a request like?namespace=foosurfaces as an unhandled 500 rather than a client-error 400.🛡️ Suggested handling
📝 Committable suggestion
🤖 Prompt for AI Agents