docs(#1464): fix ADR-1244 capability doc set — followable tutorials, overlay-model + install tutorial, set/fragment/runtimeCompat reference, accuracy fixes

This commit is contained in:
Tom Boucher
2026-06-20 11:59:09 -04:00
parent 1c1c31f677
commit 02c6491e61
11 changed files with 860 additions and 142 deletions

View File

@@ -80,11 +80,11 @@ For third-party capabilities, a version whose executable set (hooks, command mod
| Source kind | Re-resolution behaviour |
|---|---|
| `<name>@<registry>` | Registry catalogue query |
| git (`https://…/repo.git#<tag>`) | Remote tag fetch |
| npm (`npm:@org/pkg@<range>`) | `npm dist-tags` / range resolution |
| tarball (`https://…/cap-x.y.z.tgz`) | Re-fetch of the recorded URL |
| local (`./local/path`) | Re-read of the recorded filesystem path |
| registry (`<name>@<registry>`) | **Not yet implemented** — the registry source kind is reserved; re-resolution throws, so an overlay recorded from a registry spec cannot currently be updated. |
---
@@ -127,7 +127,11 @@ gsd capability disable <id> [--config-dir <path>] [--runtime <r>] [--scope <s>]
**Behaviour**
Marks the capability **inactive** in the runtime activation state — identical to `gsd capability set <id> --off`. A disabled capability stays on disk; it is excluded from the active surface and contributes no hooks, config keys, or loop extension registrations until re-enabled. This toggles the capability-state layer (the runtime config), not the install ledger. The id must be a capability known to the registry; activation toggling of an installed **third-party overlay** by id is not yet wired through this path — remove an overlay with `gsd capability remove`. `enable` reverses a disable without re-fetching.
Marks the capability **inactive** in the runtime activation state — identical to `gsd capability set <id> --off`. A disabled capability stays on disk; it is excluded from the active surface and contributes no hooks, config keys, or loop extension registrations until re-enabled. This toggles the capability-state layer (the runtime config), not the install ledger.
> **Scope: first-party capabilities only.** `<id>` is validated against the **build-time first-party registry** (the generated `capability-registry.cjs`). An installed **third-party overlay** — one added with `gsd capability install …` — is **not** in that registry, so `disable` rejects it with `unknown capability: "<id>"`. Deactivate an installed overlay with [`gsd capability remove <id> --scope <scope>`](#remove) instead.
`enable` reverses a disable without re-fetching.
---
@@ -143,6 +147,44 @@ gsd capability enable <id> [--config-dir <path>] [--runtime <r>] [--scope <s>]
Clears the inactive flag for `<id>` in the runtime activation state — identical to `gsd capability set <id> --on`. On the next GSD invocation the capability is included in the active surface again, subject to its `engines.gsd` range (an incompatible capability is still skipped with a warning at load time).
> **Scope: first-party capabilities only.** Like `disable`, `enable` validates `<id>` against the build-time first-party registry and rejects an installed overlay with `unknown capability: "<id>"`. There is no `enable` for an installed overlay — re-install it with [`gsd capability install …`](#install) if it was removed.
---
### `set`
**Synopsis**
```
gsd capability set <id> [--on | --enable | --off | --disable] [--gate <key>=<bool>]… [--config-dir <path>] [--runtime <r>] [--scope <s>]
```
**Flags**
| Flag | Description |
|---|---|
| `--on` / `--enable` | Surface the capability (activate its skills). Mutually exclusive with `--off`/`--disable`. |
| `--off` / `--disable` | Unsurface the capability (deactivate its skills). Mutually exclusive with `--on`/`--enable`. |
| `--gate <key>=<bool>` | Set one capability **gate** to `true` or `false`. Repeatable to set several gates in one call. `<bool>` must be the literal `true` or `false`; any other value is rejected. |
| `--config-dir <path>` | Override the runtime config directory the surface state is read from and written to. |
| `--runtime <r>` | When given, re-materialise the surface (rewrite skill files) for runtime `<r>` after the state change. |
| `--scope <s>` | The materialise scope (`global` or `project`); only meaningful together with `--runtime`. Defaults to `global`. |
**Behaviour**
`set` is the single write verb behind the capability **activation** axes. It mutates two independent layers and then re-resolves and reports the capability's state:
- The **enabled** axis (`--on`/`--off`) toggles whether the capability's skills are on the runtime surface (the same mechanism `disable`/`enable` use; `disable`/`enable` are thin aliases for `set … --off`/`--on`).
- The **gate** axis (`--gate`) writes capability-owned config keys into `.planning/config.json`.
> **Scope: first-party capabilities only.** `set` validates `<id>` against the **build-time first-party registry** (the generated `capability-registry.cjs`); an unrecognized id — including any installed **third-party overlay** — is rejected with `unknown capability: "<id>"` and no writes are performed. `set` is for the activation/gate axes of first-party capabilities; to turn off an installed overlay use [`gsd capability remove`](#remove).
A **gate** is a dotted config key declared in the capability's `config` slice whose boolean value controls whether one of the capability's loop hooks fires. Setting a gate to `false` stops that hook running while leaving the capability surfaced; setting it to `true` re-arms it. A `--gate <key>=…` whose `<key>` is not a declared config key of `<id>`, or whose value is not boolean, is rejected and **no** writes are performed (the whole operation is validated before any state is written).
The command is **fail-closed on intent**: if you ask to enable a capability whose skills are not in the install profile, or whose surface/profile does not actually carry it, the operation reports an error rather than silently no-op'ing. Enabling a capability that owns no skills is an advisory warning (use gates to toggle its hooks instead). Surfacing a capability whose every hook is gated off is reported as a warning ("surfaced but every hook is gated off — did you mean `--off`?").
In `--raw` mode the full `{ capabilities, warnings, errors }` envelope is emitted as JSON and the process exits non-zero when `errors` is non-empty; in human mode warnings and errors are written to stderr and a one-line summary of the target capability (`enabled`, `surfaced`, `installed`, active-hook count) is printed.
---
### `list`
@@ -150,7 +192,7 @@ Clears the inactive flag for `<id>` in the runtime activation state — identica
**Synopsis**
```
gsd capability list [--json]
gsd capability list [--json] [--scope global|project]
```
**Flags**
@@ -158,10 +200,11 @@ gsd capability list [--json]
| Flag | Description |
|---|---|
| `--json` | Currently a **no-op**: `list` always emits the JSON array regardless of this flag. The flag is accepted for forward compatibility — a formatted human-readable table is planned, at which point `--json` will select the JSON form. Do not rely on omitting `--json` to get non-JSON output today. |
| `--scope` | Read only the given scope's overlay ledger (`global` or `project`). When omitted, both overlay scopes are swept. First-party capabilities are always listed regardless of `--scope`. |
**Behaviour**
Lists capabilities visible to the current session: first-party capabilities (from the registry) plus installed overlay capabilities in both the `global` and `project` scopes. Emits a JSON array of descriptors.
Lists capabilities visible to the current session: first-party capabilities (from the registry) plus installed overlay capabilities. With no `--scope`, both the `global` and `project` overlay scopes are swept; with `--scope`, only that scope's overlay ledger is read. Emits a JSON array of descriptors.
**Output shape**
@@ -189,7 +232,7 @@ Lists capabilities visible to the current session: first-party capabilities (fro
| `incompatible` | An overlay whose `engines.gsd` range does not satisfy the current GSD version; skipped with a warning at load time. |
| `inactive` | A **project-scope** overlay that is present on disk (and may have a committed-looking project ledger) but has **no user consent record on this machine** (#1459). It is *discovered but not activated*: it contributes no surfaces and runs nothing. The accompanying `reason` field explains why. Consent it by re-installing through the lifecycle (`gsd capability install … --scope project`). |
The `reason` field is `null` for active/incompatible rows and carries a short explanation for `inactive` rows.
The `reason` field is present on **overlay** rows: `null` for active/incompatible overlays and a short explanation for `inactive` ones. First-party rows omit `reason` (and `scope`/`source`/`status` are always `first-party`/`first-party`/`active`).
> Whether a capability has been turned off via `disable` is reported by `gsd capability state` (the activation-state view), not by `list`.
@@ -281,6 +324,8 @@ gsd capability trust revoke <id> [--project <path>]
"scope": "project",
"projectRoot": "/abs/realpath/of/project",
"integrity": "sha512-… | (empty)",
"disclosureSignature": "string",
"contentHash": "sha512-…",
"consentedAt": "ISO-8601 timestamp"
}
]
@@ -298,11 +343,11 @@ The `install` subcommand accepts the following source specification forms.
| Form | Example | Adapter | `--integrity` |
|---|---|---|---|
| Registry name | `my-cap@gsd-registry` | Registry — fetches the capability bundle from the named registry; `integrity` is populated from the registry catalogue. | Verified over the fetched bundle. |
| Git URL with tag | `https://github.com/org/repo.git#v1.2.0` | Git — clones/fetches at the specified tag; `#sha:<40-hex>` pins a specific commit. | **Rejected** — a clone is a directory tree, not a single hashable artifact. Pin the commit with `#sha:<commit>` instead. |
| npm package | `npm:@org/gsd-capability-foo@^1.0.0` | npm — resolves via `npm dist-tags` / semver range; installs with `--ignore-scripts`. | Verified over the `npm pack` `.tgz` bytes (same SRI sha512 domain as a tarball). |
| Tarball URL | `https://host/path/cap-x.y.z.tgz` | Tarball — fetches over HTTPS. | Verified over the downloaded `.tgz` bytes. |
| Local path | `./local/path` (or an absolute path) | Local — copies from the filesystem path. Auto-update detection is not available for this form. | **Rejected** — a local directory has no single hashable artifact; integrity pinning is not supported for local sources. |
| Registry name | `my-cap@gsd-registry` | **Reserved — not yet implemented.** The spec form parses, but there is no first-party registry endpoint, so resolution throws and the install fails. Use a git, npm, tarball, or local source today. | n/a |
Which source forms are *permitted* is governed by the `capabilities.strict_known_registries` policy (see [Configuration](../CONFIGURATION.md) and [the capability trust model](../explanation/capability-trust-model.md)): `null`/absent is permissive, `[]` is lockdown (no third-party sources), and a host allowlist permits only matching registries. This policy is **project-scoped** — it is read from the current project's `.planning/config.json` and applied to installs run in that project regardless of `--scope`; there is no machine-wide source allowlist. (A present-but-unparseable config fails **closed** — external installs are blocked until it is fixed.)