Repository navigation
fix(gitops): a host receives its module's table less the services that stand in for it; a gateway route carries its proto method's policy - #1189
Merged
Conversation
…t stand in for it, and a gateway route carries its proto method's policy The strict permission preflight (render, run cluster, deploy module/service, ci deploy) refused the authority module on a saas host once it ships a package (codefly-dev/authority#33): its local service, which implements the host interfaces and never runs on a saas host, put 32 unmarked methods into the table the manifest carries; and marking the hop's Version made it worse, "conflicting policies for GET /version", because the hop's http face publishes the REST gateway's OpenAPI rendering of the same route, which cannot carry the policy option. - HostMethodPolicies is the table a module's manifest carries to env's host: the package's table less the services that stand in for that host (an exported endpoint implementing a Codefly interface the host.delivery service implements, and no module-identity). The preflight holds it, the native composition delivery sends it, and a caller's binding resolves against it. A module-identity service is always in it; require_method_policies still holds the whole package. - Conflicts are refused per table (ServedMethodPolicies.Table), not across services that never meet in one. - Within a service, an OpenAPI route that declares nothing and that one of the service's proto methods binds (google.api.http) carries that method's policy; a differing declaration is still refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
antoinetoussaint-byte
added a commit
to codefly-dev/authority
that referenced
this pull request
Oct 27, 2026
tools/packagegate now reads the published contracts the way the CLI's delivery reads them (codefly-dev/cli#1189's HostMethodPolicies): each served procedure, its google.api.http routes and each OpenAPI operation, a gateway route inheriting the policy of the proto method that binds it, and a service that stands in for a host (no module identity, an endpoint implementing a Codefly interface) left out. Any other method with no policy is refused by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 27, 2026
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.
What this fixes
codefly-dev/authority#33 ships
modules/authority/module.package.codefly.yaml. A saas-hosted run needs that package to admit the authority module, because its hop has beenmodule-identitysince authority#28. Once the package exists, the CLI's strict permission preflight refuses the module on a saas host. That preflight lives inpkg/gitops/permission_validation.goand runs ondeploy gitops,run cluster,deploy module/serviceandci deploy. The refusal has two causes:localservice implementscodefly.dev/authority,composition,installationandsession. Its 32 methods (27 gRPC procedures, 5 HTTP routes) went into the table the manifest carries to saas/accounts. A saas host never runslocal: the workspace binds those interfaces to accounts. A call tolocalis the host's to answer, never one the host decides for the authority.localhas to stay in the module interface, because itsimplements:list is how it acts as host when there is no saas.httpendpoint publishes its service's REST-gateway OpenAPI document (openapi/api.swagger.json). That document is the proto rendered for HTTP, and it has no place forx-codefly-authority-policy. MarkingHopService.Versionwithany_checked_caller(the console's pattern) then fails withconflicting policies for GET /version: the proto's route is marked and the gateway's copy is not.The change
gitops.HostMethodPolicies: the table a module's manifest carries to the environment's host. It is the package's table without the services that stand in for that host. A stand-in is a service with both properties:host.deliveryservice also implements (version ignored);module-identity.The preflight holds this table, the native composition delivery sends it (
cmd/run/launcher.go), and a caller's binding resolves against it (moduleAuthoritycallees). So the three uses cannot disagree.module-identityservice is always in the table. Every service of a module that implements nothing the host implements is too, so those modules are unchanged.require_method_policies: truestill holds the whole package, stand-ins included, because that is what a module that sets it asked for.Conflicts are refused per table (
ServedMethodPolicies.Table), not across services that never meet in one.localand the hop both serveGET /version, and only the hop is in the saas host's table. Two services in one table that serve one route with different policies are still refused.A gateway route carries its proto method's policy. I picked deriving the policy over not publishing the gateway document. The CLI's contract model already makes the proto the one place a gRPC service declares a route's policy:
methodpolicy.contractMethodslists eachgoogle.api.httproute under the method's policy. The OpenAPI document is a rendering of that proto. Rule, within one service:Not publishing the document would also drop the hop's http face from the package catalog, and with it the face's reach and route listing. It would also do nothing for a
restendpoint, which publishes the same gateway document.Fix or hack: fix, at the place that owns the behaviour: the reader of the method table in
pkg/gitops.Reused:
HostImplements' reading of the host's interface endpoints,isModuleNotFound, the existingmethodpolicy.Contractreader andValidateMethodPolicies. Not added: no new configuration key, no per-module exception, and no change to the authority's declarations.Why not "identity services only"
On a saas host, the identity-only rule and this one give the same result for the authority: the hop's two methods. Identity-only is too narrow in general, though. A service that only receives calls needs no module identity (identity means minting the module's own Work Context), yet its published policies are what a caller's binding resolves against. module-runtime's qualification graph has one:
owners/rest-owneris notmodule-identity, and its marked routes are what the runtime worker's binding resolves against. Identity-only would drop those policies from the delivered table and from callee resolution. This rule sets aside only services that implement the host's own interfaces.Evidence
Strict-check harness:
gitops.CheckCompositionPermissions(ctx, ws, env("local"), strict=true), the callrenderCompositionmakes. It ran on obin-ai/platform-core at PR #114's current head29e8d280(newer than552a055e), hostsaas/accounts, envlocal, pins materialized withcodefly ci plan --all. The authority module was overridden throughcodefly.local.yamlresolve.authority.path.89c76fdec5273b74+Versionmarkedany_checked_caller(local worktree,codefly generate contracts authority)module package codefly/authority: conflicting policies for GET /versionc5273b74unmodifiedlocalmethod, plus the hop's two)/api.HopService/Version declares no permission policy,GET /version declares no permission policy49101ae1Every other module of that composition is unchanged. Each one's contract-publishing services are all
module-identity, except saas, which is the host's own module.Unit tests, each confirmed to fail with its rule removed:
TestAServiceStandingInForTheHostIsNotInTheTableItsManifestCarriesMethodPolicies(the whole package) still reports the conflict;module-identityis refused by name;TestAGatewayRouteCarriesThePolicyOfTheProtoMethodItRendersLocal gates:
go vetandgo test -race -count=1on./pkg/gitops,./cmd/runand./cmd/generatepass.golangci-lint run ./...(local v2.15.0) is clean on the changed packages. Its three remaining findings areerrcheckincmd/terminal.go, which main also has under v2.15.0. CI pins v2.14.0.Not verified
codefly deploy gitops) orcodefly run clusterend to end. The harness calls the same function those paths call, but no cluster ran.codefly rundelivering the narrowed table to accounts. The delivery now readsHostMethodPolicies, andcmd/runtests pass, but no saas-hosted run was started here.authority/local) only partly. Its pins are not materialized here, so the strict check stops on those modules. It reports no authority-module error on this branch, while main reportsconflicting policies for GET /version.local.What codefly-dev/authority#33 must add once this merges
Mark the hop's one method in
modules/authority/services/hop/proto/api.protowithoption (authority.hop.v1.policy) = {any_checked_caller: true};. That needsauthority/hop/v1/policy.protovendored next to it, as the console does (services/api/proto/authority/hop/v1/policy.protoplus its drift test). Then re-runcodefly generate contracts authority, which rewrites the hop'scontract.binpb, its proto snapshot, the catalog and the manifest digest. Nothing onlocaland nothing in the hop's OpenAPI document. With that, the strict preflight passes on platform-core with the saas host.🤖 Generated with Claude Code