Skip to content

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-bead walk-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 the authority_context_id this 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

  1. 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).
  2. 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).

  1. Doesn't change the architectural destination? Yes — A/B still proceed for mutations.
  2. Removable later with a trivial diff? Yes — one read-only-subcommand exemption in wrapper.ts.
  3. 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 — resolveAuthorityContext already 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 current resolves 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.