feat: complete evidence restructuring worktree
This commit is contained in:
@@ -50,6 +50,37 @@ test("accepts the immutable workspace revision contract", async () => {
|
||||
await expect(getWorkspace("psd-clinical")).resolves.toEqual({ workspace, revision });
|
||||
});
|
||||
|
||||
test("accepts the evidence schema version materialized by the backend", async () => {
|
||||
const persistedWorkspace = {
|
||||
...workspace,
|
||||
evidence: {
|
||||
schema_version: 1,
|
||||
source: {
|
||||
type: "filesystem",
|
||||
uri: "psd-clinical/evidence",
|
||||
patterns: ["**/*.md"],
|
||||
max_bytes: 10 * 1024 * 1024,
|
||||
},
|
||||
policy: { max_chunk_chars: 5000, retain_published_generations: 3 },
|
||||
},
|
||||
};
|
||||
server.use(http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
|
||||
workspace: persistedWorkspace,
|
||||
revision,
|
||||
})));
|
||||
|
||||
await expect(getWorkspace("psd-clinical")).resolves.toEqual({
|
||||
workspace: {
|
||||
...persistedWorkspace,
|
||||
evidence: {
|
||||
source: persistedWorkspace.evidence.source,
|
||||
policy: persistedWorkspace.evidence.policy,
|
||||
},
|
||||
},
|
||||
revision,
|
||||
});
|
||||
});
|
||||
|
||||
test("decodes runtime requirements but rejects any secret value returned by the server", async () => {
|
||||
server.use(http.get(
|
||||
"/api/workspaces/psd-clinical/runtime-configuration",
|
||||
|
||||
@@ -159,6 +159,7 @@ test("opens Workspace management from the right sidebar without interrupting the
|
||||
server.use(
|
||||
http.get("/api/workspace-registry/status", () =>
|
||||
HttpResponse.json({ branch: "main", ahead: 0, behind: 0, degraded: false })),
|
||||
http.get("/api/health/dwh", () => HttpResponse.json({ ok: false })),
|
||||
);
|
||||
renderShell();
|
||||
|
||||
@@ -168,6 +169,21 @@ test("opens Workspace management from the right sidebar without interrupting the
|
||||
expect(screen.getByTestId("app-shell")).toHaveAttribute("data-activity-layout", "closed");
|
||||
});
|
||||
|
||||
test("does not block the shell when the DWH is unavailable at startup", async () => {
|
||||
let healthChecks = 0;
|
||||
server.use(
|
||||
http.get("/api/health/dwh", () => {
|
||||
healthChecks += 1;
|
||||
return HttpResponse.json({ ok: false });
|
||||
}),
|
||||
);
|
||||
renderShell();
|
||||
|
||||
expect(await screen.findByRole("button", { name: "Workspace management" })).toBeVisible();
|
||||
expect(screen.queryByRole("heading", { name: "Connection unavailable" })).not.toBeInTheDocument();
|
||||
expect(healthChecks).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
test("marks the composer as awaiting input for a pending freetext gate", () => {
|
||||
useSessionStore.setState({
|
||||
|
||||
@@ -23,7 +23,6 @@ import { toast } from "sonner";
|
||||
import {
|
||||
closeSession, listSessions, resumeSession, getSession,
|
||||
renameSession, setSessionGroup, archiveSession, unarchiveSession, deleteSession, prewarmRuntime,
|
||||
checkDwhHealth,
|
||||
} from "../api/sessions";
|
||||
import { logout as logoutUser } from "../api/auth";
|
||||
import {
|
||||
@@ -129,23 +128,8 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
const [stopConfirm, setStopConfirm] = useState(false);
|
||||
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
||||
const [renameGroupTarget, setRenameGroupTarget] = useState<string | null>(null);
|
||||
const [dwhDown, setDwhDown] = useState(false);
|
||||
const [dwhChecking, setDwhChecking] = useState(true);
|
||||
const [dwhCheckEpoch, setDwhCheckEpoch] = useState(0);
|
||||
const operationEpochRef = useRef(0);
|
||||
useEffect(() => () => { operationEpochRef.current += 1; }, []);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const operation = captureAuthOperation({ disposalEpoch: operationEpochRef.current });
|
||||
if (!operation) return () => { cancelled = true; };
|
||||
setDwhChecking(true);
|
||||
checkDwhHealth().then((r) => {
|
||||
if (cancelled || !isAuthOperationCurrent(operation, { disposalEpoch: operationEpochRef.current })) return;
|
||||
setDwhDown(!r.ok);
|
||||
setDwhChecking(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [dwhCheckEpoch]);
|
||||
|
||||
const groups = useMemo(
|
||||
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
|
||||
@@ -958,26 +942,6 @@ export function AppShell({ canLogout }: AppShellProps) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dwhDown && (
|
||||
<Dialog open onOpenChange={() => {}}>
|
||||
<DialogContent showCloseButton={false} className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Connection unavailable</DialogTitle>
|
||||
<DialogDescription>
|
||||
The database is unreachable. Check the VPN connection and try again.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
disabled={dwhChecking}
|
||||
onClick={() => setDwhCheckEpoch((e) => e + 1)}
|
||||
>
|
||||
{dwhChecking ? "Checking…" : "Retry"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ function PiInstructionSteps({ details }: { details: PiPlatformDetails }) {
|
||||
</li>
|
||||
<li>
|
||||
<h4 className="font-semibold text-foreground">Edit the provider catalog</h4>
|
||||
<p className="mt-1 text-muted-foreground">Edit <code>{details.modelsPath}</code> only when adding or correcting a provider/model definition. The provider catalog is an address book/map of the services Pi can call: each entry supplies the API endpoint and format, and lists the model identifiers offered there. It does not enable a model and never contains credentials.</p>
|
||||
<p className="mt-1 text-muted-foreground">Edit <code>{details.modelsPath}</code> only when adding or correcting a provider/model definition. The provider catalog is an address book/map of the services Pi can call: each entry supplies the API endpoint and format, and lists the model identifiers offered there. It does not enable a model and never contains credentials. Provider integrations must remain declarative; do not add model-provider code under <code>harness/.pi/extensions/</code>.</p>
|
||||
<dl className="mt-2 grid grid-cols-[auto_1fr] gap-x-2 gap-y-1 text-muted-foreground">
|
||||
<dt className="font-mono text-foreground">baseUrl </dt><dd>is the provider API endpoint.</dd>
|
||||
<dt className="font-mono text-foreground">api </dt><dd>selects the provider API format.</dd>
|
||||
|
||||
@@ -313,7 +313,11 @@ function copyS3Evidence(value: unknown): EvidenceSource | undefined {
|
||||
}
|
||||
|
||||
function copyEvidence(value: unknown, id: string): WorkspaceEvidence | undefined {
|
||||
const source = exactRecord(value, ["source", "policy"]);
|
||||
// The backend's canonical YAML parser materializes the evidence schema version
|
||||
// in responses, even though the browser draft does not need to retain it.
|
||||
const source = exactRecord(value, ["schema_version", "source", "policy"]);
|
||||
const schemaVersion = source?.schema_version;
|
||||
if (!source) return undefined;
|
||||
if (!source) return undefined;
|
||||
const type = record(source.source)?.type;
|
||||
const evidenceSource = type === "filesystem"
|
||||
@@ -324,7 +328,9 @@ function copyEvidence(value: unknown, id: string): WorkspaceEvidence | undefined
|
||||
? copyS3Evidence(source.source)
|
||||
: undefined;
|
||||
const policy = copyEvidencePolicy(source.policy);
|
||||
return evidenceSource && policy ? { source: evidenceSource, policy } : undefined;
|
||||
return (schemaVersion === 1 || schemaVersion === 2) && evidenceSource && policy
|
||||
? { source: evidenceSource, policy }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function oneOf<T extends string>(value: unknown, choices: readonly T[]): T | undefined {
|
||||
|
||||
Reference in New Issue
Block a user