ADR-0004 — blu-cli must consume ContractPlane as a service, not a local SDK path¶
- Status: Proposed (GOVERNANCE DECISION REQUIRED — stop before implementation)
- Date: 2026-06-29
- Related: ADR-0002 (authority baseline), ADR-0003 (logical authorities), standards/engineering-standard.md §Capability Consumption Order + §Authority Hierarchy
Context¶
Promoting the capability "Authority Plane Resolution (blu work current)" reached the source
(blu-cli) and found the blocker. Evidence (this workstation):
$ blu work current # cwd = workspace root
EXIT=1 (stdout empty)
stderr: [warn] Ownership map not found at <home>/BluTown/ledgers/BLUEFLY-TOOL-OWNERSHIP-MAP.md
contractplane-sdk unavailable at <home>/Sites/contractplane-sdk/dist/cli.js
Root cause in src/policy/wrapper.ts:
- work is registered as a mutation (BLU_COMMAND_ACTION.work = 'town_mutation'), so the whole
work group gets a policy-gate preAction hook that fires before every subcommand —
including the read-only work current.
- The gate (invokePolicyGate) shells out to a local file contractplane-sdk/dist/cli.js.
When absent it returns status 1, and the preAction hook process.exit(1)s before the resolver
runs.
Documented authority (proof — RETRIEVED from primary source)¶
The command's intended authority was verified against its own documentation before proposing any behavior change (the contract must drive the implementation, not the reverse):
src/work/authority-context.ts:14-16— "Cedar is intentionally NOT involved here. This module resolves context only — policy evaluation belongs in the Decision Plane (a separate module)."src/work/authority-context.ts:6-12— resolution order is entirely local: profile id → active bead (GT_BEAD_ID→./.gt/active-beadwalk-up →~/.gt/active-bead) → policy-bundle hash read passively from the local gt-policy audit log →state_version = sha256(inputs).src/commands/work.ts:6-9— "P0 of the governance roadmap. Downstream surfaces (decision evaluation, preflight gates, receipt emission, trace linkage) bind to theauthority_context_idthis command returns."
Finding: blu work current is documented as local context resolution with policy evaluation
explicitly excluded. It is the input downstream decision/preflight surfaces consume; it is not
itself policy-gated and is not Oracle-runtime-authoritative. Gating it behind the ContractPlane
mutation engine therefore contradicts the documented Context-Plane / Decision-Plane
separation. Exempting the read-only path restores the contract — it is an architecture
correction, not a convenience bypass.
Two layered defects¶
- Category error — read-only context resolution gated behind the mutation policy engine (contradicts the documented contract above; circular — the Authority Plane is what policy evaluation consumes).
- Workstation coupling — the gate is sourced from a local SDK clone
(
../../../contractplane-sdk/dist/cli.js), not the running ContractPlane service (violates §Capability Consumption Order).
Strategic decision matrix (defect 2 — how blu-cli sources the policy gate)¶
| Option | Advantages | Risks | Authorities affected | Future migration |
|---|---|---|---|---|
A — Service-first (consume Gate 2 social-api.agentblu.ai/api/v1/invoke by logical name; local SDK only as CONTRACTPLANE_SDK_PATH dev override) |
Single authority; no SDK/logic duplication across clients; portable (dev/CI/Oracle); matches Authority Hierarchy | Service availability + auth/network required for gated mutations | ContractPlane (service) | None — this is the destination |
B — Package-first (consume published @bluefly/contractplane-sdk from GitLab npm registry instead of a sibling clone) |
Works offline; removes path coupling without a network dependency | Gate logic duplicated/executed in every client; version skew across clients | SDK + each client CLI | Later consolidation into A |
| C — Reject / keep local clone | No change | Existing blocker persists; workstation coupling remains | None | None |
Alignment note (operator-stated): Option A matches the converging Authority Hierarchy (1Password=identity · GitLab=source · Oracle=runtime · ContractPlane=policy service · workstation=authoring). Recorded as a tradeoff, not a recommendation — the choice is the operator's.
Interim fix (defect 1) — evaluated against the three gating conditions¶
The interim Source fix: exempt the read-only work current from the mutation gate so it inspects
local state only (per its documented contract).
- Doesn't change the architectural destination? Yes — A/B still proceed for mutations.
- Removable later with a trivial diff? Yes — one read-only-subcommand exemption in
wrapper.ts. - Avoids introducing a second authority? Yes — it removes a policy call from a read-only
command; it does not reimplement ContractPlane locally (no policy evaluation is added —
resolveAuthorityContextalready only reads local state).
All three satisfied → the interim fix is contract-aligned and safe. It does not depend on the A/B/C outcome.
Decision owner¶
Operator + blu-cli maintainer. Endpoint inventory is operator-provided (RETRIEVED, not yet verified live from a session).
Consequences¶
- Removes workstation path coupling (ADR-0003);
blu work currentresolves portably. - Gated mutations gain an auth + network dependency under A; read-only resolution stays offline-capable.
- Requires a service registry as the single logical-name → endpoint resolver; its canonical location and ownership are part of this decision and are not invented here.
Status¶
Proposed. STOP — no blu-cli source change until the operator selects a matrix option and authorizes the interim fix. No code has changed; revert = discard these untracked governance files.