feat(architecture): [EoS/claude] drive claude through the imperative adapter + descriptor-driven hostBehaviors (#2086)
Fold Claude Code's install/uninstall onto the Embeddable Orchestration System
(ADR-1239 Phase D). claude is GSD's tier-1 reference host, but its install path
was still driven by 13 hardcoded `runtime === 'claude'` string-equality branches
scattered across bin/install.js rather than the public Host-Integration Interface.
- Route install()/uninstall() through `createImperativeAdapter({runtime})` — the
adapter delegates to the SAME installRuntimeArtifacts/uninstallRuntimeArtifacts
engine calls, so output is byte-identical (proven pre/post, both scopes).
- Replace all 13 `runtime === 'claude'` / `runtime !== 'claude'` branches with
descriptor-driven `runtime.hostBehaviors` lookups on capabilities/claude/
capability.json (attributionSource, authorsCanonicalWorkflow, localInstallStyle,
permissionsSchema, settingsFileByScope, sourceMarkerFile, agentFrontmatterExtensions,
ownsClaudePaths, nativeModelAliases, skillsGlobalOnboarding). Behavior is
identical; the brittle string-equality coupling (the add-a-host tax) is gone.
- Single-source the scattered literal 'claude' defaults/rosters behind DEFAULT_RUNTIME.
- Extend golden-install-parity to assert the claude LOCAL legacy layout is
byte-identical too (AC1 "both scopes"); exclude the platform-varying
settings.local.json (same reason settings.json is excluded).
- New tests/claude-imperative-reference.test.cjs: adapter kind, programmatic-cli
profile, fail-closed negotiation on a corrupted/partial descriptor, and an AC2
source guard that no `runtime === 'claude'` branch remains.
No user-visible install-output change (internal architecture only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,51 @@ a first-party primitive, reviewed. To add one (e.g. a new `runtime` kind):
|
||||
The parity guard (`tests/host-integration-validator-parity.test.cjs`) fails if these two drift, so
|
||||
they must be updated together. Document the new value's meaning in the matrix legend.
|
||||
|
||||
## 7. Fold an already-hardcoded host into the interface (worked example: `claude`)
|
||||
|
||||
Sections 1–6 cover a *green-field* host (`pi`, `antigravity` — a fresh descriptor + reference
|
||||
binding). This section covers the other case: a host that already has a **real production install**
|
||||
driven by scattered `runtime === '<id>'` string-equality branches in `bin/install.js`, which you want
|
||||
to move onto the Host-Integration Interface **without changing a single installed byte**. `claude`
|
||||
(the tier-1 reference host, #2086) is the worked example.
|
||||
|
||||
The pattern is byte-parity-safe by construction — each string check becomes a **descriptor lookup that
|
||||
yields the same truth value**, so behavior is unchanged and only the brittle coupling is removed:
|
||||
|
||||
1. **Inventory the branches.** Find every `runtime === '<id>'` / `runtime !== '<id>'` in `bin/install.js`
|
||||
for the host (`grep -nE "runtime\s*[!=]==\s*'claude'"`). Each is a host behavior encoded as a string
|
||||
comparison rather than a declared capability.
|
||||
|
||||
2. **Declare the behaviors on the descriptor.** Add a `runtime.hostBehaviors` object to the host's
|
||||
`capability.json`. Each key names one behavior the branches gated on — e.g. for `claude`:
|
||||
`permissionsSchema: "claude"`, `settingsFileByScope: { local: "settings.local.json", global: "settings.json" }`,
|
||||
`sourceMarkerFile: ".gsd-source"`, `agentFrontmatterExtensions: ["effort"]`, `localInstallStyle: "legacy-flat"`,
|
||||
`authorsCanonicalWorkflow: true`, `ownsClaudePaths: true`, `nativeModelAliases: true`,
|
||||
`skillsGlobalOnboarding: true`, `attributionSource: "settings-json-commit"`. The validator
|
||||
(`validateRuntimeBody`) is lenient toward these host-behavior keys; they carry install policy, not the
|
||||
closed negotiated axes.
|
||||
|
||||
3. **Replace each branch with a descriptor read.** `bin/install.js` exposes a `_hostBehaviors(runtime)`
|
||||
helper (reads `_capabilityRegistry.runtimes[runtime].runtime.hostBehaviors`, `{}` if absent). Rewrite
|
||||
`if (runtime === 'claude')` → `if (_hostBehaviors(runtime).permissionsSchema === 'claude')`, and
|
||||
`if (runtime !== 'claude')` → `if (!_hostBehaviors(runtime).authorsCanonicalWorkflow)`. Only the host
|
||||
declares the key, so every other runtime keeps the generic path.
|
||||
|
||||
4. **Route install/uninstall through the public adapter.** Replace the direct
|
||||
`installRuntimeArtifacts(...)` / `uninstallRuntimeArtifacts(...)` calls with
|
||||
`createImperativeAdapter({ runtime }).install({...})` / `.uninstall({...})`. The imperative adapter
|
||||
delegates to the *same* engine functions, so the output is byte-identical — that is the point: the
|
||||
host is now driven **through** the interface, not around it.
|
||||
|
||||
5. **Prove parity, both scopes.** `tests/golden-install-parity.test.cjs` captures a byte-stable manifest
|
||||
of every emitted file. Assert the host's install is unchanged for **global and local** scopes
|
||||
(regenerate a baseline from `origin/next` first, then confirm the migrated tree matches it). Exclude
|
||||
only genuinely volatile / platform-varying files (`settings.json`, `settings.local.json`, `.gsd-source`).
|
||||
|
||||
6. **Guard against regression.** Add a `*-imperative-reference.test.cjs` asserting the adapter classifies
|
||||
the host correctly, negotiation fails closed on a corrupted descriptor, and — with a source-grep behind
|
||||
an `// allow-test-rule:` exemption — that **no `runtime === '<id>'` branch remains** in `bin/install.js`.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
Reference in New Issue
Block a user