fix: preserve workspace evidence in browser drafts
This commit is contained in:
@@ -20,6 +20,19 @@ const workspace: CanonicalWorkspace = {
|
||||
llm_policy: { default: "zai/glm-5.2", allowed: ["zai/glm-5.2"] },
|
||||
};
|
||||
|
||||
const evidenceWorkspace: CanonicalWorkspace = {
|
||||
...workspace,
|
||||
evidence: {
|
||||
source: {
|
||||
type: "filesystem",
|
||||
uri: "workspace-content/psd-clinical/evidence",
|
||||
patterns: ["documents/**/*.pdf"],
|
||||
max_bytes: 12_000_000,
|
||||
},
|
||||
policy: { max_chunk_chars: 8_000, retain_published_generations: 5 },
|
||||
},
|
||||
};
|
||||
|
||||
const draft: WorkspaceDraft = {
|
||||
workspaceId: "psd-clinical",
|
||||
baseCommit: "a".repeat(40),
|
||||
@@ -99,3 +112,50 @@ test("rejects a non-positive DWH timeout without saving a draft", async () => {
|
||||
expect(screen.getByLabelText("DWH timeout (ms)")).toHaveAttribute("aria-invalid", "true");
|
||||
expect(onSaveDraft).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
test("shows a safe read-only Evidence summary without authoring or secret binding controls", () => {
|
||||
render(<WorkspaceEditor draft={{ ...draft, workspace: evidenceWorkspace }} onSaveDraft={vi.fn()} onPublish={vi.fn()} />);
|
||||
|
||||
const summary = screen.getByRole("region", { name: "Evidence" });
|
||||
expect(summary).toHaveTextContent("filesystem");
|
||||
expect(summary).toHaveTextContent("workspace-content/psd-clinical/evidence");
|
||||
expect(summary).toHaveTextContent("8,000");
|
||||
expect(summary).toHaveTextContent("5");
|
||||
expect(summary).toHaveTextContent("Evidence is managed by the registry descriptor in P1.");
|
||||
expect(summary).not.toHaveTextContent(/signed_urls_file|static_files|secret|binding/i);
|
||||
expect(screen.queryByLabelText(/evidence.*(source|uri|pattern|credential)/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("publishes an edited DWH and LLM field without dropping or mutating Evidence", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onPublish = vi.fn().mockResolvedValue(undefined);
|
||||
render(<WorkspaceEditor draft={{ ...draft, workspace: evidenceWorkspace }} onSaveDraft={vi.fn()} onPublish={onPublish} />);
|
||||
|
||||
await user.clear(screen.getByLabelText("DWH database"));
|
||||
await user.type(screen.getByLabelText("DWH database"), "research");
|
||||
await user.clear(screen.getByLabelText("Allowed models"));
|
||||
await user.type(screen.getByLabelText("Allowed models"), "openai/gpt-5");
|
||||
await user.click(screen.getByRole("button", { name: "Publish draft" }));
|
||||
|
||||
expect(onPublish).toHaveBeenCalledWith(expect.objectContaining({
|
||||
action: "update",
|
||||
workspace: expect.objectContaining({
|
||||
dwh: expect.objectContaining({ database: "research" }),
|
||||
llm_policy: { allowed: ["openai/gpt-5"] },
|
||||
evidence: evidenceWorkspace.evidence,
|
||||
}),
|
||||
}));
|
||||
expect(evidenceWorkspace.evidence?.source).toEqual({
|
||||
type: "filesystem",
|
||||
uri: "workspace-content/psd-clinical/evidence",
|
||||
patterns: ["documents/**/*.pdf"],
|
||||
max_bytes: 12_000_000,
|
||||
});
|
||||
});
|
||||
|
||||
test("does not show an Evidence summary for a workspace without Evidence", () => {
|
||||
render(<WorkspaceEditor draft={draft} onSaveDraft={vi.fn()} onPublish={vi.fn()} />);
|
||||
|
||||
expect(screen.queryByRole("region", { name: "Evidence" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -108,6 +108,23 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
||||
);
|
||||
}
|
||||
|
||||
function EvidenceSummary({ evidence }: { evidence: NonNullable<CanonicalWorkspace["evidence"]> }) {
|
||||
const sourceIdentity = evidence.source.type === "http"
|
||||
? `${evidence.source.uris.length} canonical URI${evidence.source.uris.length === 1 ? "" : "s"}`
|
||||
: evidence.source.uri;
|
||||
return (
|
||||
<Section title="Evidence">
|
||||
<dl className="grid gap-2 text-sm sm:col-span-2 sm:grid-cols-2">
|
||||
<div><dt className="font-medium text-foreground">Source type</dt><dd className="text-muted-foreground">{evidence.source.type}</dd></div>
|
||||
<div><dt className="font-medium text-foreground">Source</dt><dd className="break-all text-muted-foreground">{sourceIdentity}</dd></div>
|
||||
<div><dt className="font-medium text-foreground">Chunk size</dt><dd className="text-muted-foreground">{evidence.policy.max_chunk_chars.toLocaleString("en-US")} characters</dd></div>
|
||||
<div><dt className="font-medium text-foreground">Retention</dt><dd className="text-muted-foreground">{evidence.policy.retain_published_generations.toLocaleString("en-US")} published generations</dd></div>
|
||||
</dl>
|
||||
<p className="sm:col-span-2 text-sm text-muted-foreground">Evidence is managed by the registry descriptor in P1.</p>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
const fieldClass = "h-9 w-full rounded-md border border-input bg-background px-2.5 text-sm shadow-xs outline-none focus-visible:ring-3 focus-visible:ring-ring/25 aria-invalid:border-destructive";
|
||||
|
||||
export function WorkspaceEditor({ draft, onSaveDraft, onPublish, idLocked = Boolean(draft?.baseBlob) }: WorkspaceEditorProps) {
|
||||
@@ -220,6 +237,8 @@ export function WorkspaceEditor({ draft, onSaveDraft, onPublish, idLocked = Bool
|
||||
</Field>
|
||||
</Section>
|
||||
|
||||
{workspace.evidence && <EvidenceSummary evidence={workspace.evidence} />}
|
||||
|
||||
<Section title="Installation requirements">
|
||||
<p className="sm:col-span-2 text-sm text-muted-foreground">Hosts, users, secret-file paths, and credentials are installation bindings. They are intentionally not editable or stored in workspace drafts.</p>
|
||||
</Section>
|
||||
|
||||
Reference in New Issue
Block a user