The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
170 lines
4.6 KiB
TypeScript
170 lines
4.6 KiB
TypeScript
const APP_NAMESPACE = "ca-frontend";
|
|
|
|
export type StorageBackend =
|
|
| "memory"
|
|
| "sessionStorage"
|
|
| "localStorage"
|
|
| "disabled"
|
|
| "forbidden";
|
|
|
|
export type StorageValueCodec =
|
|
| "color-scheme-v1"
|
|
| "opaque-string-v1"
|
|
| "none";
|
|
|
|
export type StorageKeyInput = Readonly<{
|
|
logicalName: string;
|
|
scope: string;
|
|
name: string;
|
|
backend: StorageBackend;
|
|
classification:
|
|
| "public-preference"
|
|
| "opaque-cache"
|
|
| "sensitive-forbidden";
|
|
schemaVersion: number;
|
|
valueCodec: StorageValueCodec;
|
|
ttl: number | "session" | null;
|
|
migration: "discard";
|
|
quotaFallback: "memory" | "no-persist" | "feature-disable";
|
|
}>;
|
|
|
|
export type StorageDefinition = Readonly<
|
|
StorageKeyInput & { physicalKey: string }
|
|
>;
|
|
|
|
export const STORAGE_REGISTRY = Object.freeze({
|
|
COLOR_SCHEME: defineStorageKey({
|
|
logicalName: "COLOR_SCHEME",
|
|
scope: "preference",
|
|
name: "color-scheme",
|
|
backend: "localStorage",
|
|
classification: "public-preference",
|
|
schemaVersion: 1,
|
|
valueCodec: "color-scheme-v1",
|
|
ttl: null,
|
|
migration: "discard",
|
|
quotaFallback: "memory",
|
|
}),
|
|
CHUNK_RELOAD_GUARD: defineStorageKey({
|
|
logicalName: "CHUNK_RELOAD_GUARD",
|
|
scope: "release",
|
|
name: "chunk-reload-guard",
|
|
backend: "sessionStorage",
|
|
classification: "opaque-cache",
|
|
schemaVersion: 1,
|
|
valueCodec: "opaque-string-v1",
|
|
ttl: "session",
|
|
migration: "discard",
|
|
quotaFallback: "no-persist",
|
|
}),
|
|
QUERY_PERSISTENCE: defineStorageKey({
|
|
logicalName: "QUERY_PERSISTENCE",
|
|
scope: "cache",
|
|
name: "query-persistence",
|
|
backend: "disabled",
|
|
classification: "sensitive-forbidden",
|
|
schemaVersion: 1,
|
|
valueCodec: "none",
|
|
ttl: null,
|
|
migration: "discard",
|
|
quotaFallback: "feature-disable",
|
|
}),
|
|
CACHE_INVALIDATION_PULSE: defineStorageKey({
|
|
logicalName: "CACHE_INVALIDATION_PULSE",
|
|
scope: "cache-invalidation",
|
|
name: "pulse",
|
|
backend: "localStorage",
|
|
classification: "opaque-cache",
|
|
schemaVersion: 1,
|
|
valueCodec: "opaque-string-v1",
|
|
ttl: null,
|
|
migration: "discard",
|
|
quotaFallback: "no-persist",
|
|
}),
|
|
AUTH_TOKEN: defineStorageKey({
|
|
logicalName: "AUTH_TOKEN",
|
|
scope: "auth",
|
|
name: "auth-token",
|
|
backend: "forbidden",
|
|
classification: "sensitive-forbidden",
|
|
schemaVersion: 1,
|
|
valueCodec: "none",
|
|
ttl: null,
|
|
migration: "discard",
|
|
quotaFallback: "feature-disable",
|
|
}),
|
|
});
|
|
|
|
export function defineStorageKey<Definition extends StorageKeyInput>(
|
|
definition: Definition,
|
|
): Readonly<Definition & { physicalKey: string }> {
|
|
if (
|
|
!["color-scheme-v1", "opaque-string-v1", "none"].includes(
|
|
definition.valueCodec,
|
|
)
|
|
) {
|
|
throw new Error("Unknown client storage value codec");
|
|
}
|
|
if (definition.migration !== "discard") {
|
|
throw new Error("Unsupported client storage migration policy");
|
|
}
|
|
if (definition.classification === "sensitive-forbidden") {
|
|
if (!["disabled", "forbidden"].includes(definition.backend)) {
|
|
throw new Error("Sensitive client storage registration is forbidden");
|
|
}
|
|
if (definition.valueCodec !== "none") {
|
|
throw new Error("Sensitive client storage codec is forbidden");
|
|
}
|
|
} else if (definition.valueCodec === "none") {
|
|
throw new Error("Persisted storage keys require a value codec");
|
|
}
|
|
if (!Number.isInteger(definition.schemaVersion) || definition.schemaVersion < 1) {
|
|
throw new Error("Storage schemaVersion must be a positive integer");
|
|
}
|
|
|
|
return Object.freeze({
|
|
...definition,
|
|
physicalKey: buildPhysicalKey(
|
|
definition.scope,
|
|
definition.schemaVersion,
|
|
definition.name,
|
|
),
|
|
});
|
|
}
|
|
|
|
export function buildPhysicalKey(
|
|
scope: string,
|
|
schemaVersion: number,
|
|
name: string,
|
|
): string {
|
|
return `${APP_NAMESPACE}:${scope}:v${schemaVersion}:${name}`;
|
|
}
|
|
|
|
export function getStorageDefinition(logicalName: string): StorageDefinition {
|
|
const registry: Readonly<Record<string, StorageDefinition>> = STORAGE_REGISTRY;
|
|
const definition = registry[logicalName];
|
|
if (!definition) throw new Error(`Unregistered storage key: ${logicalName}`);
|
|
if (definition.classification === "sensitive-forbidden") {
|
|
throw new Error(`Forbidden storage key: ${logicalName}`);
|
|
}
|
|
return definition;
|
|
}
|
|
|
|
export function isStorageValueAllowed(
|
|
definition: StorageDefinition,
|
|
value: unknown,
|
|
): boolean {
|
|
switch (definition.valueCodec) {
|
|
case "color-scheme-v1":
|
|
return value === "light" || value === "dark" || value === "system";
|
|
case "opaque-string-v1":
|
|
return (
|
|
typeof value === "string" &&
|
|
value.length >= 1 &&
|
|
value.length <= 2_048
|
|
);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|