Skip to content

docs: RFC 0002 — Plugin architecture with Ledger v3 as POC - #154

Open
gfyrag wants to merge 3 commits into
mainfrom
rfc/plugin-architecture-ledger-v3
Open

docs: RFC 0002 — Plugin architecture with Ledger v3 as POC#154
gfyrag wants to merge 3 commits into
mainfrom
rfc/plugin-architecture-ledger-v3

Conversation

@gfyrag

@gfyrag gfyrag commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Key design decisions

  • Plugins live in product repos at cmd/fctl-plugin/ — ownership, versioning, and CI stay with the product team
  • Plugin version = service version — enforced, not a convention. Eliminates version mapping confusion
  • Registry is minimal — just compatibleWith semver ranges. Binary URLs derived from convention ({repo}/releases/download/v{version}/fctl-plugin-{name}-{os}-{arch})
  • Built-in commands stay for Ledger v2 — zero disruption for existing users. Plugin only activates for v3+
  • Resolution order: plugin first → built-in fallback → auto-discovery
  • Works at any semver granularity — minor version differences get their own plugin version, no intra-version feature-gating needed
  • No fctl plugin update — lifecycle is fully driven by auto-discovery when the service version changes
  • Rendering centralized in fctl core — plugins return structured data, fctl renders via display schemas

User journeys covered

  1. Cloud user gets the right plugin via auto-discovery
  2. Switching between Ledger v2 (built-in) and v3 (plugin) stacks transparently
  3. Self-hosted user with /versions detection
  4. Local developer building from cmd/fctl-plugin/ in the ledger repo
  5. Debugging across the plugin boundary (--debug with [fctl]/[plugin] prefixes)
  6. Error handling when plugin is missing (auto-discovery first, actionable errors in non-interactive mode)
  7. Version retirement via registry deprecation notices

Related

🤖 Generated with Claude Code

@gfyrag
gfyrag requested a review from a team May 15, 2026 12:51
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>
@gfyrag
gfyrag force-pushed the rfc/plugin-architecture-ledger-v3 branch from 61cb6ee to c2c6780 Compare May 15, 2026 12:53
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Rate limit exceeded

@gfyrag has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 9 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e577ada-06ba-4bca-98c3-8d30ea69f88d

📥 Commits

Reviewing files that changed from the base of the PR and between 2c531a2 and 11a1747.

📒 Files selected for processing (1)
  • docs/rfcs/0002-plugin-architecture-ledger-v3-poc.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc/plugin-architecture-ledger-v3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@flemzord

Copy link
Copy Markdown
Member

One point we should clarify early: using GitHub releases as the default plugin distribution mechanism works for Ledger because the repository is public, but it may not work as a general registry/distribution model for fctl plugins.

Some product repositories may be private, or may become private depending on the product / customer / deployment model. In that case, deriving plugin binary URLs from https://github.com/{repo}/releases/download/... creates a hidden assumption that the plugin artifact is publicly reachable by every fctl user.

For the Ledger v3 POC this is fine, but I think the RFC should explicitly separate:

  • the plugin registry metadata (name, repo, compatibleWith, deprecation, etc.);
  • the artifact distribution backend;
  • the authentication/authorization story for downloading private plugins.

Otherwise we may validate the POC on the easiest case, but leave the harder product cases unresolved.

Maybe the RFC can keep GitHub releases as the initial Ledger POC distribution backend, while making it clear that the registry abstraction must support non-public artifact sources later.

gfyrag and others added 2 commits May 15, 2026 15:17
Address review feedback: GitHub releases as default distribution backend
works for public repos (Ledger POC) but not for private product repos.

Registry now explicitly separates metadata (compatibleWith, deprecation)
from distribution backend (github-releases, private-releases, oci, url).
The format supports private and enterprise scenarios from the start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A lightweight Formance-hosted service (plugins.formance.cloud) serves
plugin binaries using the existing membership token. Avoids GitHub token
dependency for private repos and provides uniform auth, caching, and
server-side version control.

