Skip to content

feat(fctl): add portable Wallets plugin - #128

Draft
Dav-14 wants to merge 8 commits into
mainfrom
codex/mvp5-wallets-plugin
Draft

Dav-14 wants to merge 8 commits into
mainfrom
codex/mvp5-wallets-plugin

Conversation

@Dav-14

@Dav-14 Dav-14 commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Adds the portable Wallets fctl plugin: command catalogue, generated client surface, sensitive-output contracts and RenderHints for CLI consumers.

Scope

plugins/fctl/ plus the flake and Justfile entries pinning the plugin's own toolchain.

Areas outside that boundary, called out deliberately:

  • pkg/client/ — a new Speakeasy-generated Go client the plugin imports. No published client exists for this service.
  • openapi.yaml — declares the pageSize and cursor query parameters on listBalances, which the document omitted although the endpoint paginates.
  • pkg/api/ — removed. The two test files have been restored to their main state; they are tracked below for their own PR.

No change to Wallets production code.

Validation

  • Plugin coverage 85.3% (minimum 80%); plugins/fctl/component at 100%
  • nix develop --command just pre-commit passes locally
  • just fctl-component-test passes across all six packages

Known issues

The Dirty check fails: the pinned fctl SDK is unreachable from CI

The plugin gate materialises the pinned SDK from the private
formancehq/fctl-v2-poc, and the runner carries no credential for it:

fatal: could not read Username for 'https://github.com': No such device or address
fctl SDK commit is not available from https://github.com/formancehq/fctl-v2-poc.git
error: Recipe `tidy` failed on line 15

A repository-scoped CI token cannot clone that repository, so passing a
credential is not a viable fix. The established remedy is a committed SDK
bundle, pinned by commit provenance and verified by the same SDK and WIT
content hashes — already green in formancehq/connectivity and
formancehq/ledger.

Local runs pass only because the checkout carries SSH credentials; that green
does not carry to CI.

  • Commit the verified fctl SDK bundle and make FCTL_SDK_ROOT an override, so the plugin gate runs in CI without a credential
  • Re-land the two pkg/api handler tests in their own PR. They characterize a real idempotency defect — a retry carrying the original key is rejected from current hold state before Ledger can recognize the replay — and deserve review on their own merits. Removed from this PR in 2aba8d9, recoverable from that commit.
  • Review the openapi.yaml change on its own: it declares the pageSize and cursor query parameters on listBalances, which the document omitted although the endpoint paginates. It is the generator input, so it is what produces the 80 generated pkg/client files below.

Out-of-plugin changes carried here

pkg/client/ (80 files) is generator output written by the repository's own
client generation recipe. It cannot be dropped without breaking the plugin,
which imports it.

The earlier crates.io HTTP 403 failure is resolved: the Rust authoring
toolchain no longer loads in the default dev shell.

The plugin was still pinned to `545521bf` on the personal
`Dav-14/fctl-v2-poc` fork. Move the lock, the Nix authoring-toolchain
constant, the authoring contract constant and the contract script's
expectations to `e9b1395f` on the canonical `formancehq/fctl-v2-poc`
repository, and re-derive the SDK NAR digest from that commit. The
canonical WIT digest is unchanged, so the portable lifecycle interface is
untouched by the repin.

The repin closes module-level blocker B3: `e9b1395f` makes
`producthttp.Client.readResponse` report a non-2xx product response as
`product_http_error`, carrying the numeric status and marking `5xx`
retryable, instead of collapsing every non-2xx response into
`product_response_failed`. Wallets' bounded 413 `REQUEST_TOO_LARGE` is
therefore distinguishable from an invalid response, so B3 is retired from
`audit/blockers.go` and the regenerated artefacts, and the inventory and
README record the current mapping. Module-level blockers drop from 1 to 0;
the operation-scoped blockers B1 and B2 are unchanged.

CI cannot resolve this pin until `e9b1395f` is pushed to
formancehq/fctl-v2-poc.

SDK-Lock: e9b1395f46f3100b381dbe00f5213de28e6df0e1
Two current-state contracts were unasserted after the repin.

`TestSDKPinIsIdenticalAcrossTheLockToolchainAndContractScript` fails a
partial repin: the SDK revision is stated in four independent places — the
lock the wrapper enforces, the Nix authoring toolchain, the component
package constant, and the contract script's expectations — and a plugin
pinned to two revisions at once is not reproducible. Reverting any single
one of them now fails a Go test instead of surfacing at component-build
time.

`TestExecuteSurfacesProductHTTPStatusAsABoundedFailure` pins the failure
the portable surface reports when Wallets rejects a request: code
`product_http_error`, an `httpStatus` detail carrying the numeric status,
`Retryable` only for `5xx`, no product error body in the details, and no
result event. It is red on the previous SDK pin — which reported
`product_response_failed` — and green on `e9b1395f`, so it is the
executable evidence that retired blocker B3.

The inventory now names the `core` test that pins the 413 mapping.

This branch has not been deployed

No deployments
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.

1 participant