/** * Recurrence gate: every capability with a FUNCTION implementation is loadable. * * `CAPABILITY_MODULE_MAP` in capability-loader.ts is a hand-maintained table of * capability name → the script that implements it. A module can ship a perfectly * good `defineCapabilityFunction`, register the capability on deploy — and still * be unreachable, because the loader skips anything absent from that table. * * The failure is silent and inverted: consumers get "does not provide the * notification capability" for a module that demonstrably provides it. It cost * the notification transport a full round-trip to find, so it gets a gate. * * Scoped to FUNCTION capabilities on purpose. A capability that provides only * `data:` (`apt_publish`, `celilo_event_bus`) is read through `$capability:` * variables and has nothing to load — the map would be the wrong place for it. */ import { describe, expect, test } from 'bun:test'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; import { parse } from 'yaml'; import { CAPABILITY_MODULE_MAP } from './capability-loader'; /** Capabilities the framework implements itself, with no provider script. */ const FRAMEWORK_OWNED = new Set(['public_web']); /** Capability names a module implements as callable functions. */ function functionCapabilitiesIn(moduleDir: string): Set { const found = new Set(); const scriptsDir = join(MODULES_DIR, moduleDir, 'scripts'); let entries: string[]; try { entries = readdirSync(scriptsDir).filter((f) => f.endsWith('.ts') && !f.endsWith('.test.ts')); } catch { return found; } for (const file of entries) { const src = readFileSync(join(scriptsDir, file), 'utf-8'); // The declaration form is `capability: ''` inside // defineCapabilityFunction — matching it directly avoids inferring // implementation from a filename. if (!src.includes('defineCapabilityFunction')) continue; for (const match of src.matchAll(/capability:\s*'([a-z_]+)'/g)) found.add(match[1]); } return found; } const MODULES_DIR = join(import.meta.dir, '../../../../modules'); interface ManifestShape { provides?: { capabilities?: { name?: string }[] }; } function providedCapabilities(): Map { const byCapability = new Map(); for (const entry of readdirSync(MODULES_DIR, { withFileTypes: true })) { if (!entry.isDirectory()) continue; let manifest: ManifestShape; try { manifest = parse(readFileSync(join(MODULES_DIR, entry.name, 'manifest.yml'), 'utf-8')); } catch { continue; // Not a module directory. } const implemented = functionCapabilitiesIn(entry.name); for (const capability of manifest.provides?.capabilities ?? []) { if (!capability.name || !implemented.has(capability.name)) continue; const providers = byCapability.get(capability.name) ?? []; providers.push(entry.name); byCapability.set(capability.name, providers); } } return byCapability; } describe('recurrence gate: function capabilities are loadable', () => { const provided = providedCapabilities(); test('scans a non-trivial set of modules (sanity — the scan actually ran)', () => { expect(provided.size).toBeGreaterThan(2); }); test('every function capability a module implements is in CAPABILITY_MODULE_MAP', () => { const missing: string[] = []; for (const [capability, providers] of provided) { if (FRAMEWORK_OWNED.has(capability)) continue; if (capability in CAPABILITY_MODULE_MAP) continue; missing.push(`${capability} (provided by ${providers.join(', ')})`); } expect( missing, `These capabilities ship a defineCapabilityFunction but are absent from CAPABILITY_MODULE_MAP, so loadCapabilityFunctions will never return them:\n ${missing.join('\n ')}`, ).toEqual([]); }); });