/** * Every name the loader can put in `ctx.capabilities` must be a registered * capability. * * This is the gate for the class of bug, not for its instances. Twice now a * capability has been injected under a literal name that appeared in no * registry and, in one case, had no declared type anywhere: * * - `web_routes`, whose two methods were synchronous. The hook process * boundary's D2 measured the hook-facing surface as uniformly async by * reading `CapabilityRegistry`, so it never saw them. caddy's `on_install` * died on `{} is not iterable` when the async proxy handed it a Promise. * - `firewall_registry`, which had no `FirewallRegistryCapability` type, no * registry entry, and no contract version. It happens to be async, so it * broke nothing, which is exactly why nobody found it. * * `type-invariants.test.ts` proves every REGISTERED capability's methods are * async. That proof is only worth as much as the registry's completeness, and * completeness is what this file checks. Together they close the loop: the * registry names everything injected, and everything named is async. * * It reads the loader's source because the property is about what the code can * assign, not about what one run happens to produce. A runtime test would need * every provider deployed in every combination to see all the branches. */ import { describe, expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { KNOWN_CAPABILITY_NAMES } from '@celilo/capabilities'; const LOADER = join(__dirname, 'capability-loader.ts'); /** * Literal names assigned into the capability map, e.g. `result.web_routes =`. * * Computed assignments (`result[capName] =`) are deliberately not matched: * `capName` is already a registry name, since it comes from the capability * table keyed by the same list. */ function injectedCapabilityNames(source: string): string[] { const names = new Set(); for (const match of source.matchAll(/^\s*result\.([A-Za-z_][A-Za-z0-9_]*)\s*=/gm)) { names.add(match[1]); } return [...names].sort(); } describe('capabilities injected into the hook context', () => { test('every literally-named injection is a registered capability', () => { const injected = injectedCapabilityNames(readFileSync(LOADER, 'utf-8')); // If this is empty the regex has drifted and the gate is checking nothing, // which is the failure mode that makes a green test worse than no test. expect(injected.length).toBeGreaterThan(0); const known: readonly string[] = KNOWN_CAPABILITY_NAMES; const unregistered = injected.filter((name) => !known.includes(name)); expect(unregistered).toEqual([]); }); test('PROVE IT FAILS: an unregistered name is caught', () => { const withNewInjection = ` result.public_web = createPublicWeb({}); result.brand_new_view = somethingUndeclared; `; const known: readonly string[] = KNOWN_CAPABILITY_NAMES; const unregistered = injectedCapabilityNames(withNewInjection).filter( (name) => !known.includes(name), ); expect(unregistered).toEqual(['brand_new_view']); }); });