/** * A module may not declare a provider view. * * `web_routes` and `firewall_registry` are in `KNOWN_CAPABILITY_NAMES`, so the * name check that asks only "is this known?" accepts them. celilo injects both * into the hooks of the module that PROVIDES the paired capability and never * to a consumer, so a module requiring one would validate, publish, deploy, and * then find the capability simply absent at hook time — with a pre-flight error * naming a missing provider that was never going to exist. * * Registering the two names (celilo#1007) is what created this opening. They * were invisible to every check before that, which was its own, worse problem. */ import { describe, expect, test } from 'bun:test'; import { PROVIDER_VIEW_CAPABILITIES } from '@celilo/capabilities'; import type { ModuleManifest } from './schema'; import { validateCapabilityNames } from './validate'; function manifestRequiring(name: string): ModuleManifest { return { requires: { capabilities: [{ name, version: '1.0.0' }] }, } as unknown as ModuleManifest; } function manifestOptionally(name: string): ModuleManifest { return { requires: { capabilities: [] }, optional: { capabilities: [{ name, version: '1.0.0' }] }, } as unknown as ModuleManifest; } describe('provider views are not declarable', () => { for (const view of PROVIDER_VIEW_CAPABILITIES) { test(`requires.capabilities rejects '${view}'`, () => { const result = validateCapabilityNames(manifestRequiring(view)); expect(result).not.toBeNull(); expect(result?.errors[0].message).toContain('provider view'); }); test(`optional.capabilities rejects '${view}' too`, () => { // The optional path is checked for the same reason the privileged- // capability check covers it: otherwise the declaration is smuggled in // through the soft-require door. const result = validateCapabilityNames(manifestOptionally(view)); expect(result).not.toBeNull(); expect(result?.errors[0].message).toContain('provider view'); }); test(`'${view}' is not offered in the suggestion list`, () => { // The message for a genuine typo lists what an author CAN require, so a // view must not appear there either. const result = validateCapabilityNames(manifestRequiring('definitely_not_a_capability')); expect(result?.errors[0].message).not.toContain(view); }); } test('a real capability still validates', () => { expect(validateCapabilityNames(manifestRequiring('public_web'))).toBeNull(); }); });