Skip to content

feat(gitops): a module installing prepared bindings asks for each one - #1121

Merged
lukazhupa merged 1 commit into
mainfrom
feat/runnable-binding-grants
Oct 23, 2026
Merged

lukazhupa merged 1 commit into
mainfrom
feat/runnable-binding-grants

Conversation

@lukazhupa

Copy link
Copy Markdown
Contributor

A module whose service installs prepared runnable bindings (it declares the runnable-bindings workspace configuration group) is admitted to every owner those bindings call by the mesh, but asked the host for nothing to call them with. The host grants only bindings a manifest requests, so a prepared call could never exchange a capability as the person it acts for.

What changes

  • Request (permanent half). moduleAuthority appends, for a module that installs prepared bindings, one binding per prepared key in the environment's runnable-bindings group: {id: <MODULE__OPERATION>, audience, operations: {invoke, lookup: [<kind>:<action>]}}, read from each value's resolved policy (runnable.DecodePrepared). Resource ids are dropped; the module's installation scope narrows the binding per organization. A key equal to a binding id the module's own declaration uses is refused by name, and a value that is not a prepared binding fails the render by key.
  • Envelope entry (labelled stopgap). WithPreparedBindingGrants adds, to the grant of each module that asks for authority and installs prepared bindings, an operation_audiences entry per prepared key the grant does not already state: audience, kinds and actions without resource ids, and "delegation_mode": "module_actor". Both the native run (launcher.grants) and a publish (listPreparedBindings, after declareEnvelope) apply it, so a cell lists what a run lists. It exists only because the host reads a binding's mode from the envelope; it is deleted when the mode travels with the manifest's binding.
  • Unchanged: a composition with no prepared bindings, a module whose services do not install them, and a module that asks for no authority (the host grants it its envelope whole; adding a request would change that). An entry the composition states for a prepared key is kept as stated, and a principal the composition does not grant gains none.

Reuse

  • The executor rule is the one derivedRunnableReach already applies for mesh edges and cell consumers: a service declaring runnable-bindings.
  • The scopes come from the prepared values generate runnable-bindings writes (core's DecodePrepared and the resolved policy), not a second resolution.
  • loadModuleServices, authorityRequestOf and the composition's envelope merge (ReadCompositionGrants) are reused as they are.

Classification

The request half is a fix where the behaviour belongs: the render already derives what a module calls, and now derives what it asks for from the same source. The envelope entry is a stopgap, labelled in code and docs: it carries the delegation mode only because the host reads the mode from the envelope today.

Tests

go test ./pkg/gitops/... ./cmd/run/... passes, and golangci-lint run ./pkg/gitops/... ./cmd/run/... (v2.14.0) reports 0 issues. New tests in pkg/gitops/runnable_grants_test.go:

  • a module installing prepared bindings asks for each one, with kinds and actions and no resource id;
  • a module whose services do not install them asks for its declaration alone;
  • a module asking for nothing gains neither a request nor an envelope entry;
  • a prepared key colliding with a declared binding id is refused, and an undecodable value is refused by key;
  • the envelope lists each prepared binding with module_actor and no resource id, keeping every other field and entry;
  • an entry the composition states for a prepared key is kept;
  • an envelope without prepared bindings, or with no grants, is unchanged;
  • a publish lists them in the envelope it delivers and leaves alone one it does not.

Not run: a render or publish against a live host.

Per-organization narrowing

The resource ids a binding reaches come from the module's installation scope, not from this change. Narrowing an existing installation is refused on every host today: the host leaves its policy log unset (codefly-dev/module-saas-starter module/services/accounts/code/work.go:569) until codefly-dev/module-saas-starter#1067, and narrowing requires it. Until then, an organization's resource ids are set when the module is installed, with an explicit InstallSolution scope.

Docs

docs/module-manifests.md (what a module asks for; the envelope's stopgap entry) and docs/runnable.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ehgefxu5moNkjuHfTJofXM

A service that declares the runnable-bindings group installs every binding
the composition prepared, and the mesh already admits it to each owner from
that one derivation. Its module's authority request now names each prepared
binding too: the prepared key as the binding id, with the audience and the
kinds and actions of the binding's resolved policy, and no resource id. Which
resources an organization lets the module reach is that organization's
consent, written to the module's installation scope.

A native run and a publish also list each prepared binding in the
installing module's envelope grant: the audience, the kinds and actions
without resource ids, and delegation_mode module_actor. This is a labelled
stopgap that exists only because the host reads the mode from the envelope;
it goes away when the mode travels with the manifest's binding. An entry the
composition states for a key is kept, and a module that asks for no
authority gains nothing, since the host grants it its envelope whole.
@lukazhupa
lukazhupa marked this pull request as ready for review October 23, 2026 17:19
@lukazhupa
lukazhupa added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 66c6080 Oct 23, 2026
14 checks passed
@antoinetoussaint-byte
antoinetoussaint-byte deleted the feat/runnable-binding-grants branch October 25, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant