ADR 0017: Strict-tier capability declarations as a CLI-emitted artifact
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-05-25 |
| Deciders | Project maintainers |
| Supersedes | — |
| Superseded by | — |
| Related ADRs | ADR-0005 (Adapter contract shape), ADR-0012 (Spec↔core type reconciliation) |
Summary
At Strict tier (strict conformance), aidokit init and aidokit sync
emit a .aidokit/capabilities.json artifact derived from every declared
adapter's manifest.capabilities. The artifact is the canonical
machine-readable surface that downstream tooling — the upcoming
capability verifier (workstream D4), aidokit verify, audit exports
(C5/C6) — reads to assert what the install promises.
The artifact is emitted by the CLI, not by adapter methods. The
Adapter interface gains no new methods; existing manifest fields are
the source of truth.
Context
Workstream A's tier model promises Strict as the audit-ready tier
(wiki/tiers/strict.md): capability declarations, signed manifest,
audit export. The Capabilities and CapabilityDeclarations types
are already exported from @aidokit/core and every adapter declares
them in manifest.capabilities. What's missing is the artifact —
a file in the user's project that records the declarations, that:
- humans can audit during compliance review,
- the future capability verifier (D4) can compare against the actual emitted hook scripts,
- the future audit export (C5/C6) can map onto SOC2 / EU AI Act controls,
aidokit verify(G5) can check for drift.
Today the declarations live only inside the npm package; the user has no in-repo artifact to point an auditor at. That gap blocks Strict from being a real product offering.
A second pressure: any extension to the Adapter interface ripples
across three first-party adapters (claude-code, codex, copilot) and
any future third-party adapter. Adding a new method (emitCapabilities)
to satisfy Strict has high blast radius for what is, semantically, a
read-only projection of data the manifest already carries.
Decision
1. The artifact is .aidokit/capabilities.json
A single JSON file under the project's .aidokit/ directory, written
during init and re-written by sync whenever the install changes.
Multi-adapter projects emit one entry per declared adapter.
{
"specVersion": "2.0",
"aidokitCliVersion": "...",
"tier": "strict",
"adapters": [
{
"name": "claude-code",
"conformance": "strict",
"capabilities": {
"shellCommands": ["claude mcp add"],
"writePaths": ["CLAUDE.md", ".claude/", "agent-artifacts/"],
"networkHosts": []
}
}
]
}
The shape is the same CapabilityDeclarations type already exported by
@aidokit/core, wrapped in a thin envelope with versioning metadata.
2. The artifact is emitted by the CLI, not by the adapter
packages/cli/src/init/capabilities-artifact.ts reads the state.json
manifest plus each adapter's manifest.capabilities and produces the
EmittedFile. The CLI's file-plan computation includes it at Strict
tier only.
The Adapter interface (ADR-0005) gains no method. Adapters remain
the source of truth for what they're capable of (via manifest);
the CLI is the source of truth for how that surfaces in the user's
project.
3. The artifact is Strict-only
Starter and Standard projects do not emit it. The semantics of the artifact — a stable, auditable contract surface — only matter at the tier that promises audit-readiness. Emitting it at lower tiers would suggest a guarantee the lower tiers don't make.
4. The artifact is included in the byte-compare gate
The Strict-tier reference fixture (when one exists — currently the
reference is at Standard) will include the expected capabilities.json.
Until then, the artifact is asserted only by the unit tests for the
emitter.
Consequences
Positive
- No
Adapterinterface change. Codex, Copilot, and any future third-party adapter need no rewrite — their existing manifest declarations are read directly. - Single source of truth.
manifest.capabilitiesremains authoritative; the artifact is a projection, not a parallel store. - Unblocks D4. The capability verifier has a canonical file to compare against the emitted hook scripts.
- Unblocks C5/C6. The audit export maps the artifact onto SOC2 / EU AI Act controls; the mapping document references stable JSON paths, not adapter source code.
aidokit verifyintegration is cheap. Existing diff machinery already covers JSON files in.aidokit/.
Negative
- The artifact is one more file users see in their repo. Mitigation:
it lives under
.aidokit/, the existing aidokit-controlled directory, alongsidestate.jsonandmanifest.md. - Cross-adapter dedupe is not represented. When two adapters declare the same shellCommand, the artifact lists it under each — the verifier needs to be aware of that. Mitigation: documented in D4's design.
- Schema versioning. The
specVersionfield in the envelope locks us into a schema; changing it later means a migration. Mitigation: reuse the existingSpecVersionfrom@aidokit/core/schema.
Neutral
- The artifact is a .json, not .md. Machine-readable wins over human-readable here because every downstream consumer (verifier, audit export, doctor) is machine-driven. A human-readable rendering can live in the docs site, generated from the same JSON.
- Tier-gated emission joins existing tier-aware code paths —
verbsForConformance,templateForConformance,baseSkillsForTier. The pattern is established; this is one more entry.
Alternatives considered
Alternative 1: New emitCapabilities(ctx) method on Adapter
Add emitCapabilities(ctx: ProjectContext): readonly EmittedFile[] to
the Adapter interface. Each adapter implements it (typically by
serialising its own manifest.capabilities).
Pros
- Adapters own the rendering — could include tier-specific tweaks.
- Symmetrical with
emitAgentRulesFile,emitEngineConfig, etc.
Cons
- Touches three adapter packages and any future third-party adapter.
- Boilerplate — every adapter implements ~the same function reading its own manifest.
- Risks drift between adapters' renderings of the same data.
Reason rejected: the artifact is a pure projection of
manifest.capabilities; pushing rendering into adapters adds
boilerplate without flexibility we actually use. The contract surface
should stay minimal.
Alternative 2: Tuck the artifact into emitEngineConfig
Have each adapter's emitEngineConfig include the capabilities file
in its returned EmittedFile[].
Pros
- No new method, no new module.
- Co-located with the rest of the engine directory output.
Cons
- Mixes "adapter engine config" (e.g.
settings.json) with the audit artifact, which has different ownership semantics. - Same per-adapter boilerplate as Alternative 1.
- The artifact lives under
.claude/(or.codex/,.copilot/) rather than the cross-adapter.aidokit/— multi-adapter projects would emit N copies.
Reason rejected: the artifact is cross-adapter and project-wide; putting it under a per-adapter engine directory breaks the multi-adapter case.
Alternative 3: Emit only at aidokit doctor --strict runtime, never to disk
Compute the capability declarations on demand from state.json +
adapter manifests; never persist them.
Pros
- No file to keep in sync.
- Always fresh.
Cons
- Auditors need a file artifact to attach to their review packet; "run this command and screenshot" is not acceptable evidence.
- The verifier (D4) needs a fixed snapshot to compare against; a freshly-computed value is not a control surface.
Reason rejected: compliance evidence requires durable artifacts.
The whole point of Strict tier is making the declarations auditable
outside the running aidokit process.
Alternative 4: YAML instead of JSON
Use .aidokit/capabilities.yaml.
Pros
- More human-readable.
Cons
- Requires a YAML parser (or hand-rolled write) in the CLI; the project has avoided YAML as a runtime dep on purpose.
- Auditors and downstream tooling prefer JSON for machine consumption.
Reason rejected: marginal readability win does not justify a new dependency or hand-rolled YAML emission.
Implementation notes
The CLI module:
- Lives at
packages/cli/src/init/capabilities-artifact.ts. - Exports
buildCapabilitiesArtifact(state, adapterManifests)(pure function) andemitCapabilitiesArtifact(ctx, adapters)(returnsEmittedFile | null— null below Strict). - The file plan computation in
file-plan.tsincludes the artifact when emitted. - Existing
emitEngineConfigand other adapter methods are unchanged.
Schema versioning:
- The artifact's
specVersionmirrorsctx.specVersion(already versioned in core). - Adding new top-level fields is a minor bump; changing existing
field shapes requires
aidokit migratesupport.
Validation
This decision is validated when:
- [ ] A Strict-tier project emits
.aidokit/capabilities.jsonmatching the declared adapter manifests. - [ ] Standard and Starter projects emit no capabilities artifact.
- [ ] D4's capability verifier reads the artifact and successfully cross-checks against emitted hook scripts.
- [ ] At least one SOC2 control row in
docs/compliance/soc2.mdcites a stable JSON path in the artifact as evidence. - [ ]
aidokit verifyreports drift on the artifact if it diverges from the templates.
References
Internal
- ADR-0005 — Adapter contract shape (the interface this ADR intentionally avoids extending)
- ADR-0012 — Spec↔core type reconciliation (the
CapabilityDeclarationstype lives in@aidokit/core/schema) wiki/tiers/strict.md— user-facing promise for Strict that this ADR makes good onpackages/core/src/schema.ts—CapabilitiesandCapabilityDeclarationstypesdocs/specs/security-model.md— broader capability-declarations context
External
- npm provenance (Sigstore) — adjacent supply-chain hardening (G4 workstream), conceptually similar pattern of declared-then-verified.