Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions cmd/run/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ func (cluster *launcher) deliver(ctx context.Context, module string) error {
// the fixture's platform administrator, unless the host holds it already. A
// composition that states no grant writes none.
func (cluster *launcher) ensureEnvelope(ctx context.Context, gatewayURL string) error {
grants, err := cluster.grants()
grants, err := cluster.grants(ctx)
if err != nil || len(grants) == 0 {
return err
}
Expand Down Expand Up @@ -772,12 +772,16 @@ func (cluster *launcher) fixtureAdministrator(ctx context.Context) (runfixture.A
// grants each module principal, in the host's own grant shape. A platform base
// states its modules' grants and a solution composed on it states its own
// principal's; the consuming workspace decides a principal it states
// (runfixture.ReadCompositionGrants).
func (cluster *launcher) grants() (map[string]gitops.EnvelopeGrant, error) {
// (runfixture.ReadCompositionGrants), with an entry for each prepared binding
// a module installs (gitops.WithPreparedBindingGrants).
func (cluster *launcher) grants(ctx context.Context) (map[string]gitops.EnvelopeGrant, error) {
principals, err := runfixture.ReadCompositionGrants(cluster.composed(), cluster.env.Name)
if err != nil {
return nil, err
}
if principals, err = gitops.WithPreparedBindingGrants(ctx, cluster.composed(), cluster.env, principals); err != nil {
return nil, err
}
out := map[string]gitops.EnvelopeGrant{}
for prefix, grant := range principals {
out[prefix] = gitops.EnvelopeGrant(grant)
Expand Down
27 changes: 27 additions & 0 deletions docs/module-manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,20 @@ Both at once is refused by name. The request is advisory: the host grants the
envelope's grant for the principal, reports requested bindings the grant lacks
as a reason on the binding, and never widens or narrows the grant from it.

**A module that installs prepared bindings also asks for each one.** When one
of its services declares the `runnable-bindings` workspace configuration group
([Runnable](runnable.md)), the module's request gains a binding for every
prepared binding the environment's group carries:
`{id: <the prepared key, MODULE__OPERATION>, audience, operations: {invoke, lookup}}`,
with the audience and the kinds and actions of the binding's resolved policy.
It carries **no resource id**: which resources an organization lets the module
reach is that organization's consent, written to the module's installation
scope, which narrows the binding there. A prepared key equal to a binding id
the module's own declaration uses is refused by name, and a value in the group
that is not a prepared binding fails the render by key. A module that asks for
nothing — no contract, no scoped consumption — gains nothing: the host grants
it its envelope whole, and a request would change that.

The contract declares the principal, the operation bindings the module redeems
(each with the operations it needs and a scope ceiling per operation), the
module's queues and namespaces, its own scope ceilings and the destinations it
Expand Down Expand Up @@ -526,6 +540,19 @@ does not decide are refused by name, never resolved to either. A native run
ignores the floor. Core's `AdmitEnvelope` runs over the base
branch's record before the host runs it over its own.

**Prepared bindings are listed for the module that installs them (a
stopgap).** The host reads a binding's delegation mode only from the
envelope's entry for it, so a requested binding the envelope does not list is
never granted. A native run and a publish therefore add, 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:
the binding's audience, its kinds and actions **without resource ids**, and
`"delegation_mode": "module_actor"`, so a call through it names the installing
module as the actor. An entry the composition states for that key is kept as
stated, and a principal the composition does not grant gains none. This exists
only because the mode is read from the envelope; when the mode travels with
the manifest's binding, it goes away.

**Stating nothing is not revoking.** A composition that delivered an envelope
and no longer states one — no `module-envelope.json` in any workspace of the
composition for the environment — is refused. Revoking every grant is a
Expand Down
7 changes: 7 additions & 0 deletions docs/runnable.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ refused when the environment is read, because it would look like either.
there is nothing to decline and it would hide a typo, and `[]` on a key that
names no derived operation is refused as an unknown key.

A module whose service installs prepared bindings asks for each one in its
manifest's authority request, by its key, with the binding's audience, kinds
and actions and no resource id; the module's installation scope narrows it to
the resources an organization consents to
([module manifests](module-manifests.md#what-a-module-asks-for)). A declined
operation has no prepared binding, so nothing asks for it.

A service that installs a declined operation is refused at its own point: the
`runnable-bindings` key it names is not configured, and it reports that key.
The decline is the environment's own answer, so the same operation can be
Expand Down
11 changes: 11 additions & 0 deletions pkg/gitops/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ func moduleAuthority(
}
authority.Bindings = append(authority.Bindings, solutionhost.AuthorityBinding{ID: binding.ID, Audience: binding.Audience, Operations: operations})
}
// A module that installs prepared bindings also asks for each one it
// installs (runnable_grants.go).
if installsPreparedBindings(services) {
grants, err := preparedGrants(provided)
if err != nil {
return nil, fmt.Errorf("module %s: %w", module.Name, err)
}
if err := appendPreparedBindings(authority, module.Name, grants); err != nil {
return nil, err
}
}
authority.Queues = append([]string(nil), resolved.Queues...)
authority.Namespaces = append([]string(nil), resolved.Namespaces...)
for _, ceiling := range resolved.ScopeCeilings {
Expand Down
3 changes: 3 additions & 0 deletions pkg/gitops/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -2566,6 +2566,9 @@ func stageAndSettleDelivery(
if err := publication.declareEnvelope(inventory, environment); err != nil {
return nil, err
}
if err := publication.listPreparedBindings(ctx, environment); err != nil {
return nil, err
}
return settleManifestDelivery(ctx, repo, publication.baseBranch, target, targetPath, environment, inventory, &publication.options)
}

Expand Down
16 changes: 16 additions & 0 deletions pkg/gitops/publish_envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ func (p *deliveryPublication) declareEnvelope(inventory *Inventory, environment
return nil
}

// listPreparedBindings lists, in the envelope this publish delivers, each
// prepared binding a module installs, exactly as a native run lists them
// (WithPreparedBindingGrants), so a cell grants what a run grants. An
// envelope this publish does not deliver is left alone.
func (p *deliveryPublication) listPreparedBindings(ctx context.Context, environment string) error {
if p.options.Envelope == nil {
return nil
}
principals, err := WithPreparedBindingGrants(ctx, p.workspace, p.env, p.options.Envelope.Principals)
if err != nil {
return fmt.Errorf("list the prepared bindings in the module envelope for %s: %w", environment, err)
}
p.options.Envelope.Principals = principals
return nil
}

// settleEnvelope settles, signs and writes the envelope beside the manifests
// already settled, and returns the delivery record and file list extended by
// it. Nothing to deliver and nothing delivered before leaves both unchanged.
Expand Down
Loading
Loading