GitHub releases remains the default for the POC (public repo).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@laouji laouji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally speaking this looks very good.

Having the fctl plugins bundled in the repo of the module also means we can more easily wire up end to end tests that use fctl and more easily detect if changes break compatibility with fctl.

@Dav-14

Dav-14 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

ADR proposal: OCI as the sole distribution backend for fctl plugins

This RFC nails the hard parts — plugin-in-product-repo ownership, plugin version = service version (enforced), resolution order, centralized rendering. Proposal: keep all of that, and replace the distribution layer (registry.yaml + github-releases/cdn/url backends) with plain OCI. This aligns fctl with RFC-0013 "Formance Artifact Distribution over OCI", which generalizes the contract Connectivity already runs in production (RFC-0012).

Context

The RFC currently designs four distribution backends (github-releases, cdn, oci, url) plus a maintained registry.yaml for metadata. Each backend carries its own auth story, and the registry file is a second source of truth to keep in sync with releases. The open questions at the end of the RFC (YAML vs JSON registry, private-repo backends unvalidated) are symptoms of that layer.

Meanwhile RFC-0012 (Connectivity) shipped the same problem-shape on OCI primitives only: immutable semver tags as the version inventory, metadata attached as OCI 1.1 referrers, cosign signatures, mirroring via standard pull-through. RFC-0013 promotes that contract platform-wide, adds an identity-bound registry front (Cloud → Membership token, self-hosted → licence token), and makes per-module repositories viable with a single credential.

Decision (proposed)

  1. One OCI repository per module: <registry>/formancehq/<module>/fctl-plugin, tags vX.Y.Z. Since plugin version = service version is already enforced, the tag list is the version inventory. fctl plugin install ledger resolves against formancehq/ledger/fctl-plugin — nothing else.
  2. Delete registry.yaml. compatibleWith ranges and deprecation notices travel as an OCI referrer on each tag (application/vnd.formance.fctl-plugin.v1+yaml). Referrers can be attached after publication, so deprecating a version never mutates the tag. The "YAML repo vs JSON endpoint" open question dissolves: the registry is the OCI repo itself.
  3. Multi-arch via OCI image index — fctl pulls its platform; no {os}-{arch} URL convention, no checksums.txt (digests are content-addressed, cosign covers authenticity).
  4. Auth = the profile's existing identity. Auto-discovery and pull go through the identity-bound registry front from RFC-0013: a cloud profile presents its Membership token, a self-hosted profile its licence token, public plugins pull anonymously. No GitHub PAT, no new credential — the cdn backend's main selling point (membership-token UX), delivered in a standard protocol with the whole oras/cosign/mirror ecosystem for free.
  5. Publishing stays in the product's CI (unchanged from this RFC): GoReleaser builds the plugin from the release tag, pushes the index + attaches the referrer (oras attach) + signs (cosign). Same flow Connectivity uses today.
  6. Everything else in this RFC stands: resolution order (plugin → built-in → auto-discovery), user journeys, --path local-dev flow, air-gapped installs (which actually get simpler: standard OCI mirroring instead of hand-copied binaries).

Consequences

  • fctl core gains an oras-go dependency and drops the multi-backend download matrix + registry-file client.
  • Auto-discovery becomes tags/list + semver match against the service version, then referrer fetch for compatibleWith — same read path the connectivity operator already implements (shareable in go-libs).
  • The POC can start today against ghcr (anonymous, public repo) exactly as the RFC's POC scope intends; the identity-bound front lands with RFC-0013 Phase 1–2 without changing the plugin contract.
  • Version retirement = deprecation referrer (+ optional tag removal), replacing registry-entry deletion.

If this direction sounds right, I can push the corresponding amendment to docs/rfcs/0002-plugin-architecture-ledger-v3-poc.md on this branch or as a follow-up PR.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants