Conversation
Preparation tranche for the fctl Flows plugin (fctl-v2 programme Task 10B). Contains no plugin: no runtime, component entry point, HTTP client, generated bindings or ABI. It delivers the one artefact that does not depend on the fctl-v2 MVP4 contract freeze — a versioned, test-pinned inventory of the orchestration operation surface and its mapping onto the legacy fctl command baseline. Derived from openapi.yaml at this commit, cross-checked against pkg/client: 35 operations (17 orchestration.v1, 18 orchestration.v2), 35 unique operationIds, none deprecated. All 16 executable legacy fctl `orchestration` commands at fctl 693c58e2 map onto a current operation — there is no exclusion to justify — reaching 17 distinct operations. The baseline is v1 throughout except `orchestration triggers test`, which calls V2.TestTrigger because v1 declares no trigger-test operation. Records all 35 operations as blocked, separated from the verified facts: - B1: the generated pkg/client does not build and cannot be imported. Its go.mod declares `module openapi` while 94 of its 221 Go files import `openapi/...` and formance.go imports `github.com/formancehq/flows/pkg/client/...`; its go.sum holds three `/go.mod` hashes and no module-content hash. Task 10B requires routing every operation through this client, so nothing is admissible. Not repaired here: the client is regenerated by `just generate-client`, so its path is a generation-configuration decision between the root module and VCS namespaces, not a hand edit. - B2: seven collection reads have no declared way to bound them; the three v1 listings emit no SQL LIMIT and the four history reads return the whole array. - B3: v1 GET /triggers returns at most 15 triggers, computes the next-page cursor and then drops it, while declaring no pagination parameter at all — a wrong answer, not an error. - B4: runWorkflow/v2RunWorkflow with wait=true block until the Temporal workflow terminates, with no server-side deadline. Plus eight spec-versus-server divergences, including: /_info answers unauthenticated though declared under orchestration:read; GET /v2/_info is declared and generated but not routed, so the version probe is only ever available unprefixed; scope checking is method-derived and off by default; `testTrigger` is the only v2 operationId without a v2 prefix; and `cancelEvent` aborts an instance rather than cancelling an event. This module declares github.com/formancehq/orchestration/plugins/fctl — a subdirectory of the authoritative root module path — and imports nothing from this repository. Determinism: `just fctl-audit` regenerates the committed report and tables, `just fctl-audit-check` gates them, and invariant tests pin every count quoted in prose. Wired into tidy, lint, tests and pre-commit. No Task 10B acceptance box is ticked. Remaining gates are listed in plugins/fctl/docs/command-inventory.md §9.
…ted pins The lock still pinned the SDK to `Dav-14/fctl-v2-poc` at `545521bf`. The authoritative remote is now `formancehq/fctl-v2-poc` and the programme's current fctl revision is `e9b1395f`, so the pin was stale in both fields. Repins `fctl-sdk.lock.json` to `e9b1395f46f3100b381dbe00f5213de28e6df0e1` of `https://github.com/formancehq/fctl-v2-poc.git` with the SDK content hash `sha256-DnTiEFya3R9KCYmgv5SO/1StKTCPmndObQrrVHf79Xk=`. The WIT hash is unchanged: the lifecycle interface is byte-identical across the two revisions, so the vendored `wit/plugin.wit` needs no change. `pkg/plugin/go.mod` and `go.sum` are also unchanged between the revisions, so the plugin module metadata stays tidy. `nix/fctl-component-tools.nix` bumps its provenance label only. Every definition it copies from the fctl authoring toolchain — Rust 1.91.1, wasm-tools 1.239.0, componentize-go 0.4.1, wasi-virt 448f6df8 with all four source and cargo hashes, and the closed-terminal stdio patch byte-for-byte — is identical at `e9b1395f`. The 37 lines fctl added to `nix/portable-go-probe.nix` between the revisions are the jco TypeScript toolchain, which this repository does not copy. Closes two gaps the repin exposed: - Five tracked files restated part of the lock (the Nix tool pin, four fixtures in the wrapper contract test, and the revision in both documents) with nothing gating their agreement, so a repin could silently leave a superseded revision, remote or hash behind. `scripts/fctl_sdk_lock_pins_test.go` now anchors each restatement to exactly one value and requires it to equal the lock. Each of the five assertions was verified to fail when its file drifts. It also re-derives the vendored WIT hash from the lock without needing an SDK checkout. - `producthttp` at `e9b1395f` classifies a non-2xx product response as the typed `product_http_error` with `httpStatus` details and 5xx retryability instead of the opaque `product_response_failed`. No Flows test covered that boundary. `TestProductHTTPErrorsReachTheHostTyped` pins the code, the status and the retryability verdict for 404, 409 and 503; it fails against the superseded SDK, which proves it pins the new contract rather than restating whatever the bridge happens to do. Documents both facts in the plugin README and records the typed failure in the inventory's implemented-surface list. Verified against a checkout of `e9b1395f`: the wrapper and tidy contract tests, `tidy --check`, the three build-gate self-tests, `go vet`, `go test -race ./...`, the `fctl_component_guest` entrypoint race suite, the 81.6% coverage gate and `specaudit -check`. Nix was not run in this wave, so `nix develop -c just precommit`, `just build-component` and the lint recipe remain unverified; the SDK content hash was computed by an independent NAR serialiser validated against the previously recorded hash for `545521bf`. No Task 10B acceptance box is ticked.
…emote The pin gate added with the repin anchored one value per site, which left two ways for a superseded value to survive. The README restated the SDK remote with no anchor at all, so reverting it to the retired `Dav-14` URL kept the suite green. And because the exactly-one rule constrains matches of the anchor pattern, a superseded revision written in any other phrasing — prose, a comment, a "previously pinned" note — was never looked at. The README and the commit message both claimed otherwise. Anchor the README's remote, and add TestNoSupersededSDKValueSurvivesInATrackedFile as the anchors' complement: it walks every SDK-shaped value in the four restating files and in the lock itself — 40-hex revisions, the canonical WIT hash, SRI content hashes, the SDK remote owner and the SDK module path — and fails on any that the lock does not pin, wherever and however it is written. The handful of unrelated revisions and hashes those files legitimately carry (the negative fixtures, the WASI-Virt upstream revision and cargo hashes, this repository's origin/main and the legacy fctl baseline) are listed with their reason, so adding one is a decision rather than an oversight. Anchors now tolerate any whitespace between their literal words, so reflowing a paragraph or respacing a table no longer fails with a message that reads like a stale pin. Mutation-verified on every vector: each of the eight anchored sites drifted in turn fails; a superseded revision, remote owner or module path appended as prose to either document fails; a superseded content hash appended as a Nix comment fails; a wrong WIT hash appended to the contract script fails; and both a README reflow and an inventory table respacing with correct pins stay green. The README's gate sentence is rewritten to say exactly what the two tests do. It also records what the lock does not say: the pinned revision is reachable from the SDK's codex/mvp5-integration branch, not its default branch, and the lock carries no reason field. The inventory records the same, plus the fact that nothing gates the copied Nix toolchain against the SDK at the locked revision — the equality was checked by hand and can drift silently.
… boundary
TestProductHTTPErrorsReachTheHostTyped did not test what its name asserted. It
called the adapter in process and read its returned sdk.Failure, which is not
what a host sees. Flows' only entrypoint is the portable component, and the
pinned SDK's terminal frame carries a failure code and nothing else: the
component rebuilds the wire failure as FailureFromSDK(sdk.Failure{Code: ...}),
so the message, the httpStatus details and the retryable verdict are discarded
by construction. The test could not have detected a boundary that drops them,
because the boundary already does, and the README promised a host-visible HTTP
status and retryability no host can observe.
Rename the adapter test to TestProductHTTPErrorsSurfaceTypedFromTheAdapter and
say in its comment that its status and retryability assertions are adapter-local.
Its value is unchanged: it still forward-pins the typed classification the older
SDK did not have.
Add component.TestOnlyTheTypedFailureCodeCrossesThePortableHostBoundary, which
drives the real portable lifecycle over the protocol — start, host request,
product response, termination — for 404, 409 and 503, and asserts what the wire
termination carries: the code is product_http_error, and message, details and
retryable are empty. The second half is deliberate: it pins today's narrowing,
so a future SDK frame that widens the boundary fails here and forces the README
to be corrected rather than silently becoming true.
Mutation-verified: inverting either assertion fails all three statuses.
The README now states the real end-to-end guarantee — a host sees
product_http_error where it used to see the opaque product_response_failed, and
cannot build a retry policy on the status or the retryability verdict. The
protobuf dependency becomes direct because the new test encodes wire envelopes.
The catalogue declared no RenderHints, so a host had nothing but the raw JSON to lay out. Declare one compact, ordered table per command — but only where real result evidence supports a truthful column. Eight commands get one. Trigger reads carry ID, Name, Event, Workflow ID and Created At; occurrences carry Date, Trigger ID and Instance ID; workflow reads carry ID, Created At and Updated At; the instance listing carries ID, Workflow ID, Created At, Updated At and Terminated. The other eight get none, and nothing is invented for them. The four no-content mutations emit the canonical empty result. `triggers test`, `workflows run`, `instances show` and `instances describe` emit only nested objects or arrays — the composite presentation reads pair two whole resources, and the trigger-test result is a filter verdict object and a variables map — so no stable flat column can name anything in them. Their exact IDs and reasons are recorded in the tests and in both documents. Tests came first and assert the exact ordered headers and fields, then prove them against reality: TestTableRenderHintsDescribeActualResultFields executes all 16 commands against generated-client fixtures and checks that every column names a flat field the emitted result actually carries, that a command without a hint emits no flat field at all, and that every flat field a result does emit is either a column or an exclusion with a recorded reason — so a new product field forces a decision instead of disappearing. Nested, unbounded and low-signal fields are excluded deliberately: trigger `vars`, `filter` and `version`, the occurrence and instance `error` reasons, the instance `terminatedAt` and the workflow `config`. No sensitive field is exposed; the inventory records that this surface declares none, and the user-authored expressions it does carry are precisely what the exclusions keep out of a default table. PublicOutputSchema is untouched: it stays the exhaustive, unnarrowed result schema and stays byte-equal to RawOutputSchema, which the pinned SDK requires for an ordinary JSON result. Render hints select what is displayed, not what is contracted. No fctl renderer is modified; this repository only declares hints.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the 16-command Flows portable plugin, exact SDK pin guards, typed portable failure boundary, concrete output schemas, and dotted-path RenderHints.\n\nValidation:\n- post-origin/main plugin race suite passes\n- coverage 81.9% overall, 82.9% core\n- 11 table hints and 5 evidence-bound omissions\n\nOpen external gates:\n- product tests requiring Docker could not run locally\n- Nix gate not completed locally