Files
mesh-sdk/go/buscompose/process_grants.go
T
jschoubben b5f8604d03 Copy the controller's bus composer into mesh-sdk as buscompose, held byte for byte to the controller's lists (issue 1377, ADR 0547 §5)
The grants part composes the bus's user list with the composer the controller uses (ADR 0547 §5), copied rather than
moved: the controller is not changed and keeps its own until it loses the lease. Left out: the bus client, the broker's
address from the environment and the client's two methods; the lease's two subjects are copied in.

One table test holds the copy to four meshes' lists and the server golden, written by the controller's own
internal/broker from the same fixtures (testdata/controller-user-lists.json).
2026-10-25 21:54:58 +01:00

53 lines
2.6 KiB
Go

package buscompose
// The bus account of one process of a module that declares bus (novox/hq ADR 0436 §1, §2, §3, §5).
//
// **Never the module's own account's grants, never the tool runner's.** A process's authority is read
// straight from its bus declaration — its emits, its queue's consumer, its state, whether it reads the
// records of its module's own asks, and the tools and seat verbs it calls — and nothing else: it holds no
// seat, serves no tool, hears no module stream, and answers nothing a caller asked (§3). Modelled on
// provisionerGrants, the other kind of process account the mesh mints (ADR 0349 clauses 6 and 11).
// processGrants is one process's whole authority: to publish, and to subscribe.
func processGrants(p Principal) (pub, sub []string, err error) {
// 1. Its own events, under its module's own namespace — the same subjects the module's own principal
// would publish them on, had it declared them there instead (novox/hq ADR 0436 §1).
own := "mesh.mod." + p.Module
for _, e := range p.Emits {
pub = append(pub, own+".event."+e)
}
// 2. The one durable consumer of each work-queue stream its bus names (novox/hq ADR 0436 §1, §2, §6):
// asked about, pulled from and acknowledged, by the same name ConsumersOf composes the consumer under
// (QueueConsumer) — and no other stream's.
for _, q := range p.Queues {
consumer := p.Node + "_" + p.Module + "_" + q.Process
pub = append(pub,
"$JS.API.CONSUMER.INFO."+q.Stream+"."+consumer,
"$JS.API.CONSUMER.MSG.NEXT."+q.Stream+"."+consumer,
"$JS.ACK."+q.Stream+"."+consumer+".>")
}
// 3. Its module's own state, by the local names its bus names alone — never the whole of what the
// module keeps (novox/hq ADR 0436 §1), read and written as stateGrants gives any module's own bucket.
pub = append(pub, stateGrants(stateAccess{Module: p.Module, Node: p.Node, Keeps: p.State})...)
// 4. The router's records of its module's own asks (novox/hq ADR 0436 §1, ADR 0259 §3): the same
// direct-get subjects a module's own principal reads for the seats its Uses names that keep them —
// read here from the one list, so the two cannot disagree.
if p.Asks {
pub = append(pub, SeatTrafficOf(p.Module, nil, p.Uses, nil).Records...)
}
// 5. The tools and seat verbs it calls (novox/hq ADR 0152, ADR 0436 §1): the same derivation a
// module's own principal's invokes takes, so `callerNamed` (applied to every non-controller principal)
// adds the caller-named `call` variants for it exactly as it would for a module.
invoked, err := invokedSubjects(p.Invokes)
if err != nil {
return nil, nil, err
}
pub = append(pub, invoked...)
return pub, sub, nil
}