Resync hq ADR references (0044-0054 -> 0039-0049) #6

Merged
jschoubben merged 1 commits from feat/adr-ref-resync into main 2026-09-05 10:47:53 +00:00
12 changed files with 28 additions and 28 deletions
Showing only changes of commit 87c193b202 - Show all commits
+2 -2
View File
@@ -198,7 +198,7 @@ func moduleCommand(ctx context.Context, args []string) error {
return nil
case "issue":
// A module's broker account, scoped by its emits and consumes (novox/hq ADR 0048) and
// A module's broker account, scoped by its emits and consumes (novox/hq ADR 0043) and
// sealed to the machine that will run it — the generic case the builder was the first of.
set := flag.NewFlagSet("module issue", flag.ContinueOnError)
forNode := set.String("node", "",
@@ -244,7 +244,7 @@ func moduleCommand(ctx context.Context, args []string) error {
return err
}
// A consumer's queue, with its dead-letter, is the substrate's to declare — its own account
// may not (ADR 0048). Made now, so it exists before the module binds onto it.
// may not (ADR 0043). Made now, so it exists before the module binds onto it.
if len(m.Consumes) > 0 {
if err := management.EnsureModuleQueue(ctx, *forNode, module); err != nil {
return err
+1 -1
View File
@@ -521,7 +521,7 @@ func grantsFor(ctx context.Context, open *stores, node string) ([]catalogue.Gran
from = ""
}
// The consumer's identity slug, from its own manifest, carried on the grant so the provider
// derives the same login the consumer does (novox/hq ADR 0054). Refused here if it still would
// derives the same login the consumer does (novox/hq ADR 0049). Refused here if it still would
// not fit the tightest backend — the mesh chose the name, so the mesh refuses it, with the
// remedy a short slug rather than a login a provider silently shortened.
slug := ""
+8 -8
View File
@@ -67,7 +67,7 @@ const ExchangeName = "mesh"
// constant that differed would be caught by the test that asserts they agree.
const BuildQueueName = "builds"
// The events bus (novox/hq ADR 0047): one topic exchange every event rides, a second for tool
// The events bus (novox/hq ADR 0042): one topic exchange every event rides, a second for tool
// RPC kept apart, and a dead-letter home for a poison event. The substrate owns these — a module's
// account cannot declare them, only bind its own queue to the events one.
const (
@@ -77,15 +77,15 @@ const (
)
// ModuleQueueFor is the durable queue a module consumes its events from — one per module per node
// (novox/hq ADR 0047), named so the account that may read it is exactly this module's.
// (novox/hq ADR 0042), named so the account that may read it is exactly this module's.
func ModuleQueueFor(node, module string) string { return node + "." + module + ".events" }
// modulePermissions is a module's authority on the bus, derived from its manifest (novox/hq
// ADR 0048): what it consumes and what it emits, and nothing else. Pure, so the scope is tested as
// ADR 0043): what it consumes and what it emits, and nothing else. Pure, so the scope is tested as
// patterns without a broker — the way a builder's is.
//
// A note on the limit: the broker's write permission is per exchange, not per routing key (LavinMQ
// has no topic permissions), so an emitting module is granted the events exchange whole. ADR 0047's
// has no topic permissions), so an emitting module is granted the events exchange whole. ADR 0042's
// origin reservation — a module publishes only under `module.<self>.*` — is stamped by the sdk, not
// enforced here; that gap is the broker's, and is recorded rather than hidden. A pure consumer like
// the audit logger is unaffected: it is granted no write to the exchange at all.
@@ -94,7 +94,7 @@ func modulePermissions(node, module string, emits, consumes []string) (configure
events := regexp.QuoteMeta(EventsExchangeName)
rpc := regexp.QuoteMeta(RPCExchangeName)
// A module serves each of its tools on its own queue, namespaced by the module (novox/hq
// ADR 0052) — serve.<module>.<tool> — so the account may declare, bind and read exactly its own,
// ADR 0047) — serve.<module>.<tool> — so the account may declare, bind and read exactly its own,
// and no other module's.
serve := "serve\\." + regexp.QuoteMeta(module) + "\\..*"
@@ -102,7 +102,7 @@ func modulePermissions(node, module string, emits, consumes []string) (configure
configure = "^(" + queue + "|" + serve + ")$"
// Write to bind its queue and serve queues (binding is a write on the queue), and to the RPC
// exchange to publish replies (ADR 0052: replies ride mesh.rpc, never the default exchange, which
// exchange to publish replies (ADR 0047: replies ride mesh.rpc, never the default exchange, which
// would let it publish into any queue). To the events exchange only if it emits.
writes := []string{queue, serve, rpc}
if len(emits) > 0 {
@@ -121,7 +121,7 @@ func modulePermissions(node, module string, emits, consumes []string) (configure
}
// CreateModuleAccount gives an assigned module its own broker account, scoped by what it emits and
// consumes (novox/hq ADR 0048). The account name carries the node so the same module on two machines
// consumes (novox/hq ADR 0043). The account name carries the node so the same module on two machines
// holds two accounts, each sealed to its own; the permissions carry the module so one module cannot
// read another's queue. Generic — the builder is one instance of this rule, not a separate kind.
func (m *Management) CreateModuleAccount(ctx context.Context, node, module, password string, emits, consumes []string) (string, error) {
@@ -152,7 +152,7 @@ func (m *Management) CreateModuleAccount(ctx context.Context, node, module, pass
// EnsureModuleQueue declares a consuming module's queue with its dead-letter exchange, idempotently.
// The substrate declares it because a scoped module account may not: the broker refuses a queue with
// a dead-letter exchange to a non-administrator (novox/hq ADR 0048), so a consumer passively checks
// a dead-letter exchange to a non-administrator (novox/hq ADR 0043), so a consumer passively checks
// the queue the mesh made rather than declaring its own.
func (m *Management) EnsureModuleQueue(ctx context.Context, node, module string) error {
queue := ModuleQueueFor(node, module)
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// The audit logger consumes everything and emits nothing. Its account must let it declare and read
// its own queue and read the events exchange to bind onto — and must not reach another module's
// queue, nor grant any write to the events exchange (novox/hq ADR 0048).
// queue, nor grant any write to the events exchange (novox/hq ADR 0043).
func TestAConsumerReadsItsOwnQueueAndTheEventsExchangeAndNoOthers(t *testing.T) {
// Rebuilt through CreateModuleAccount's own path by asking for the same scope it would apply.
// The queue this module reads:
+2 -2
View File
@@ -61,7 +61,7 @@ type Grant struct {
Values map[string]any
// Slug is the consumer module's identity slug, if it declared one — carried on the grant so the
// provider side derives the same login the consumer does, even across nodes where the consumer's
// manifest is not in view (novox/hq ADR 0054). Empty means "use the module name".
// manifest is not in view (novox/hq ADR 0049). Empty means "use the module name".
Slug string
// At is where the consuming machine is on the private network, empty if it is not on one.
//
@@ -143,7 +143,7 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
// Once, from every module's listens -- not per module. A module receiving only its own ports
// would write a rule set that closed every other module on the machine. Each module's per-node
// exposure settings override its listens' source first (novox/hq ADR 0051).
// exposure settings override its listens' source first (novox/hq ADR 0046).
exposure := map[string]map[int]string{}
for _, m := range r.Modules {
e, err := Exposure(m, with.Settings[m.Module])
+3 -3
View File
@@ -73,7 +73,7 @@ func (r Resolution) Filtering(computed map[string]Generator, ports map[string]ma
}
// The source, with any per-node exposure setting applied. The override names the port
// the module declares, so it travels with that port to wherever the machine publishes
// it (novox/hq ADR 0051): the same module is internal on one node and public on another.
// it (novox/hq ADR 0046): the same module is internal on one node and public on another.
from := l.From
if override, set := exposure[m.Module][l.Port]; set {
from = override
@@ -108,7 +108,7 @@ func (r Resolution) Filtering(computed map[string]Generator, ports map[string]ma
return widest(out), nil
}
// ExposeSetting is the settings key that overrides a listen's source per node (novox/hq ADR 0051):
// ExposeSetting is the settings key that overrides a listen's source per node (novox/hq ADR 0046):
//
// {"expose": {"5432": "anywhere"}}
//
@@ -122,7 +122,7 @@ const ExposeSetting = "expose"
// It refuses an override for a port the module does not listen on, or to a source that is not a
// real one — an exposure setting that reaches no port, or names a source nothing enforces, is the
// "reads as a restriction and is none" fault this whole mechanism exists to prevent (novox/hq
// ADR 0048/0050). A module with no `expose` setting yields nothing and keeps its manifest defaults.
// ADR 0043/0045). A module with no `expose` setting yields nothing and keeps its manifest defaults.
func Exposure(m Manifest, layers []Layer) (map[int]string, error) {
listened := make(map[int]bool, len(m.Listens))
for _, l := range m.Listens {
+2 -2
View File
@@ -580,7 +580,7 @@ func named(r Resolution) []string {
}
// A port's source is a per-node setting, not a manifest constant: the same module is internal on
// one machine and public on another (novox/hq ADR 0051). postgres listens from the mesh by default;
// one machine and public on another (novox/hq ADR 0046). postgres listens from the mesh by default;
// a setting on one node exposes it to anywhere, and the rule set follows.
func TestExposureSettingOverridesAListensSource(t *testing.T) {
postgres := Manifest{Module: "postgres", Version: "1",
@@ -605,7 +605,7 @@ func TestExposureSettingOverridesAListensSource(t *testing.T) {
}
// Exposure refuses a setting that names a port the module does not listen on, or a source that is
// not a real one — a setting reaching nothing is worse than none (novox/hq ADR 0048/0051).
// not a real one — a setting reaching nothing is worse than none (novox/hq ADR 0043/0046).
func TestExposureRefusesAPortNotListenedOnAndABadSource(t *testing.T) {
postgres := Manifest{Module: "postgres", Listens: []Listening{{Port: 5432, From: FromMesh}}}
layer := func(port, source string) []Layer {
+3 -3
View File
@@ -35,7 +35,7 @@ var identityUnusable = regexp.MustCompile(`[^a-z0-9_]+`)
const IdentityPrefix = "mesh_"
// IdentitySource is the name the mesh derives a consumer's identity from: the module's slug when it
// has declared one, otherwise its name (novox/hq ADR 0054). A module with a name short enough to fit
// has declared one, otherwise its name (novox/hq ADR 0049). A module with a name short enough to fit
// the tightest backend needs no slug; one whose name would overflow declares a short legible one.
func IdentitySource(slug, name string) string {
if slug != "" {
@@ -59,7 +59,7 @@ func ConsumerIdentity(node, module string) string {
// identityLimit is the shortest identifier limit among the systems these names reach: an S3 access
// key's 20 (novox/hq 04-ISSUES/010). PostgreSQL keeps 63 and MinIO 20, so 20 is the one that binds —
// the comment used to name PostgreSQL and was wrong. A name over it is refused, with the remedy a
// short slug (ADR 0054), not silently cut to fit.
// short slug (ADR 0049), not silently cut to fit.
const identityLimit = 20
// CheckIdentity refuses an identity that would not fit the tightest backend a consumer reaches.
@@ -68,7 +68,7 @@ const identityLimit = 20
// the statement succeeds, so two consumers agreeing for the first N bytes would become one login
// (04-ISSUES/022) — and S3 refuses outright. Refused here, at the mesh, because the mesh chose the
// name and is the only thing that can choose another. The remedy is a first-class one: give the
// module a short `slug` (ADR 0054), or shorten the machine's name.
// module a short `slug` (ADR 0049), or shorten the machine's name.
func CheckIdentity(node, module string) error {
got := ConsumerIdentity(node, module)
if len(got) <= identityLimit {
+1 -1
View File
@@ -6,7 +6,7 @@ import "testing"
func TestASlugIsPreferredOverTheModuleName(t *testing.T) {
// A module that declared a slug is identified by it, so a long name can be made to fit the
// tightest backend without a hash (novox/hq ADR 0054).
// tightest backend without a hash (novox/hq ADR 0049).
if got := IdentitySource("kc", "keycloak"); got != "kc" {
t.Errorf("the slug was not preferred: %q", got)
}
+3 -3
View File
@@ -120,7 +120,7 @@ type Manifest struct {
Version string `json:"version,omitempty"`
// Slug is a short identifier the mesh uses in place of the module name when it derives a
// consumer's login (novox/hq ADR 0054). Optional: a module with a short name needs none. It
// consumer's login (novox/hq ADR 0049). Optional: a module with a short name needs none. It
// exists because `mesh_<node>_<module>` must fit the tightest backend a consumer reaches — an S3
// access key is 20 characters — and a long module name would overflow it. A person choosing
// `kc` for keycloak keeps the identity legible where a hash would not.
@@ -135,7 +135,7 @@ type Manifest struct {
// Emits are the event types this module publishes onto the broker — dotted topic keys, e.g.
// "module.umami.site.created". Declared so the mesh knows the event graph; events are
// provisioning's lighter sibling — 1:many and broadcast, no credential (novox/hq ADR 0046).
// provisioning's lighter sibling — 1:many and broadcast, no credential (novox/hq ADR 0041).
Emits []string `json:"emits,omitempty"`
// Consumes are the event patterns this module subscribes to — topic patterns over module,
@@ -474,7 +474,7 @@ func ParseManifest(raw []byte) (Manifest, error) {
problems = append(problems, fmt.Sprintf(
"%q is not a usable module name: lower-case letters, digits, dashes and dots", m.Module))
}
// A slug is a short identifier the mesh derives a login from (novox/hq ADR 0054). The same
// A slug is a short identifier the mesh derives a login from (novox/hq ADR 0049). The same
// charset as a name; its length is checked against a backend's limit at assignment, where the
// node it joins is known — a slug that is fine on one machine's short name can overflow another's.
if m.Slug != "" && !name.MatchString(m.Slug) {
+1 -1
View File
@@ -172,7 +172,7 @@ func UnusedSettings(m Manifest, layers []Layer) []string {
for _, layer := range layers {
for key := range layer.Values {
// `expose` is a real destination for a module that listens: it overrides a port's
// source (novox/hq ADR 0051), validated in Exposure, so it is not stray here.
// source (novox/hq ADR 0046), validated in Exposure, so it is not stray here.
if key == ExposeSetting && len(m.Listens) > 0 {
continue
}
+1 -1
View File
@@ -56,7 +56,7 @@ func Make(consumerKey, providerKey string) (Sealed, error) {
// 30 bytes, not 32: base64url of 30 is exactly 40 characters, and 40 is the longest secret an
// S3 access key accepts (8–40), the tightest of the backends a minted password reaches — the same
// "fit the tightest backend" rule ADR 0054 sets for the login, on the secret. 240 bits is ample.
// "fit the tightest backend" rule ADR 0049 sets for the login, on the secret. 240 bits is ample.
value := make([]byte, 30)
if _, err := rand.Read(value); err != nil {
return Sealed{}, err