Repository navigation
fix(run): serve the composition's Work Context key set on loopback - #1075
antoinetoussaint-byte wants to merge 5 commits into
Conversation
A service that verifies an inbound Work Context loads its key set from an endpoint, and `workcontext.AdmitEndpoint` — the one transport rule for every key set — already admits "plaintext http to a loopback host, on a local run only". Nothing served one, so a module whose identity comes from the host could not boot on its own: `codefly run service` resolved the graph, started its dependencies, and then the service failed runtime-init with "no configuration found for work-context". A developer with only that module's repository could start no service at all. The native run now mints an Ed25519 key for itself, serves the public half as a JWKS over that admitted loopback hop, and hands the work-context group to any service that declares the dependency. The group stays AUTHORED and is never render-owned, exactly as FederationConfiguration is: a composition with a host declares it, pointing at the host's key set, and this only answers for the composition that has none. It is handed last so it settles over what an authored group leaves empty rather than displacing one. Nothing is relaxed by this. The service boots on its unchanged FromJWKS path, the KeySet invariant holds because the keys still come from an admitted endpoint with admission and fetch as one operation, and the run issues under its own issuer — so a capability minted here is structurally unacceptable to a deployed service, which pins the host's. Each run's issuer names the run, so two runs on one machine never accept each other's capabilities. The listener lives exactly as long as the run. Tests cover the shape a verifier admits (OKP/Ed25519, raw-URL base64, a 32-byte key), that the published half verifies what the signer signs, that each run issues under its own issuer, and that a run with no identity serves nothing. Refs #1074 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
A lazybox agent is working on this. claim detail |
Verification failed — this does not fix boot as written. Converting to draft.I built this branch and ran it against The same error as before the change. The key set is served, but the group never reaches the service. WhyThe group is appended in conf, ok := manager.worspaceConfigurations[dep]
if !ok {
return nil, w.NewError("no configuration found for %s", dep)
}That map is the manager's own, populated from authored workspace configuration files through its So my placement was wrong: I followed the Where it likely belongs instead
The ordering needs thought: the authority currently starts in What still stands
I would rather have this marked draft than merged on an unverified claim. Happy to move the wiring to the loader if that is the direction you want. |
…tions The first attempt appended the work-context group in renderOwnedGroups, and it never reached the service: `codefly run service record-store/records` failed exactly as before with "no configuration found for work-context". A declared workspace-configuration dependency is resolved from the manager's OWN loaded set — configurations.Manager.GetWorkspaceDependenciesConfigurations reads manager.worspaceConfigurations, which only its Loader populates. renderOwnedGroups feeds the per-service configurations handed to the runtime, which that lookup never consults; RenderOwnedWorkspaceConfiguration says as much, that render-owned groups are "never dependencies of the configuration manager". federation is an extra handed to the host, work-context is a declared dependency, and following the federation precedent put it in the wrong half. So the run's key set now layers onto the loader where WithLoader is bound, and the flow owns the authority the launcher borrows. Two constraints the decorator respects. Configurations() returns the same instances on every call, because the manager resolves secret references in those objects in place and reads them back, so fresh copies would discard what it resolved. And the local reader carries two optional capabilities -- CompositionRootWorkspaceConfigurationNames and ambiguous-group reporting -- which are forwarded; swallowing either would leave the manager unable to tell which groups come from the composition root. An authored group still wins: if the loaded set already names work-context, the decorator adds nothing. Verified against obin-ai/module-record-store: the failure moves from "no configuration found for work-context" to "no configuration found for platform", the next group that service declares. work-context resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
govet's shadow check flagged pkg/orchestration/flow.go:521: the key set's error declaration shadowed the one from NewServiceDependencies above it. Named separately so each error is the one it reads as. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolved — verified working, back out of draftThe placement was wrong and is fixed. Re-verified against
and the full composition — store, records and a Next.js frontend — runs with no host composed. What changed since the draftThe group is layered onto the configuration manager's loader, not appended beside the per-service configurations. A declared workspace-configuration dependency is resolved from Two constraints the decorator respects: The run also routes declared-but-unsupplied groups in empty, read from what the workspace's services declare. Each service then applies its own documented rule about absence rather than failing at the manager before it can. An authored group always wins, so a composition with a host is untouched. Lint
|
The previous commit staged pkg/orchestration/flow.go alone, so main carried a three-argument call to WithKeySet against a two-argument signature and the package stopped compiling: "too many arguments in call to runworkcontext.WithKeySet". Everything downstream of pkg/orchestration failed with it — lint, race, coverage, the native agent matrix, bootstrap-audit and control-integration — which is what a broken build looks like when it is reported one job at a time. The loader half is the part that routes declared-but-unsupplied groups in empty, so each service applies its own documented rule about absence instead of failing at the configuration manager before it can. Verified together this time rather than one file at a time: go build ./..., go vet over pkg/run and pkg/orchestration, and the pkg/run tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Routing a declared group in empty was unconditional, so a DEPLOYED composition that declared a group and supplied nothing would have been handed it empty instead of refusing to start. That is a misconfiguration, and the manager refusing is how it gets found; covering it would have turned a loud boot failure into a service running silently without a capability it said it needed. It is now answered only in the local environment — the same line this repository draws everywhere else, read from the environment the loader is already given rather than from a flag. The key set and the declared groups were also conjoined behind one early return: when a composition authored work-context, every OTHER declared group was skipped with it. The two are unrelated — one says where a key set is served, the other is about groups nothing supplied — and a composition that authored work-context and nothing else would have lost the rest. They are now decided separately. Tests cover both, including that an authored key set is never duplicated by the run's own, and that the composition-root capability is forwarded rather than swallowed by the decorator. Verified to discriminate: removing the environment gate fails the deployed case by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Closed: superseded by the authority interface design. The CLI no longer holds authority knowledge such as serving key sets or deriving grants. Authority lives in codefly-dev/authority, and the CLI only delivers through 🤖 Generated with Claude Code |
Fixes the boot half of #1074.
What was wrong
A service that verifies an inbound Work Context loads its key set from an endpoint.
workcontext.AdmitEndpoint— the single transport rule for every key set — already admits "plaintext http to a loopback host, on a local run only". Nothing served one.So a module whose identity comes from the host could not boot on its own. In
obin-ai/module-record-store,codefly run service record-store/playgroundresolved the dependency graph, started Postgres natively, and then:A developer with only that repository could start no service at all.
The change
The native run mints an Ed25519 key for itself, serves the public half as a JWKS over that admitted loopback hop, and hands the
work-contextgroup to any service declaring the dependency.The group stays authored and is never render-owned, exactly as
FederationConfigurationis. A composition with a host declares it, pointing at the host's key set; this answers only for the composition that has none, and is handed last so it settles over what an authored group leaves empty rather than displacing one. Making it render-owned would have forbidden authoring it and broken production composition.Nothing is relaxed
FromJWKSpath — no second verifier, noFromKeystest doorKeySetinvariant holds: keys still come from an admitted endpoint, with admission and fetch as one operationTests
pkg/run/workcontext/authority_test.gocovers the shape a verifier admits (OKP/Ed25519, raw-URL base64, a 32-byte key — a padded encoding or wrong curve fails), that the published half verifies what the signer signs, that each run issues under its own issuer, and that a run with no identity serves nothing.go build ./cmd/... ./pkg/...,go vetand the touched packages' tests are green.What this does not do
It does not mint capabilities for callers. Serving the key set unblocks boot; a call still needs a token. The natural source is the fixture, which already declares the local principals and their grants (
pkg/run/fixture:ReadAdministrator,ReadCompositionGrants) — so the run would assert what the fixture declares rather than anything invented. That half is a product decision and is left for you; it is the remaining item on #1074.🤖 Generated with Claude Code