fix(modules): switch the inbound-documents module off in code

The inbound-documents surface is withdrawn from this release and returns
later with more depth. Park its module key (`inboundDocuments`) in a new
`CODE_DISABLED_MODULE_KEYS` list so it is hard-off for every account ahead
of both the operator-availability and the per-user opt-in layer.

The gate short-circuits a code-disabled key to disabled, so the resolved
module map reports it off everywhere: the nav entry stays born-gated and
hidden, the `/documents` page redirects, and every `/api/documents/inbound/*`
route returns the 403 module-disabled envelope. The Modules hub and the
operator-availability panel drop the row, and the per-user PATCH surface
refuses to write the key (a clean 422 instead of an inert preference).

The whole implementation stays intact — routes, the inbound-documents view,
the registry entry with its opt-in marker, the validation schemas, and the
migration are all preserved. Re-enabling the module is a one-line removal
from the list once the rebuild lands.

Regenerate the OpenAPI contract: the module-preferences PATCH body no
longer advertises the parked key.
This commit is contained in:
Marc-André Bombeck committed 2026-06-29 14:58:30 +02:00
1 parent 4266203d3a
commit 7e83af5701
8 files changed
+152 -73

No files matched your search

@@ -7,7 +7,11 @@ import { toast } from "sonner";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import { MODULE_KEYS, MODULE_REGISTRY } from "@/lib/modules/registry";
import {
MODULE_KEYS,
MODULE_REGISTRY,
isCodeDisabledModule,
} from "@/lib/modules/registry";
import type { ModuleKey } from "@/lib/modules/registry";
import { SettingsToggle } from "./_shared";
import { apiGet, apiPatch } from "@/lib/api/api-fetch";
@@ -90,24 +94,30 @@ export function ModuleAvailabilitySection() {
description={t("admin.modules.description")}
/>
<div className="mt-4 space-y-4 pl-7">
{MODULE_KEYS.map((key) => {
const def = MODULE_REGISTRY[key];
// Default-available until the read resolves; an explicit `false`
// from the operator layer is the only thing that turns it off.
const available = availability?.[key] ?? true;
return (
<SettingsToggle
key={key}
label={t(def.labelKey)}
description={t(def.descriptionKey)}
checked={available}
onCheckedChange={(checked) =>
mutation.mutate({ [key]: checked } as Partial<AvailabilityMap>)
}
disabled={mutation.isPending}
/>
);
})}
{MODULE_KEYS
// A module switched off in code (pending a rebuild) is hard-off
// server-wide; drop its row so the operator toggle can't mislead.
.filter((key) => !isCodeDisabledModule(key))
.map((key) => {
const def = MODULE_REGISTRY[key];
// Default-available until the read resolves; an explicit `false`
// from the operator layer is the only thing that turns it off.
const available = availability?.[key] ?? true;
return (
<SettingsToggle
key={key}
label={t(def.labelKey)}
description={t(def.descriptionKey)}
checked={available}
onCheckedChange={(checked) =>
mutation.mutate({
[key]: checked,
} as Partial<AvailabilityMap>)
}
disabled={mutation.isPending}
/>
);
})}
</div>
</div>
);