From 243565a75e77c2cd3fb18a1a0e13f9c742106aee Mon Sep 17 00:00:00 2001 From: chick Date: Fri, 17 Jul 2026 01:34:13 +0800 Subject: [PATCH] feat(registry): add fail-closed runtime lookup --- packages/operation-registry/package.json | 9 +- packages/operation-registry/src/index.ts | 9 ++ .../src/runtime-registry.ts | 122 ++++++++++++++++++ .../test/runtime-registry.test.ts | 105 +++++++++++++++ 4 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 packages/operation-registry/src/index.ts create mode 100644 packages/operation-registry/src/runtime-registry.ts create mode 100644 packages/operation-registry/test/runtime-registry.test.ts diff --git a/packages/operation-registry/package.json b/packages/operation-registry/package.json index dff99b9..9e5f597 100644 --- a/packages/operation-registry/package.json +++ b/packages/operation-registry/package.json @@ -2,5 +2,12 @@ "name": "@multi-simadmin/operation-registry", "version": "0.0.0", "private": true, - "type": "module" + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + } + }, + "types": "./src/index.ts" } diff --git a/packages/operation-registry/src/index.ts b/packages/operation-registry/src/index.ts new file mode 100644 index 0000000..5cc2e35 --- /dev/null +++ b/packages/operation-registry/src/index.ts @@ -0,0 +1,9 @@ +export { + OperationRegistry, + RuntimeRegistryError, + listOperations, + operationRegistry, + requireExecutableOperation, + requireOperation, +} from './runtime-registry.ts'; +export type { RuntimeOperationDescriptor, RuntimeRegistryErrorCode } from './runtime-registry.ts'; diff --git a/packages/operation-registry/src/runtime-registry.ts b/packages/operation-registry/src/runtime-registry.ts new file mode 100644 index 0000000..dfdbfec --- /dev/null +++ b/packages/operation-registry/src/runtime-registry.ts @@ -0,0 +1,122 @@ +import { operationAcceptanceOverrides58e2204 } from './acceptance-58e2204.ts'; +import { upstream58e2204Operations } from './upstream-58e2204.ts'; + +export type RuntimeRegistryErrorCode = 'UNKNOWN_OPERATION' | 'DEDICATED_FLOW_REQUIRED'; + +export class RuntimeRegistryError extends Error { + readonly code: RuntimeRegistryErrorCode; + + constructor(code: RuntimeRegistryErrorCode, message: string) { + super(message); + this.name = 'RuntimeRegistryError'; + this.code = code; + } +} + +type UpstreamOperation = (typeof upstream58e2204Operations)[number]; +type AcceptanceOverride = Record & { uiStrategy?: unknown }; + +export type RuntimeOperationDescriptor = Readonly< + UpstreamOperation & { + module: UpstreamOperation['upstreamDomain']; + uiStrategy: string; + } +>; + +const OPERATION_ID_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/; +const EXPECTED_OPERATION_COUNT = 117; + +function deepFreeze(value: T): T { + if (value === null || typeof value !== 'object' || Object.isFrozen(value)) return value; + for (const child of Object.values(value)) deepFreeze(child); + return Object.freeze(value); +} + +function unknownOperation(operationId: unknown): RuntimeRegistryError { + const rendered = typeof operationId === 'string' && operationId ? operationId : ''; + return new RuntimeRegistryError('UNKNOWN_OPERATION', `Unknown operation: ${rendered}`); +} + +/** Runtime-only, operationId-keyed projection of the frozen audited registry. */ +export class OperationRegistry { + readonly #byOperationId: ReadonlyMap; + readonly #operations: readonly RuntimeOperationDescriptor[]; + + constructor( + operations: readonly UpstreamOperation[], + acceptanceOverrides: Readonly>, + ) { + const sourceById = new Map(); + for (const operation of operations) { + const id = operation.operationId; + if (!OPERATION_ID_PATTERN.test(id)) throw new Error(`invalid operationId in registry: ${id}`); + if (sourceById.has(id)) throw new Error(`duplicate operationId: ${id}`); + sourceById.set(id, operation); + } + if (sourceById.size !== EXPECTED_OPERATION_COUNT) { + throw new Error( + `runtime registry must contain exactly ${EXPECTED_OPERATION_COUNT} operations`, + ); + } + + const descriptors: RuntimeOperationDescriptor[] = []; + for (const id of [...sourceById.keys()].sort()) { + if (!Object.hasOwn(acceptanceOverrides, id)) { + throw new Error(`missing acceptance override: ${id}`); + } + const acceptance = acceptanceOverrides[id]; + if (!acceptance || typeof acceptance.uiStrategy !== 'string' || !acceptance.uiStrategy) { + throw new Error(`missing acceptance override: ${id}`); + } + const operation = sourceById.get(id)!; + const descriptor = structuredClone({ + ...operation, + module: operation.upstreamDomain, + uiStrategy: acceptance.uiStrategy, + }) as RuntimeOperationDescriptor; + descriptors.push(deepFreeze(descriptor)); + } + + this.#operations = Object.freeze(descriptors); + this.#byOperationId = new Map( + descriptors.map((descriptor) => [descriptor.operationId, descriptor]), + ); + Object.freeze(this); + } + + listOperations(): readonly RuntimeOperationDescriptor[] { + return this.#operations; + } + + requireOperation(operationId: string): RuntimeOperationDescriptor { + if (typeof operationId !== 'string' || !OPERATION_ID_PATTERN.test(operationId)) { + throw unknownOperation(operationId); + } + const operation = this.#byOperationId.get(operationId); + if (!operation) throw unknownOperation(operationId); + return operation; + } + + requireExecutableOperation(operationId: string): RuntimeOperationDescriptor { + const operation = this.requireOperation(operationId); + if (operation.executionPolicy === 'dedicatedFlow') { + throw new RuntimeRegistryError( + 'DEDICATED_FLOW_REQUIRED', + `Operation requires a dedicated flow: ${operationId}`, + ); + } + return operation; + } +} + +export const operationRegistry = new OperationRegistry( + upstream58e2204Operations, + operationAcceptanceOverrides58e2204, +); + +export const listOperations = (): readonly RuntimeOperationDescriptor[] => + operationRegistry.listOperations(); +export const requireOperation = (operationId: string): RuntimeOperationDescriptor => + operationRegistry.requireOperation(operationId); +export const requireExecutableOperation = (operationId: string): RuntimeOperationDescriptor => + operationRegistry.requireExecutableOperation(operationId); diff --git a/packages/operation-registry/test/runtime-registry.test.ts b/packages/operation-registry/test/runtime-registry.test.ts new file mode 100644 index 0000000..2f19463 --- /dev/null +++ b/packages/operation-registry/test/runtime-registry.test.ts @@ -0,0 +1,105 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + OperationRegistry, + RuntimeRegistryError, + listOperations, + operationRegistry, + requireExecutableOperation, + requireOperation, +} from '../src/index.ts'; +import { upstream58e2204Operations } from '../src/upstream-58e2204.ts'; +import { operationAcceptanceOverrides58e2204 } from '../src/acceptance-58e2204.ts'; + +test('runtime registry projects exactly 117 descriptors in stable operationId order', () => { + const listed = listOperations(); + assert.equal(listed.length, 117); + assert.equal(new Set(listed.map((row) => row.operationId)).size, 117); + assert.deepEqual( + listed.map((row) => row.operationId), + listed.map((row) => row.operationId).toSorted(), + ); + assert.ok(Object.isFrozen(listed)); + assert.ok(listed.every((row) => Object.isFrozen(row))); + + const device = requireOperation('getDevice'); + assert.equal(device.module, device.upstreamDomain); + assert.equal(device.uiStrategy, operationAcceptanceOverrides58e2204.getDevice.uiStrategy); + assert.equal(device.method, 'GET'); + assert.ok(Object.isFrozen(device.sourceEvidence)); + assert.strictEqual(requireOperation('getDevice'), device); + assert.strictEqual(operationRegistry.requireOperation('getDevice'), device); +}); + +test('runtime construction rejects duplicate rows and missing acceptance overrides', () => { + const rows = upstream58e2204Operations as readonly (typeof upstream58e2204Operations)[number][]; + assert.throws( + () => new OperationRegistry([...rows, rows[0]], operationAcceptanceOverrides58e2204), + /duplicate operationId: getDevice/, + ); + const missing = { ...operationAcceptanceOverrides58e2204 }; + delete (missing as Record).getDevice; + assert.throws( + () => new OperationRegistry(rows, missing), + /missing acceptance override: getDevice/, + ); +}); + +test('requireOperation validates only operationId and fails closed with a stable typed error', () => { + for (const id of [ + '', + ' ', + 'GET /api/device', + '/api/device', + '__proto__', + 'constructor', + 'get-device', + undefined, + null, + ]) { + assert.throws( + () => requireOperation(id as string), + (error: unknown) => + error instanceof RuntimeRegistryError && error.code === 'UNKNOWN_OPERATION', + String(id), + ); + } + assert.throws( + () => requireOperation('getDefinitelyUnknown'), + (error: unknown) => error instanceof RuntimeRegistryError && error.code === 'UNKNOWN_OPERATION', + ); +}); + +test('dedicated auth flows are catalogued but cannot cross executable boundary', () => { + for (const id of [ + 'postAuthSetup', + 'postAuthPassword', + 'postAuthSettings', + 'postAuthLogin', + 'postAuthLogout', + ]) { + assert.equal(requireOperation(id).executionPolicy, 'dedicatedFlow'); + assert.throws( + () => requireExecutableOperation(id), + (error: unknown) => + error instanceof RuntimeRegistryError && error.code === 'DEDICATED_FLOW_REQUIRED', + id, + ); + } + assert.strictEqual(requireExecutableOperation('getDevice'), requireOperation('getDevice')); +}); + +test('descriptors are deeply immutable and mutation cannot affect later lookups or frozen source', () => { + const source = upstream58e2204Operations.find((row) => row.operationId === 'getDevice')!; + const originalSourceEvidence = structuredClone(source.sourceEvidence); + const descriptor = requireOperation('getDevice'); + + assert.ok(Object.isFrozen(descriptor)); + assert.ok(Object.isFrozen(descriptor.sourceEvidence)); + assert.ok(Object.isFrozen(descriptor.sensitiveFields)); + assert.throws(() => ((descriptor as { method: string }).method = 'DELETE'), TypeError); + assert.throws(() => (descriptor.sourceEvidence as string[]).push('mutation'), TypeError); + + assert.equal(requireOperation('getDevice').method, 'GET'); + assert.deepEqual(source.sourceEvidence, originalSourceEvidence); +});