From 0cdbdca1393283bdf9d4f3ab1ff8c3a210e11373 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 20:33:05 +0100 Subject: [PATCH] Foresee a bus account not yet issued in a composition asked ahead of the Approve, so a module with its own account can be placed (issue 774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On 2026-10-30 the placement proposal p-20261030t192428-77dabc was refused on ace and novox: "has no bus credential: nothing was issued for novox.mesh-conditions" (and mesh-self-check, mesh-healers). The vault mints a bus account from the grants part's wanted list once the placement is written on the operator's Approve (ADR 0561 §1: "From the lease write, the vault mints every new bus account and the controller mints none"; ADR 0567 §5: "A placement is written on the operator's Approve"), so a shadow composition can never hold it, and no new module with its own account could be placed. Quick fix: skip the check for the three modules. Thorough fix: the Foreseeing mode foresees an unissued bus account like any own secret the next step makes; the issue-203 refusal (busCredentialIssued), the controller's send's, is gone, and the Reading mode still leaves out what was never issued. Chose thorough: it holds for every module with its own account. --- go/compose/credentials.go | 30 ++++-------------------- go/compose/credentials_test.go | 42 ++++++++++++++++------------------ 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/go/compose/credentials.go b/go/compose/credentials.go index 8d39bbd..3d7d1e9 100644 --- a/go/compose/credentials.go +++ b/go/compose/credentials.go @@ -257,28 +257,6 @@ func (c *composer) wouldMakeSecretForModule(node Node, module, name string) erro return nil } -// busCredentialIssued refuses an own secret called `broker` whose bus account nobody issued (novox/hq issue 203): the broker -// credential is never invented, an account on the bus minted by `module issue`. -func (c *composer) busCredentialIssued(node, module, name string) error { - var user string - switch name { - case "broker": - user = node + "." + module - case ProvisionerSecret: - user = "provisioner." + node - default: - return nil - } - if !slices.Contains(c.in.BusMinted, user) { - return fmt.Errorf( - "%s on %s has no bus credential: nothing was issued for %s, and a push "+ - "would seal a placeholder its process cannot read (novox/hq issue 203). "+ - "`module issue %s --node %s`, then push again", - module, node, user, module, node) - } - return nil -} - // givenUnderAnOldKey is the refusal for a value a person gave sealed to a key the machine no longer holds. func givenUnderAnOldKey(module, node, name string) error { return fmt.Errorf("%s on %s holds %q, which was given to the mesh rather than made by it, and %s has "+ @@ -354,10 +332,10 @@ func (c *composer) ownSecretsFor(node Node, plan catalogue.Resolution, handing c } if !held && mode == Foreseeing { // Asked ahead of the send: what the send would do about it, by the send's own rules. Made by it -- a stand-in, - // named; refused by it -- refused here, the same words (novox/hq issue 275). - if err := c.busCredentialIssued(node.Name, m.Module, name); err != nil { - return nil, nil, err - } + // named; refused by it -- refused here, the same words (novox/hq issue 275). A bus account not yet issued is + // foreseen, never refused: the vault mints it from the grants part's wanted list once the placement is written + // on the operator's Approve (ADR 0561 §1, ADR 0567 §5), so a composition asked ahead of that cannot hold it + // (issue 774, 2026-10-30: every proposal placing mesh-conditions, mesh-self-check or mesh-healers refused). if err := c.wouldMakeSecretForModule(node, m.Module, name); err != nil { return nil, nil, err } diff --git a/go/compose/credentials_test.go b/go/compose/credentials_test.go index 0697358..f56900d 100644 --- a/go/compose/credentials_test.go +++ b/go/compose/credentials_test.go @@ -116,34 +116,32 @@ func TestAnOutsideSecretIsPlacedOnlyOnceAPersonGaveIt(t *testing.T) { } } -// 1364/AC3 (the send's refusals, novox/hq issue 203 and 275): in the Foreseeing mode a bus credential nobody issued and a given secret -// under an old key are refused in the controller's words; with the credential issued, the secret is foreseen. No secret value is in -// an error. +// 1364/AC3 (the send's refusals, novox/hq issue 275): in the Foreseeing mode a given secret under an old key is refused in the +// controller's words. No secret value is in an error. A bus account not yet issued is foreseen, issued or not (issue 774: the vault +// mints it once the placement is written on the Approve, ADR 0561 §1, ADR 0567 §5; mesh-self-check's proposal on ace was refused +// "has no bus credential: nothing was issued for ace.mesh-self-check"). func TestTheSendsRefusalsAreRefusedInTheSendsWords(t *testing.T) { - broker := catalogue.Manifest{Module: "gate", Version: "1", - OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/var/lib/mesh/gate/broker", Taken: catalogue.TakenAtStart}}} + broker := catalogue.Manifest{Module: "mesh-self-check", Version: "1", RunsAs: "mesh-self-check", + OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/var/lib/mesh/mesh-self-check/broker", Taken: catalogue.TakenAtStart}}} in := withAssistant(t) - in.Catalogue["gate"] = broker + in.Catalogue["mesh-self-check"] = broker laptop := nodeOf(t, &in, "laptop") - laptop.Assigned = append(laptop.Assigned, "gate") - _, err := Render(in, "laptop", Foreseeing) - want := "gate on laptop has no bus credential: nothing was issued for laptop.gate, and a push would seal a placeholder " + - "its process cannot read (novox/hq issue 203). `module issue gate --node laptop`, then push again" - if err == nil || err.Error() != want { - t.Fatalf("got %v, want %q", err, want) - } - // Reading refuses nothing for it: the machine is not running it. - if _, err := Render(in, "laptop", Reading); err != nil { - t.Fatalf("Reading refused a secret the machine is not running: %v", err) - } - in.BusMinted = []string{"laptop.gate"} - if got := rendered(t, in, "laptop", Foreseeing); !got.Rendering.Foreseen["gate"]["broker"] { - t.Errorf("an issued bus credential is not foreseen: %v", got.Rendering.Foreseen) + laptop.Assigned = append(laptop.Assigned, "mesh-self-check") + for _, minted := range [][]string{nil, {"laptop.mesh-self-check"}} { + in.BusMinted = minted + got, err := Render(in, "laptop", Foreseeing) + if err != nil || !got.Rendering.Foreseen["mesh-self-check"]["broker"] { + t.Fatalf("minted %v: the bus account is not foreseen: %v", minted, err) + } + // Reading refuses nothing for it: the machine is not running it. + if _, err := Render(in, "laptop", Reading); err != nil { + t.Fatalf("Reading refused a secret the machine is not running: %v", err) + } } // A given secret under an old key. in.OwnSecrets = []OwnSecretRow{{Node: "laptop", Module: "assistant", Name: "password", Origin: "accepted", SealedTo: "an-old-key", Sealed: secretValue}, - {Node: "laptop", Module: "gate", Name: "broker", Origin: "made", SealedTo: laptop.SealingKey, Sealed: secretValue}} - _, err = Render(in, "laptop", Foreseeing) + {Node: "laptop", Module: "mesh-self-check", Name: "broker", Origin: "made", SealedTo: laptop.SealingKey, Sealed: secretValue}} + _, err := Render(in, "laptop", Foreseeing) wantOld := "assistant on laptop holds \"password\", which was given to the mesh rather than made by it, and laptop has since generated a new " + "sealing key. The mesh cannot make another; issue it again" if err == nil || err.Error() != wantOld {