Repository navigation
feat(run): derive what a composition would have to be granted - #1076
Closed
antoinetoussaint-byte wants to merge 3 commits into
Closed
antoinetoussaint-byte wants to merge 3 commits into
antoinetoussaint-byte wants to merge 3 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>
…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>
An installation is one solution's bound identity in an org, and InstallSolution produces one agent principal, one solution scope node, one standing role grant and one organization installation. Its installer surface is already four paths — token, inspect, apply, verify — so the shape of "show what needs approving, then approve it" exists. What is missing is the first part: something that says what a given composition is asking for. This derives that from the manifests. For each service it reports the workspace-configuration groups it reads and the endpoints it calls, and separates what leaves the composition from what is inside it — a dependency the operator is installing in the same act is composed, not granted, and listing it buries the ones that genuinely cross a boundary. It derives the REQUEST and never the approval. MODULE_INSTALLATION.md is explicit that the envelope is not derivable from a module's own declaration: "Policy publication is an explicit trust-root operation; an ordinary module release declaration cannot write this projection." A module that could write its own envelope could grant itself authority by declaring it. So this grants nothing, signs nothing and reaches no network, and the rendered text says so twice, because a list of requirements reads like a list of things already arranged. Derive is pure, so what an operator approves in a deployed environment is what a local run derived from the same manifests rather than a differently ordered reading of it. A test asserts that stability directly. This is the half that is safe to automate. Approving against a fixture's declared approver in a local environment is the obvious next step and is not here: it decides authority, and that decision belongs to whoever owns the trust root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
A lazybox agent is working on this. claim detail |
Contributor
Author
|
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 |
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.
Groundwork for the auto-install idea in #1074: the half that is safe to automate.
Why only half
InstallSolutionalready produces one agent principal, one solution scope node, one standing role grant and one organization installation, and the installer surface is alreadytoken,inspect,apply,verify. So "show what needs approving, then approve it" is the shape that exists.What is missing is the input to
inspect: something that says what a given composition is actually asking for.This derives that, and stops there.
MODULE_INSTALLATION.mdis explicit about why:A module that could derive its own envelope could grant itself authority by declaring it. So this derives the request and never the approval. It grants nothing, signs nothing, and reaches no network.
What it does
For each service in the composition it reports the workspace-configuration groups it reads and the endpoints it calls, and separates what leaves the composition from what is inside it. A dependency the operator is installing in the same act is composed, not granted — listing it buries the ones that genuinely cross a boundary. For record-store that means
saas/accounts/authorityandsaas/auth-gateway/restsurface, whilerecord-store/store/tcpcorrectly does not.Deriveis pure, so what an operator approves in a deployed environment is what a local run derived from the same manifests, rather than a differently ordered reading of it. There is a test for that specifically.The rendered text states twice that nothing is granted by deriving it, because a list of requirements reads like a list of things already arranged.
What is deliberately not here
Approving it. In a local environment the fixture already declares every input an approval would need — pinned users with
role: super_admin, pinned organizations ("a module principal grant names its tenant by organization id"), andReadCompositionGrantsmerging each principal's grants — so a dev auto-install would be reading an approval someone committed, not inventing one. That is the obvious next step and it decides authority, so it belongs to whoever owns the trust root rather than to this PR.Context
Found while making a module run standalone (#1074, #1075). The motivating case:
record-store/recordsdeclares five workspace-configuration groups and could not start without four of them, because it had only ever been run composed with a host — which is the class of thing this derivation makes visible before it is a surprise.🤖 Generated with Claude Code