Repository navigation
fixture-composition fragments merged by the run, and hop-routes delivered where no module is named - #1197
Merged
Conversation
… before the local authority boots Each module states once, next to its code, what the local authority's standard fixture roles may do at its kinds (configurations/<profile>/fixture-composition.yaml in the module); a composition's own module states only what is its own. `codefly run` now merges those fragments into the workspace's fixture-composition document before anything reads the configuration, when a composed service declares the group, and `codefly generate fixture-composition --env local` does the same on its own. The merge rules are the authority's (codefly-dev/authority libraries/fixture-composition): every grant on one (organization, role, kind), installation and stand-in module has one source, and a second is refused naming both. The written file says it is generated; a hand-written one is never overwritten (generate refuses it; a run reads it as it stands and warns that no module's fragment is applied). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… as a run does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
golangci-lint v2.14.0 (`make lint`): the generated document is written 0600 like every other file the run writes, the three shadowed `err` declarations are named for what they report, and the unused cobra parameter is `_`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed gets the group empty, not nothing
A host that stands a hop on some compositions and not others declares
`hop-routes` always — saas's auth-gateway is the hop on a cell and stands none
where the composition names no module. `generatedHopRoutes` returned no group
at all for that composition, and core's reader
(GetWorkspaceDependenciesConfigurations) refuses a declared group that resolves
to nothing: the service failed at runtime-init, from the declaration, before
any of its code ran, so it could not degrade — and it could not stop declaring
the group either, since generation requires a declaring service.
The group is now delivered with an empty ROUTES. Nothing decodes it: the
gateway stands its hop only where `hop/MODULES` names modules, and
freezeDeliveredHopAddresses skips an empty value, so DecodeRouting is never
reached. The routing snapshot of an empty value equals that of no group, so no
run is told its routes changed. A composition that names modules still gets its
generated routes, and a workspace no service of which declares the group still
gets nothing.
`codefly doctor workspace` is the same seam, not a second fix: it takes
generated groups from this inventory ("never from a name exemption"), so it now
reports the group as supplied by the invocation instead of
`configuration_missing`. The baselined refusal that asserted the old failure
("no selected modules") is replaced by a test of the new behaviour rather than
kept green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
codefly-dev/authority#42 merged as bfe4876a. The pseudo-version this PR carried, e2fba9d058e1, exists only on that PR's branch: a squash-merge and branch delete would orphan it and break `go mod download` here. The library tree is byte-identical between the two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The branch-only e2fba9d058e1 is no longer required; only the merged bfe4876a is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
codefly-dev/authority#43 merged as 5178f016, adding the intra-document half of the one-statement-one-source rule: Decode refuses the same (organisation, role, kind), installation or stand-in module stated twice within ONE fragment. The bump is about the diagnostic, not correctness. Without it a duplicate inside one fragment is still refused — but at the authority's boot-time Decode of the MERGED document, which names the merged file. The developer is then told the wrong file is wrong: the generated workspace document rather than the module fragment that owns the duplicate. With this pin the merge refuses it where it is written, naming that fragment. The orphaned bfe4876a pseudo-version is dropped from go.sum: `go get` leaves it behind, and it names a ref that is no longer required. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
crisfiorini-oss
pushed a commit
to crisfiorini-oss/cli
that referenced
this pull request
Oct 27, 2026
…lies the merge (codefly-dev#1200) * fix(configurations): fixture-composition is multi-provider by design, and the composition resolves it Since codefly-dev#1197 every composed module ships a fixture-composition fragment and the run merges them. Core's single-provider rule then refuses the composition: two composed modules defining one group differently is ambiguity with no principled winner — right for every group whose value one module owns, wrong for this one, where each module is EXPECTED to carry its own part and the parts combine. The moment a composition composes a second fragment-shipping module, `codefly doctor workspace` reported required workspace configuration "fixture-composition" is ambiguous (required by authority/local): provided by composed modules "documents" and "authority"; declare it in the solution workspace to resolve the ambiguity and refused a composition a run handles happily — platform-core#117, moving the documents pin to v0.2.0, which is the hosted render's last wall. The suggested remedy is also a refusal now: `codefly generate fixture-composition` will not overwrite a hand-written document, and the local authority reads the merge, not one module's part. The group now has a provider: the shared WorkspaceConfigurationReader supplies the merge of every composed module's fragment, exactly as it already supplies hop-routes. Nothing is exempted from the single-provider rule — a group with a provider is not ambiguous — and because that reader is the one loader a run, CI, a deployment and the readiness check share, none of them can disagree about this group. A workspace that answers for itself keeps its answer, so a developer's generated file and the file a run writes before it loads any configuration both win as before, through core's own precedence. `AmbiguousWorkspaceConfigurations` drops every name the reader supplies, and the doctor does the same for the groups this invocation supplies. Both are keyed on what was supplied, not on a name, so a second generated group needs no second exemption. Tests: two modules shipping fixture-composition is ready and merges, through the reader and through the readiness check; the workspace's own document still wins; and an ordinary group two modules disagree on is still ambiguous and still refused with the same diagnostic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(configurations): merge fixture fragments before accepting module defaults * test(run): make delivery deadline diagnostics deterministic --------- Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
Two changes to one repo, one PR.
1.
fixture-compositionis merged by the platformThe local authority's
fixture-compositiondocument is merged from one fragment per composed module, never typed per playground.pkg/fixturefragments: finds each workspace module'sconfigurations/<profile>/fixture-composition.yaml(profile chain resolved by core'sconfigurations.ProfileDirectory, in the module's own directory), merges them with the authority's library (codefly-dev/authority/libraries/fixture-composition/go: one source per(org, role, kind)grant / installation / stand-in, conflicts refused naming both fragments), and writes the workspace'sconfigurations/<profile>/fixture-composition.yamlwith a generated marker and the list of sources.codefly run(newRunFlow, before the flow reads any configuration — the same seam as the generated service-secrets): writes it when a composed service declares the group. No Makefile step.codefly generate fixture-composition --env local: the same, on its own (materializes pinned modules first, like a run).generaterefuses it by name;runreads it as it stands and warns that no module's fragment is applied, which keeps the not-yet-migrated playgrounds (authority/playground, core-solutions wiki/proposals) booting.docs/cli-reference.mdregenerated.Reuse: core already delivers each composed module's
configurations/<profile>/*, and a workspace's own document already resolves an ambiguity between composed modules (composeModuleWorkspaceConfigurations). The generated file uses exactly that precedence, so core is unchanged and the authority's reader is unchanged. The merge rules are the schema owner's library, which this repo already depends on.2.
hop-routesis delivered empty where no module is namedA host that stands a hop on some compositions and not others declares
hop-routesalways — saas's auth-gateway is the hop on a cell and stands none where the composition names no module, which its ownservice.codefly.yamldocuments: "A local run composes the authority module's hop instead and states no hop/MODULES for this service."generatedHopRoutesreturned no group at all for that composition, and core'sGetWorkspaceDependenciesConfigurationsrefuses a declared group that resolves to nothing: the service failed at runtime-init, from the declaration, before any of its code ran, so it could not degrade — and it could not stop declaring the group either, since generation requires a declaring service. This blocks saas #1265.The group is now delivered with an empty
ROUTES. Verified safe:hop/MODULESnames modules, andfreezeDeliveredHopAddressesskips an empty value, sorouting.DecodeRoutingis unreachable;Why an empty value satisfies the resolver at all: core's
Manager.GetWorkspaceDependenciesConfigurationskeys group presence by name, not by content.Why the empty string, specifically.
routing.Validaterequires at least one endpoint, and authority's ownwork_test.goasserts that even{"schema":…,"endpoints":[]}is refused. There is deliberately no "no routes" document inhop-routes/v1, so the empty string is the only available spelling for "no table". If that should be hardened, the fix belongs at authority — whether the schema gains a legitimate empty document — and it is a follow-up, not this PR.The shape I considered and rejected:
OptionalWorkspaceConfigurationThe alternative is to leave
generatedHopRoutesreturning nothing and addhop-routestoenvironments.OptionalWorkspaceConfiguration, whose doc comment states this contract word for word: a service that declares such a group reads the keys it needs and treats an absent one as not configured, so every path that resolves a service's declared groups delivers nothing for one the workspace does not provide. Both hop consumers do read their group that way — saas'sstandHopreadshop/MODULESwith the tolerant accessor and returns before it mentionshop-routes, and the authority module's own hop refuses by name on the same read. On the reuse rule alone that is the better-looking fix, and I built it before rejecting it.It is rejected because that seam is two halves and only the run half is generic.
checkRequiredWorkspaceConfigurationdispatches every optional group intocheckRunnableBindingsGroup, which callsgenerate.SelectRunnableBindingsunconditionally and judges the group against the runnable bindings the environment selects. Added to that list as it stands,hop-routesgets judged about runnable bindings:status=okwithenvironment local prepares no runnable binding, so "hop-routes" holds none— green about an entirely different thing;hop-routesand selects runnable bindings,heldis empty so every selected binding key counts as missing and the doctor emits a spurious hardconfiguration_missing: "workspace configuration "hop-routes" lacks N runnable binding(s) environment local selects".Making it right means splitting that dispatch and writing a
checkHopRoutesGroup— more work than this fix and a change to a shared seamrunnable-bindingsdepends on. The two shapes are behaviourally equivalent at both known consumers (the refusal was never raised in gateway code; both make the declaration resolve), so the doctor defect is the only thing separating them.Can a composition that should name modules resolve to nothing and fail later?
No. The group is delivered only where a service declares it, and the empty table only when
hop/MODULESis empty — a documented, legitimate composition. Where modules are named nothing is weakened: an uncomposed module, an unreadable catalog or a disagreeing principal still fails the reader, whichcodefly doctor workspacereports asconfiguration_invalid("not checked"), and a new case in the refusal test proves it.What neither shape can catch is an operator who meant to name modules and left
hop/MODULESempty: to the CLI that is indistinguishable from "stand no hop". That was equally true before — the old behaviour did not catch the mistake, it crashed every composition that legitimately stands no hop.The doctor is the same seam, not a second fix
checkConfigurationSourcesalready takes generated groups from this inventory — its own comment says "never from a name exemption" — so once the group is delivered the doctor reports it as supplied by the invocation instead ofconfiguration_missing. No name list was touched.One deliberate test change.
TestDoctorWorkspaceStillRefusesInvalidHopInputscarried{"no selected modules", …, codeConfigurationMissing, "hop-routes"}, asserting that this composition must fail the doctor. That was the symptom, not a rule — the consumer repo documents the composition as legitimate — so it is replaced byTestDoctorWorkspaceAcceptsAHopThatSelectsNoModulerather than kept green. The sibling{"missing ordinary group", …}case still proves the doctor reports a genuinely missing non-generated group, so the weakening is scoped.Evidence
go test ./pkg/fixturefragments/ ./pkg/orchestration/ ./cmd/ ./cmd/run/ ./cmd/generate/green. Fragment merge of the authority's, two modules' and the playground's; conflict naming both fragments; hand-written file kept; nothing declared without a declaring service; the run hook writes before load. For hop-routes: the empty delivery for an empty and an absenthopgroup, a composition naming modules still generating routes, and a workspace declaring nothing still receiving nothing.golangci-lint run(v2.14.0,make lint) clean on the changed packages.de0deb68): the run printedmerged …/configurations/local/fixture-composition.yaml from 3 fragment(s), the authority logged the same 8 grants as before except Maya's collection read is nowid: derived\x1fdocuments-playground; Maya then lists Rémi's document in that collection (200), and every other verified call is unchanged. Robin's playground: see obin-ai/module-robin#562.Fix or hack
Both are fixes at the owner: the platform generates the fixture document from its one source per module, and the CLI delivers the group its own generation owns.
🤖 Generated with Claude Code