/* RFC-0288: pure resolution of the closed capability catalog into the set of capabilities ACTIVE on a given site. A capability is active iff the site holds `agent.actions`, every additional `requires.entitlements` is held, every `requires.sections` archetype renders on some page, and its id is not in `agent.actionsDisabled`. Consumed by agent.manifest.generate (RFC-0286) to populate `AgentActionRef[]`, and by RFC-0289/0290 to project OpenAPI operations and MCP tools. Do not load the catalog from disk or read entitlements/content — the kernel command does that and passes already-loaded data here. RFC-0288: initial capability resolution. RFC-1169: capabilityToActionRef projects sideEffect into AgentActionRef — preview vs write is readable off agent.json. RFC-1186: boundCapabilityIds input — requires.formBinding records activate only where a bound send-message block exists; preview records resolve through their nextAction sibling id. RFC-1186: steps 1-2 — capability contracts, catalog records, resolver, propsSchema RFC-1186: AGC-FORM-06 missing formId, strict FORM-04 preview binding, shared bound-set helpers (RFC-1186) */ import type { CapabilityRecord } from "@warpgogol/werkstatt-shared/ontology"; import type { AgentActionRef } from "./manifest.ts"; import { capabilityPricingProjection } from "./capability-schema.ts"; export type { CapabilityRecord } from "@warpgogol/werkstatt-shared/ontology"; /** * Capability record as serialized into `agent-capabilities.generated.{json,yaml}` * by `agent.routes.generate` — RFC-1186 stamps `formBinding.formId` onto bound * capabilities so the runtime gate can tag `IntegrationEvent.payload.formId` * with the same discriminator the web form path sends (RFC-0827). The catalog * schema itself stays strict; this is the site-serialized shape only. */ export interface SiteBoundCapabilityRecord extends CapabilityRecord { formBinding?: { formId?: string }; } export interface ResolveActiveCapabilitiesInput { catalog: CapabilityRecord[]; /** Resolved entitlement feature ids (e.g. from entitlements.generated.yaml). */ entitlements: string[]; /** Section archetypes (blocks[].type) that render on some page of the app. */ renderedSectionTypes: string[]; /** system.md agent.actionsDisabled — capability ids to withhold even if otherwise active. */ actionsDisabled: string[]; /** * RFC-1186: capability ids bound via `agentCapability` props on site * `send-message` blocks. A `requires.formBinding: true` record activates * only when its bound id is present — for `sideEffect: "none"` records the * bound id is the `nextAction.capability` sibling, so one block prop * activates the submit/apply capability and its preview pair. */ boundCapabilityIds?: string[]; } /** Pure: gate the closed catalog down to the capabilities active on this site. */ export function resolveActiveCapabilities( input: ResolveActiveCapabilitiesInput, ): CapabilityRecord[] { const entitlements = new Set(input.entitlements); const renderedSections = new Set(input.renderedSectionTypes); const disabled = new Set(input.actionsDisabled); const bound = new Set(input.boundCapabilityIds ?? []); if (!entitlements.has("agent.actions")) return []; return input.catalog.filter((cap) => { if (disabled.has(cap.id)) return false; for (const req of cap.requires.entitlements) { if (!entitlements.has(req)) return false; } for (const section of cap.requires.sections) { if (!renderedSections.has(section)) return false; } if (cap.requires.formBinding === true) { const boundId = cap.nextAction?.capability ?? cap.id; if (!bound.has(boundId)) return false; } return true; }); } /** Project one active capability into its manifest AgentActionRef (RFC-0286). */ export function capabilityToActionRef(cap: CapabilityRecord): AgentActionRef { const pricing = capabilityPricingProjection(cap); return { id: cap.id, // RFC-1171: canonical action urls live under the versioned namespace; the // unversioned path stays alive only as a deprecated alias (manifest // deprecations[] + gate-stamped headers). url: `/api/agent/v1/actions/${cap.id}`, title: cap.title, inputSchemaRef: `#/components/schemas/${cap.id}-input`, entitlement: "agent.actions", // RFC-1169: consumers must distinguish preview ("none") from write actions. sideEffect: cap.sideEffect ?? "write", // RFC-1212: metered capabilities expose machine-readable pricing — absent // for unmetered records (bijective projection, AGM-04 enforces). ...(pricing ? { pricing } : {}), }; }