Conversation
Capabilities system: gap analysisThe capabilities system solves the coarse-grained problem well (which API namespace — v1/v2/v3 — to use for a given operation), but there's a significant gap in sub-operation capability modeling. What works
What doesn't work yet1. The generated manifest is inert at runtime.
2. Feature is ignored in resolution logic.
3. No sub-operation capability modeling. There's no way to express "this operation supports parameter X starting from component version Y.Z.0" within the same API namespace. Today this is handled with ad-hoc conditionals scattered across handlers: // list_transactions.go — manual handler filtering
if (input.StartTime != nil || input.EndTime != nil) && handler.APIVersion != "v1" {
continue
}
// revert v1 handler — manual error
if input.AtEffectiveDate {
return ..., fmt.Errorf("--at-effective-date requires ledger API v2+")
}This pattern doesn't scale. Each new conditional parameter or field requires a hand-coded Concrete scenarios that break
SuggestionThe structural pieces are there (
|
Follow-up:
|
Long-term concern: CLI bloat from accumulated version handlersOne more thing to flag — with this architecture, every new API version adds a new handler per operation, but old handlers are never removed (they're needed for backward compatibility with older stacks). Over time this means:
This is the natural consequence of "one CLI binary supports all stack versions," but it's worth deciding upfront what the support window is. Without a policy like "fctl v4 supports stack versions N and N-1 only," the handler/mapping/test accumulation is unbounded. Something to keep in mind as the v4 architecture solidifies — the current design makes it easy to add version support, but has no mechanism to deprecate or remove it. |
Comparison with #126 (plugin architecture) — focused on versioning strategySetting aside authentication and rendering (both can be solved the same way in either approach — plugins can call core rendering methods, and the profile/context model from this PR can be reused), the key differentiator is how product code is organized and how it evolves as API versions change. Side-by-side
The core issueThe fundamental problem with the monolithic v4 is that complexity is O(products × API versions) in a single binary, and it only grows. Every line in The plugin approach transforms this into O(1) per binary — each plugin knows exactly one API version, so there's a single code path, zero branching, zero feature-gating. Multi-version complexity moves from compilation (code) to deployment (registry), where it's easier to manage. For reference, Install friction — the only real argument against pluginsThe process/gRPC overhead is real but technical and optimizable. The actual question is user experience:
This is real but one-time friction (at install, not per command), and it's the price for not accumulating multi-version code indefinitely in a monolith. The three issues identified earlier — how each approach handles them1. Capabilities / intra-version feature gaps: In #153, the capabilities system doesn't model sub-operation differences (new query params, fields added in minor versions). Each difference requires a hand-coded 2. 3. Long-term binary bloat: In #153, handlers/mappers/tests accumulate with no removal mechanism. In #126, each plugin is an independent binary that can be replaced or retired without touching the core. Bottom lineOnce auth and rendering are neutralized, the question is: where does multi-version API complexity live?
The plugin architecture structurally resolves the three problems identified in this review without requiring additional mechanisms — it's a natural side effect of per-binary isolation. |
Proposes a plugin-based architecture for fctl where product CLI commands ship as independent binaries from product repos. Uses Ledger v3 as the concrete proof of concept with detailed user journeys covering Cloud, self-hosted, local dev, debugging, error handling, and version retirement. Key design decisions: - Plugins live in product repos at cmd/fctl-plugin/ - Plugin version = service version (enforced) - Registry derives binary URLs from convention - Built-in commands remain for Ledger v2, plugin activates for v3+ - Resolution: plugin first, built-in fallback, auto-discovery last - Rendering centralized in fctl core via display schemas - Plugin lifecycle fully driven by auto-discovery, no manual updates Relates to #126 and #153. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Draft PR: fctl v4 isolated implementation
Scope
This PR intentionally introduces the fctl v4 rewrite in an isolated top-level
v4/directory. It does not cut over the repository root yet. The goal is to make the v4 architecture, command mapping, compatibility aliases, tests, and migration documentation reviewable before the disruptive root replacement happens.Important Review Note
Review this PR as the isolated v4 implementation and migration plan, not as the final cutover. The current v3 implementation remains available at the repository root so reviewers can compare old and new behavior side by side.
The follow-up PR should be a dedicated cutover PR that removes/moves the root v3 implementation and promotes
v4/to the repository root once this draft is validated.What This PR Includes
v4/with Cobra kept as a thin parser/router.session login/status/token/logout;authis reserved for Auth service resources./versions-based API version resolution with compatibility manifests.v4/testdata/v3-command-inventory.json.v4/todos/so the root stays focused on the current v3 implementation until cutover.Explicitly Not In This PR
session login cloudandsession login oidcare visible but explicitly deferred until the device/browser flow contracts are defined.loginandauth login/status/token/logoutare intentionally not kept as aliases, to avoid mixing CLI session state with the Auth service.cloud personal-tokens createis documented as blocked by the current Cloud stack/token exchange model.--telemetryand--quietare intentionally not exposed as no-op flags.httptestservers.Follow-Up Cutover PR
After this PR is reviewed, create a separate cutover PR that:
v4/;go.mod, localreplacedirectives, CI, packaging, and release config;go test ./...;Verification
Latest local checks run from this branch:
Previous coverage check from
go test ./... -cover:cmd: 75.9%internal/credentials: 83.0%internal/capabilities: 75.4%internal/auth: 72.9%internal/runtime: 72.0%internal/commands/target: 76.2%Known lower package-level coverage remains documented in the audit because several product behaviors are currently covered through CLI integration tests in
v4/cmd/root_test.go.Key Docs
plan.mddocs/cli-v4/migration-v3-v4.mddocs/cli-v4/command-reference.mddocs/cli-v4/compatibility-aliases.mddocs/cli-v4/testing-strategy.mddocs/cli-v4/cutover-plan.mddocs/cli-v4/implementation-audit.md