feat(documents): module gate, nav, and review-then-confirm UI

Add the opt-in inboundDocuments module (off by default, like the MCP
endpoint): ingesting a clinical document egresses it to the OCR/vision
provider, so the surface ships dark and the user turns it on from the
Modules hub. The born-gated /documents page carries the prominent
non-diagnostic disclaimer (we read, we don't interpret; nothing is saved
until you approve it; only your clinician can interpret these results),
uploads a document, and walks each extracted fact: source provenance and
confidence are shown, low-confidence facts are blocked from approval until
edited, and the confirm step commits the approved facts.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 16:57:16 +02:00
1 parent 98ca0b9d61
commit 422778f679
10 files changed
+707 -4

No files matched your search

+38 -4
View File
@@ -352,15 +352,49 @@ describe("resolveModuleEnabled — mcp module (opt-in, default-OFF)", () => {
});
describe("registry — opt-in marker", () => {
it("marks only the mcp module opt-in (every other module is default-on)", () => {
expect(isOptInModule("mcp")).toBe(true);
// The opt-in modules ship dark because they open an egress/attack surface:
// `mcp` (remote external-assistant endpoint) and `inboundDocuments`
// (sends an uploaded clinical document to the OCR/vision provider). Every
// other module is the default-on disabled-allowlist.
const OPT_IN_KEYS = new Set(["mcp", "inboundDocuments"]);
it("marks only the egress-surface modules opt-in (every other is default-on)", () => {
for (const key of MODULE_KEYS) {
if (key === "mcp") continue;
expect(isOptInModule(key)).toBe(false);
expect(isOptInModule(key)).toBe(OPT_IN_KEYS.has(key));
}
});
});
describe("resolveModuleEnabled — inboundDocuments module (opt-in, default-OFF)", () => {
it("is OFF when no preference is recorded", () => {
expect(
resolveModuleEnabled("inboundDocuments", inputs(), false, ALL_AVAILABLE),
).toBe(false);
});
it("turns ON only on an explicit true (opt-in)", () => {
expect(
resolveModuleEnabled(
"inboundDocuments",
inputs({ modulePreferences: { inboundDocuments: true } }),
false,
ALL_AVAILABLE,
),
).toBe(true);
});
it("operator-off short-circuits even when the user opted in", () => {
expect(
resolveModuleEnabled(
"inboundDocuments",
inputs({ modulePreferences: { inboundDocuments: true } }),
false,
operator({ inboundDocuments: false }),
),
).toBe(false);
});
});
describe("resolveOperatorAvailability — disabled allowlist (default-available)", () => {
it("treats null / non-object / array as all-available", () => {
for (const raw of [null, undefined, "nope", ["mood"]]) {
+16
View File
@@ -123,6 +123,13 @@ export const MODULE_KEYS = [
// (operator-availability layer, PATCH plumbing, the Modules hub toggle)
// reuses the existing module machinery unchanged.
"mcp",
// v1.25.0 (W-DOCS-IN) — inbound clinical documents (`/documents/inbound`).
// Like `mcp` this is OPT-IN (off by default): ingesting a doctor report /
// discharge letter sends the document to the configured OCR/vision provider,
// so the surface ships dark and the user turns it on deliberately. The
// `optIn` marker on its registry entry inverts the per-user default;
// everything else reuses the existing module machinery unchanged.
"inboundDocuments",
] as const;
export type ModuleKey = (typeof MODULE_KEYS)[number];
@@ -265,6 +272,15 @@ export const MODULE_REGISTRY: Readonly<Record<ModuleKey, ModuleDefinition>> =
// must be turned on deliberately (ADR-007 / REQ-OPS-1).
optIn: true,
},
inboundDocuments: {
key: "inboundDocuments",
labelKey: "modules.inboundDocuments.label",
descriptionKey: "modules.inboundDocuments.description",
category: "tracking",
// Off by default: ingesting a clinical document egresses it to the
// configured OCR/vision provider, so the user turns it on deliberately.
optIn: true,
},
});
const MODULE_KEY_SET: ReadonlySet<string> = new Set(MODULE_KEYS);