* test(#2801): failing-first suite for the hostBehaviors.reviewerCli alias removal Inverts the Phase 5a rows that assert the derived legacy alias still contributes a reviewer slug, and adds the removal-warning coverage the alias's exit needs (ADR-2782 D9). RED against unmodified production code, by design: the six shipped manifests still declare the key and collectReviewerWarnings emits nothing for hostBehaviors. Refs #2801 * chore(#2801): remove the hostBehaviors.reviewerCli deprecated alias ADR-2782 D9, Phase 7 — the final phase of epic #2782. The derived legacy alias survived one release (Phase 5a shipped in 1.9.0; 1.9.1 and 1.10.0 have since gone out), so it goes. A declared reviewer body is now the only route onto the reviewer roster. - deriveReviewerSlugs no longer reads runtime.hostBehaviors.reviewerCli - the key is stripped from the six manifests that carried it; each already declares a reviewer body whose slug equals its capability id, so the derived roster is unchanged at the same twelve slugs - collectReviewerWarnings emits a presence-based, non-fatal removal notice for any manifest still declaring the key, reaching both the build-time registry generation and the third-party overlay load path. The check runs before the reviewer-body early-return, because the manifest it exists for is the alias-only one that has no body. - hostBehaviors stays an open, unvalidated bag for its other 59 keys; this adds one keyed removal notice, not general validation Refs #2801 * refactor(#2801): give the reviewer-warning channel a typed IR Review finding: the new tests asserted with String#includes() on the warning prose, which CONTRIBUTING.md's 'Prohibited: Raw Text Matching on Test Outputs' bans in favor of a typed intermediate representation. Adds the IR beside the renderer rather than replacing it, which is the shape that section prescribes and bin/verify-reapply-patches.cjs already models: - REVIEWER_WARNING, a frozen code enum - REMOVED_REVIEWER_CLI_FIELD, so the emitting site and its test share one symbol instead of duplicating a literal - collectReviewerWarningRecords(cap), returning typed records collectReviewerWarnings(cap) keeps its exact string[] contract as a thin map over the records, so both production consumers are untouched. Every section-K row now asserts on record.code/field/capId and none on the rendered message. Locks the code surface, asserts the renderer stays one-to-one with the records, and migrates the pre-existing Phase 2 test on the same channel off prose matching. Refs #2801 * test(#2801): invert the section F alias fall-through regression row Caught by the remote runner: 2 unique failures on both Node lanes out of 31,692. tests/reviewer-lane-declarations.test.cjs section F — Phase 5a's isolated-security-review regressions — asserted that a blank reviewer.slug falls through to the hostBehaviors.reviewerCli alias rather than dropping the lane. That is the direct inverse of this phase's contract. The original rationale held only while the alias existed. With it gone there is nothing to fall through to: a blank body is not a declaration, and a declaration is the only route onto the roster. Inverted rather than deleted — the row carries the adversarial-review provenance for the slug trim, and removing a security regression guard to make a change pass is backwards. The duplicate row added earlier in section C is dropped instead; section F is its canonical home. Also corrects two count strings Phase 5b left at eleven while asserting twelve, which would misreport on failure. Refs #2801 * docs(#2801): give the removed reviewerCli flag a migration path The Reference edit alone satisfied CI — a file under docs/ moved, so lint-docs-required.cjs was green — while the task-oriented quadrant said nothing about the removal. A maintainer whose lane had just gone silent would have found the field documented as removed and no page telling them what to do about it. Adds a migration section to the how-to: the symptom, the verbatim warning they will see, the before/after manifest, and the note to keep the reviewer slug equal to the capability id so existing review.default_reviewers entries and --<slug> flags survive. Refs #2801 * chore(#2801): backfill changeset pr number to 3272 * feat(#2801): close the runtime.hostBehaviors vocabulary ADR-1016 closes twelve descriptor axes and rejects an open escape hatch in the descriptor. It never mentioned runtime.hostBehaviors, and that silence was read as permission: 59 keys across 18 manifests, 39 of them set by a single capability, validated by nothing. The reference docs went further and attributed the open seam to ADR-1016, which does not mention the field at all. KNOWN_HOST_BEHAVIORS enumerates the vocabulary. An undeclared key yields a non-fatal UNKNOWN_HOST_BEHAVIOR record on the same D4.3 channel as the alias removal notice, reaching both build-time generation and overlay install. Warning, never error, for the reason this phase exists: an error would hard-break an out-of-tree descriptor carrying a bespoke key with no deprecation window, which is what reviewerCli was given a release to avoid. Escalation is a separate decision. reviewerCli is excluded from the unknown-key sweep so it keeps its own notice with the migration pointer rather than drawing two records. A parity test binds the vocabulary to the shipped manifests in both directions, and a second asserts no shipped capability draws a notice, so the closure is provably inert in-tree. Records the decision and the miscitation as an ADR-1016 amendment. Refs #2801 * fix(#2801): bound and sanitize the unknown-key diagnostics Two findings from an isolated adversarial review of the closure commit, both proven by execution rather than asserted. MAJOR, introduced by the closure: the new Object.keys(hostBehaviors) sweep had no ceiling. An installed third-party manifest is bounded only by MANIFEST_MAX_BYTES, and an 8.69MB manifest with 800,000 keys produced 800,000 records and ~139MB of message text, retained for the registry's lifetime in OverlayMeta.diagnostics. Now capped at ten records plus a summary carrying omittedCount, mirroring capability-loader's existing slice(0,3) idiom. The same manifest now yields 11 records and 1748 chars. MINOR, newly reachable: manifest-supplied key names were interpolated raw. Unlike cap.id, which validateCapability gates on KEBAB_RE before these diagnostics run, hostBehaviors keys have no grammar check anywhere, so ANSI escapes and CRLF reached stderr and OverlayMeta.warnings intact. New describeKey replaces C0/C1 controls and clips at 80 chars. The file already had describeValue for this and applied it only to values. Both fixes land on the pre-existing reviewer.* sweep too — it carried the identical pair, and fixing only the new copy would leave the same defect one screen from its own fix. Refs #2801 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -10,6 +10,29 @@
|
||||
- **Subsumed by:** [ADR-1239](1239-gsd-embeddable-orchestration-engine.md) (GSD as an Embeddable Orchestration Engine) — read it first; see the amendment below
|
||||
- **Amended by:** [ADR-2866](2866-install-surface-resolution.md) (Install-surface resolution) — **one axis is added to this ADR's closed descriptor vocabulary: host trigger precedence.** ADR-2866 is the review this ADR's closed-vocabulary friction exists to force. The axis is *required-with-default*, so descriptors authored against today's schema keep working and [ADR-894](894-capability-declaration-format.md)'s additive-only contract holds; the registry generator and validator move with it. **Timing:** the decision is recorded and `Accepted`; the descriptor schema itself changes at epic [#2866](https://github.com/open-gsd/gsd-core/issues/2866) Phase 2 ([#2871](https://github.com/open-gsd/gsd-core/issues/2871)), not before. Nothing else in this ADR's vocabulary opens — precedence is a fact about the *host*, which is exactly why it belongs on the descriptor rather than in a per-runtime branch.
|
||||
- **Amended by:** [ADR-2782](2782-reviewer-lane-capability-surface.md) (Reviewer Lane capability surface) — a `role: "runtime"` capability may now carry a `reviewer` body **alongside** its runtime body. The runtime body itself remains closed and unchanged, and no feature-only field becomes permissible on it. ADR-2782 D6 **upholds** this ADR's closed-vocabulary principle: the lane's `handler` is a closed enum of first-party names (the `ConverterName` construction of Decision 3), never an open escape hatch, so §Alternatives #2 stands unreversed.
|
||||
- **Amended by:** [#2801](https://github.com/open-gsd/gsd-core/issues/2801) (closes `hostBehaviors`) — the one hole in this ADR's closure is closed; see the amendment below.
|
||||
|
||||
## Amendment (2026-08-09): `hostBehaviors` is closed (#2801)
|
||||
|
||||
This ADR closed twelve axes and rejected "an open escape hatch in the descriptor" (§Alternatives #2). It never mentioned `runtime.hostBehaviors`, and that silence was read as permission: the field accumulated **59 keys across 18 manifests — 39 of them set by a single capability** — validated by nothing. `docs/reference/capability-manifest.md` went further and described it as *"the deliberate open seam"* sanctioned by this ADR. That attribution was never true.
|
||||
|
||||
**Decision.** `hostBehaviors` is a closed vocabulary, enumerated in `KNOWN_HOST_BEHAVIORS` (`gsd-core/bin/lib/capability-validator.cjs`). A key outside it is **ignored with a non-fatal warning**, surfaced on both paths a manifest arrives through — build-time registry generation to stderr, and overlay install through `OverlayMeta.warnings`.
|
||||
|
||||
**Why warning and not error.** Two reasons, and the second is the load-bearing one:
|
||||
|
||||
1. It matches the [ADR-2782](2782-reviewer-lane-capability-surface.md) D4.3 treatment of an unknown `reviewer` field, so one manifest surface does not contradict its neighbor.
|
||||
2. An error would hard-break any out-of-tree runtime descriptor carrying a bespoke key, with **no deprecation window** — the exact failure mode the change that closed this (#2801, ADR-2782 D9) spent a full release avoiding for `reviewerCli`. Escalating to an error is a separate decision and needs its own window.
|
||||
|
||||
So this closure is real but soft: the vocabulary is enumerated, drift is visible, and adding a key is deliberate — while nothing installed today breaks.
|
||||
|
||||
**Consequences.**
|
||||
|
||||
- Adding a host behavior is now a reviewed change: the key must be declared in the vocabulary. That is this ADR's intended friction (§Consequences, "the closed vocabulary must grow (reviewed) … intentional friction, the trust boundary"), now applied to the surface that was escaping it.
|
||||
- A parity test asserts the vocabulary equals the set of keys the shipped manifests declare, in **both** directions — an undeclared key warns on every build, and a key left behind after its last manifest drops it is dead vocabulary. Neither can rot silently.
|
||||
- Zero shipped capability draws a warning at the time of closure; the change is inert for everything in-tree, and a test asserts that too.
|
||||
- Third-party descriptors carrying bespoke keys now see a warning where they previously saw silence. That is the intended signal, not a regression — but it is the reason enforcement stops at warning.
|
||||
|
||||
**Not decided here.** Whether the 39 single-use keys should be consolidated, promoted to real axes, or retired. Closing the vocabulary makes that question answerable; it does not answer it.
|
||||
|
||||
## Amendment (2026-07-16): subsumed by ADR-1239 (EoS) — this ADR is the *declarative adapter*, not the whole architecture
|
||||
|
||||
|
||||
@@ -219,6 +219,46 @@ For the reasoning behind consent-plus-integrity rather than a sandbox, see [The
|
||||
|
||||
---
|
||||
|
||||
## Migrate off the removed `reviewerCli` flag
|
||||
|
||||
Before 1.9.0, a runtime capability declared itself a reviewer with a boolean in the open host-behaviors bag:
|
||||
|
||||
```json
|
||||
"runtime": { "hostBehaviors": { "reviewerCli": true } }
|
||||
```
|
||||
|
||||
That flag carried no invocation data — it only added the capability id to the roster, leaving the probe, argv shape, timeout, and output policy hardcoded in GSD core. It was superseded by the `reviewer` body in 1.9.0, kept working for one release as a derived alias, and **was removed in the release after that**.
|
||||
|
||||
**Symptom.** Your capability installs and validates exactly as before, but `/gsd-review` no longer offers your flag and your lane never runs. On a registry build or a capability install you will see:
|
||||
|
||||
```
|
||||
⚠ capability "your-cap" runtime.hostBehaviors.reviewerCli was removed (ADR-2782 D9)
|
||||
— ignored, and it contributes no reviewer lane. Declare a `reviewer` body instead;
|
||||
see docs/how-to/ship-a-reviewer-lane.md
|
||||
```
|
||||
|
||||
**Fix.** Delete the flag and declare a `reviewer` body, following [Declare a spawned-CLI lane](#declare-a-spawned-cli-lane) above. Your `reviewer.slug` should be whatever the flag used to contribute — your **capability id** — so existing `review.default_reviewers` entries and `--<slug>` flags keep working:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "your-cap",
|
||||
"role": "runtime",
|
||||
"runtime": { "hostBehaviors": { } },
|
||||
"reviewer": {
|
||||
"slug": "your-cap",
|
||||
"flags": ["--your-cap"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then rebuild and re-verify with the steps in [Build and install](#build-and-install) and [Verify the lane resolves](#verify-the-lane-resolves).
|
||||
|
||||
Two things the migration buys you beyond restoring the lane: your invocation shape becomes declared data rather than something GSD core has to know about, and your lane can own its own config keys (see [Own your lane's config keys](#own-your-lanes-config-keys)).
|
||||
|
||||
> **Nothing else about your capability changes.** A manifest still carrying the removed key parses, validates, and installs exactly as before — it simply contributes no lane, and says so. The key is inert, not fatal.
|
||||
|
||||
---
|
||||
|
||||
## Conditionals: when the vocabulary does not fit your tool
|
||||
|
||||
Third-party lanes are **data-only**. `handler` is a closed enum of first-party names (`antigravity`, `openai-compatible`, `opencode`, or `null`) — you may reference an existing member, but you cannot ship your own handler module.
|
||||
@@ -238,6 +278,7 @@ Filing the issue is the supported route, not a workaround. The `openai-http` tra
|
||||
## Related
|
||||
|
||||
- [Capability manifest](../reference/capability-manifest.md) — the full `reviewer` body field table and validation rules
|
||||
- [Capability manifest → `hostBehaviors`](../reference/capability-manifest.md#hostbehaviors) — why the bag is unvalidated, and the one removal notice inside it
|
||||
- [Set up cross-AI review](set-up-cross-ai-review.md) — the user-facing side: choosing, configuring, and running reviewers
|
||||
- [Develop a Capability for GSD 1.5+](develop-a-capability.md) — manifests, registry generation, and federated config
|
||||
- [Publish a capability](publish-a-capability.md) — versioning, `engines.gsd`, and distribution
|
||||
|
||||
@@ -161,24 +161,31 @@ Runtime capabilities describe how GSD projects its artefacts onto one host CLI.
|
||||
|
||||
### `hostBehaviors`
|
||||
|
||||
`runtime.hostBehaviors` is an **open, unvalidated bag** of per-host behavior switches consumed directly by installer and runtime-adaptation code. Unlike every axis in the table above, it is **not covered by any schema**: the key `hostBehaviors` appears zero times in `scripts/gen-capability-registry.cjs` and zero times in `scripts/registry-schema.cjs`. An unknown key inside `hostBehaviors` is neither rejected nor warned about — it is simply ignored by any code path that does not look for it by name.
|
||||
`runtime.hostBehaviors` is a **closed vocabulary** of per-host behavior switches consumed directly by installer and runtime-adaptation code. A key outside the vocabulary is **ignored, with a non-fatal warning** naming the capability and the key; it is never a validation error, so a manifest authored against a newer GSD degrades visibly instead of failing the build of a repo that merely reads it.
|
||||
|
||||
58 distinct keys are declared across the shipped runtime manifests; most are set by exactly one capability. This table is not exhaustive — it lists the keys with the widest reuse so a reader can pattern-match new ones against the same shape:
|
||||
Adding a key is a reviewed first-party change, which is [ADR-1016](../adr/1016-runtime-capability-descriptor.md)'s intended friction rather than an obstacle: the runtime descriptor expresses every per-host difference as a value over a closed vocabulary, and a host needing a new shape gets a named primitive rather than an open escape hatch.
|
||||
|
||||
> **History.** `hostBehaviors` went unvalidated until [#2801](https://github.com/open-gsd/gsd-core/issues/2801), and this page previously described it as a deliberate open seam sanctioned by ADR-1016. That attribution was wrong — ADR-1016 does not mention `hostBehaviors` at all. See the [ADR-1016 amendment](../adr/1016-runtime-capability-descriptor.md#amendment-2026-08-09-hostbehaviors-is-closed-2801).
|
||||
|
||||
The vocabulary holds 59 keys; 39 of them are set by exactly one capability. This table is not exhaustive — it lists the keys with the widest reuse so a reader can pattern-match new ones against the same shape:
|
||||
|
||||
| Key | Capabilities declaring it |
|
||||
|---|---|
|
||||
| `reapplyCommand` | 9 |
|
||||
| `skipSharedHooksInstall` | 8 |
|
||||
| `reviewerCli` | 6 |
|
||||
| `frontmatterDialect` | 5 |
|
||||
| `hyphenNameAgentBody` | 3 |
|
||||
| `legacyCommandsGsdInstallMigration` | 3 |
|
||||
| `legacyCommandsGsdUninstall` | 3 |
|
||||
| `nativePlugin` | 3 |
|
||||
| `skipUpdateBannerCommand` | 3 |
|
||||
| `verificationStyle` | 3 |
|
||||
|
||||
**`reviewerCli` is deprecated.** It is a boolean that historically marked a runtime capability as also being a reviewer lane. It is now a **derived legacy alias**, retained for one release so an out-of-tree runtime descriptor that still sets it keeps working. A declared `reviewer` body (see below) takes precedence over the alias, and a capability declaring both contributes **one** slug, not two. `reviewerCli` is superseded by the `reviewer` body; its removal is tracked by issue #2801. It is currently set by 6 capabilities: `antigravity`, `claude`, `codex`, `cursor`, `opencode`, `qwen`.
|
||||
**`reviewerCli` has been removed.** It was a boolean that marked a runtime capability as also being a reviewer lane. [ADR-2782](../adr/2782-reviewer-lane-capability-surface.md) replaced it with the [`reviewer` body](#reviewer-body-role-reviewer-or-on-any-role); it survived one release (1.9.0 → 1.10.0) as a derived legacy alias and was deleted in Phase 7 ([#2801](https://github.com/open-gsd/gsd-core/issues/2801)). No shipped capability declares it.
|
||||
|
||||
See [ADR-1016](../adr/1016-runtime-capability-descriptor.md) (the runtime body is a closed 8-axis plus 4 install-surface vocabulary; `hostBehaviors` is the deliberate open seam beside it) and [ADR-2782](../adr/2782-reviewer-lane-capability-surface.md) (introduces the `reviewer` body and the `reviewerCli` alias's deprecation).
|
||||
**If your out-of-tree manifest still sets it:** nothing crashes and nothing else about your capability changes — it simply contributes no reviewer lane, and the registry reports a non-fatal warning naming the capability. The warning reaches you at build time on stderr, and at install time through the overlay loader's diagnostics. To restore the lane, declare a `reviewer` body; [Ship a reviewer lane in your capability](../how-to/ship-a-reviewer-lane.md) is the migration path, and the field reference is below.
|
||||
|
||||
See [ADR-1016](../adr/1016-runtime-capability-descriptor.md) (the runtime descriptor is a closed vocabulary; its 2026-08-09 amendment closes `hostBehaviors` too) and [ADR-2782](../adr/2782-reviewer-lane-capability-surface.md) (introduces the `reviewer` body, and D9 retires the `reviewerCli` alias).
|
||||
|
||||
For a minimal `role: "runtime"` example, see [ADR-1016 §Decision 8](../adr/1016-runtime-capability-descriptor.md).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user