/** * @fileoverview Scope-owned capability registry (release 2.10.0, §5.3). * * The host-side runtime for the capability model in `tools/capability.ts`. * A tool declares the domains it OWNS in its static manifest; the host * registers each declared domain here alongside the owner-supplied * REGISTRAR, then routes incoming contributions to the owning registrar * after a Zod-free schema check. The host never interprets a * contribution — it only confirms "targets a declared domain + passes its * schema" and hands off (north-star §4.5). * * **Per-`RunScope`, no module singleton.** Mirrors the simulation §5.11 * template (`createScenarioRegistry` / `currentScenarioRegistry`): * * - `createCapabilityRegistry()` — factory the CLI bootstrap calls once * per invocation, attaching the result to `scope.capabilities`. * - `currentCapabilityRegistry()` — reads the scope-bound registry off * `currentScope()`; throws when called outside a `RunScope`. * * Unlike a tool subscope, the capability registry is a KERNEL concern (the * host owns it, not any one tool), so its slot lives directly on * `ToolScope` (`capabilities?`) rather than under a tool's name. A run that * never constructs one carries `scope.capabilities === undefined` and reads * return `undefined`. * * Errors are typed + structured (§ Steps 2.1.3): an unknown domain is a * `NotFoundError` (`CAPABILITY.DOMAIN.UNKNOWN`); a schema mismatch is a * `ValidationError` (`CAPABILITY.CONTRIBUTION.SCHEMA_MISMATCH`), both * carrying a structured `diagnostic` for the CLI error boundary. */ import { type Logger } from '../lib/logger.js'; import { type CapabilityDomainSpec, type CapabilityRegistrar } from '../tools/capability.js'; import type { ToolPluginManifest } from '../tools/manifest.js'; export type { CapabilityRegistrar } from '../tools/capability.js'; /** * Per-`RunScope` capability registry. Holds the declared domains (by id) * and routes contributions to their owners. Construct one per CLI * invocation via {@link createCapabilityRegistry}; read the scope-bound * instance via {@link currentCapabilityRegistry}. */ export declare class CapabilityRegistry { private readonly domains; private readonly logger; constructor(logger?: Logger); /** * Register a capability domain + its owner's registrar. First-writer-wins * on duplicate domain id: a second registration of the same id is a no-op * (logged at debug) — mirrors the discovery walker's nearest-ancestor * dedup, so re-running discovery is idempotent. * * @param spec The domain description (id, owner, epoch, schema, kind). * @param registrar The owner callback invoked for a validated contribution. */ registerDomain(spec: CapabilityDomainSpec, registrar: CapabilityRegistrar): void; /** * Replace the registrar for an ALREADY-REGISTERED domain (Phase 4). The * manifest-read path ({@link registerCapabilityDomainsFromManifest}) * registers each declared domain with a DEFERRED placeholder registrar that * throws on any contribution; once the owning tool's runtime module loads, * the host calls this to swap in the tool's REAL registrar. * * This is the deliberate complement to {@link registerDomain}'s * first-writer-wins: a domain's REGISTRAR is owner-replaceable (the owner * supplies it late), but its SPEC is not — the spec stays exactly as the * manifest declared it, so identity/epoch/schema cannot be overwritten by a * late registrar wiring. Only the owning tool should call this (the host * routes by `ownerToolId`); the spec is left untouched. * * @param domainId The domain whose registrar to replace. * @param registrar The owner's real registrar. * @throws {NotFoundError} (`CAPABILITY.DOMAIN.UNKNOWN`) when no domain * `domainId` is registered — a registrar cannot be wired for a domain the * host never declared (a manifest/tool-id mismatch). */ setRegistrar(domainId: string, registrar: CapabilityRegistrar): void; /** Whether a domain with `domainId` is registered. */ hasDomain(domainId: string): boolean; /** Look up a registered domain's spec, or `undefined`. */ getDomain(domainId: string): CapabilityDomainSpec | undefined; /** All registered domain specs (registration order). */ listDomains(): readonly CapabilityDomainSpec[]; /** * Route a contribution to the owner of `domainId`. The host (a) confirms * the domain is declared, (b) validates `contribution` against the * domain's `contributionSchema`, then (c) hands it to the owner's * registrar. The host never interprets the contribution beyond the * schema check — the domain owner does. * * @throws {NotFoundError} (`CAPABILITY.DOMAIN.UNKNOWN`) when no domain * `domainId` is registered. * @throws {ValidationError} (`CAPABILITY.CONTRIBUTION.SCHEMA_MISMATCH`) * when the contribution fails the domain's schema check. */ routeContribution(domainId: string, contribution: unknown): void; } /** * Augment the kernel scope with the capability registry slot. Unlike a * tool subscope (which a tool augments from ITS package), the capability * registry is a kernel concern, so core declares the slot itself here — * keeping the leaf `scope-types.ts` free of any edge back to this module * (which imports `run-scope.ts`). Optional + mutable: only the CLI * bootstrap (or a test) attaches it; a run without it reads `undefined`. */ declare module '../lib/scope-types.js' { interface ScopeContribution { /** * The host's per-run capability registry. Seeded by the CLI bootstrap * (Phase 4) and read via {@link currentCapabilityRegistry}. Absent on * a scope that never constructed one — consumers MUST null-check (the * reader throws a descriptive error in that case). */ capabilities?: CapabilityRegistry; } } /** Construct a fresh capability registry for a single `RunScope`. */ export declare function createCapabilityRegistry(logger?: Logger): CapabilityRegistry; /** * Read the current scope's capability registry. Throws when no scope is * active or when the scope has no capability registry — both indicate the * caller is running outside the CLI's pre-action-hook (or a test fixture * forgot to construct + attach one). * * @throws {Error} When called outside `runWithScope(...)`, or when the * active scope carries no `capabilities` registry. */ export declare function currentCapabilityRegistry(): CapabilityRegistry; /** * Register every capability domain a manifest declares into the per-run * {@link CapabilityRegistry}, stamping `ownerToolId = manifest.id` on each * (§5.3 / Task 2.2). This is how `MARKER_KINDS` becomes a BOOTSTRAP DEFAULT: * the marker enum still seeds the discovery vocabulary, and a * manifest-declared domain EXTENDS that set — registered here without any * host-enum edit. Additive: a manifest with no `capabilities` registers * nothing. * * The owner-supplied registrar is NOT known at manifest-read time (the * tool's runtime module hasn't been imported), so this registers the * domain with a deferred-registrar placeholder that THROWS if a * contribution is routed before the owning tool wires its real registrar * (Phase 4). The host knows the domain EXISTS and who owns it; the owner * supplies the actual registrar when its module loads. * * Emits one structured `capability.domain.from_manifest` evt per domain so * a manifest-sourced domain is observable in structured logs (Task 2.2.3). * * @param manifest The validated manifest carrying the declarations. * @param registry The per-run capability registry to populate. * @returns The {@link CapabilityDomainSpec}s registered (owner-stamped). */ export declare function registerCapabilityDomainsFromManifest(manifest: ToolPluginManifest, registry: CapabilityRegistry): readonly CapabilityDomainSpec[]; //# sourceMappingURL=capability-registry.d.ts.map