Drop the lease write's findings, the controller's artifacts-gone and the issuing gate (issue 1126, B9)

ADR 0590 supersedes the lease write: mesh-grants says no lease event any
more, so the watchman's lease-altered and removal-failed findings and
their row had nothing to hear. The ended controller's consumers are
removed as hand acts, so their deletion is no consumer lost; only the
one on DEAD_LETTER_NOTICES is still read, since its removal hands the
notices to the watchman. The builder hears artifacts-gone from the
artifact sweep seat alone, and mesh-enrolment's tokens no longer report
an issuing gate that nothing can turn off.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 11:35:55 +01:00
parent f7f3518a04
commit 43d4f511c5
17 changed files with 60 additions and 185 deletions
@@ -188,8 +188,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
}
defer cc.Stop()
}
// The controller says artifacts-gone when its take-in found a key's blob gone (issue 1937): the builder's durable
// consumer on EVENTS, made by the controller from the manifest's consumes. Where it is not there yet the builder says
// The seat mesh-artifact-sweep says artifacts-gone when it found a key's blob gone (issue 1937, ADR 0571 §2): the
// builder's durable consumer on EVENTS, made by the grants part from the manifest's consumes. Where it is not there yet the builder says
// so; it still asks held at its start and at every module value.
switch cons, err := cred.EventConsumer(ctx, js, eventsStream); {
case err != nil:
@@ -15,8 +15,8 @@ import (
"mesh-delivery-builder/internal/build"
)
// eventsStream is the stream every module's and role's events are on, where the controller makes the builder's durable
// consumer of artifacts-gone (mesh-controller internal/broker EventsStream).
// eventsStream is the stream every module's and role's events are on, where the grants part makes the builder's durable
// consumer of artifacts-gone (mesh-sdk buscompose EventsStream).
const eventsStream = "EVENTS"
// SeatHeld is the seat mesh-artifact-sweep's verb held (ADR 0571 §2).
@@ -27,8 +27,7 @@ type toolCaller func(key string, body any) (json.RawMessage, error)
// storedVia is build.Builder's Stored: it asks the seat mesh-artifact-sweep's `held` (novox/hq ADR 0571 §2, issue 1937)
// and answers the references the store no longer holds. One it could not ask about, with none gone, is an error: the key
// is then left done, and the controller's take-in still refuses a gone blob. The controller's `held` is not asked: it
// is no longer a fallback (issue 1955).
// is then left done, and asked again at the next module value or artifacts-gone.
func storedVia(call toolCaller) func(ctx context.Context, references []string) ([]string, error) {
return func(_ context.Context, references []string) ([]string, error) {
body := map[string]any{"references": references}
@@ -260,8 +260,8 @@ func (b *Builder) storedOnce(key string) func(ctx context.Context, references []
}
}
// ArtifactsGone takes the controller's event artifacts-gone in (issue 1937): a take-in found that the store does not
// hold what the module's key names. The module is looked at again, which asks the store (ClaimOptions.Stored) and
// ArtifactsGone takes the seat mesh-artifact-sweep's event artifacts-gone in (issue 1937, ADR 0571 §2): the sweep found
// that the store does not hold what the module's key names. The module is looked at again, which asks the store (ClaimOptions.Stored) and
// builds the key again; an event for a fingerprint the module no longer has is said and left. The event only wakes the
// builder: what is built rests on the store's answer, never on the event alone.
func (b *Builder) ArtifactsGone(ctx context.Context, e delivery.ArtifactsGone) {
@@ -112,9 +112,9 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1
t.Errorf("invokes = %v, config = %v, want %v and no config", m.Invokes, m.Config, want)
}
// The builder is woken by a build module's report of a toolchain, as the indexer is: the bucket may not exist when it
// starts; and by the controller's artifacts-gone, a take-in refused for a gone blob (1937/AC4).
// 1955/L7: and the seat mesh-artifact-sweep's, the controller's until the lease write (its take-in sweeps until then).
if want := []string{"mesh-artifact-sweep.artifacts-gone", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-controller.artifacts-gone"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
// starts; and by the seat mesh-artifact-sweep's artifacts-gone, a blob the sweep found gone (1937/AC4, 1955/L7), the
// controller's no longer (1126/B9, ADR 0590).
if want := []string{"mesh-artifact-sweep.artifacts-gone", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
t.Errorf("consumes = %v, want %v", m.Consumes, want)
}
if b, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || b != "${dir:state}/broker" {
+1 -2
View File
@@ -16,8 +16,7 @@
"consumes": [
"mesh-artifact-sweep.artifacts-gone",
"mesh-build-go.toolchain.reported",
"mesh-build-node.toolchain.reported",
"mesh-controller.artifacts-gone"
"mesh-build-node.toolchain.reported"
],
"invokes": [
"seat:mesh-artifact-sweep.held"
@@ -37,6 +37,13 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
if routeOf("mesh.mod.web.event.started") != "" {
t.Error("another module's event goes to a row")
}
// 1126/B9 (ADR 0590 §3): the lease write's events went with it; the grants part says no lease event any more.
for _, gone := range []string{"mesh.seat.mesh-grants.event.lease-altered", "mesh.seat.mesh-grants.event.removal-failed",
"$JS.EVENT.ADVISORY.STREAM.DELETED.ASSIGNMENTS"} {
if r := routeOf(gone); r != "" && r != routeAdvisory {
t.Errorf("%s goes to the row %s", gone, r)
}
}
})
err := run(context.Background(), func(string) string { return "" }, io.Discard)
if err == nil || !strings.Contains(err.Error(), "MESH_BROKER_FILE") {
@@ -13,7 +13,6 @@ import (
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/health"
"mesh-delivery-watchman/internal/deadletters"
@@ -39,13 +38,6 @@ const (
routeDead = "dead-letter"
routeBusFault = "bus-fault"
routeProv = "provisioned"
routeLease = "lease"
)
// The lease write's subjects (ADR 0582 clauses 4, 6): the seat mesh-grants' events, and ASSIGNMENTS deleted.
var (
leaseEventPrefix = "mesh.seat." + catalogue.GrantsSeat + ".event."
assignmentsGone = "$JS.EVENT.ADVISORY.STREAM.DELETED." + buscompose.AssignmentsStream
)
// The bus module's events of the connections the bus closed (S9).
@@ -69,8 +61,6 @@ func routeOf(subject string) string {
return routeTools
case subject == CheckedSubject:
return routeChecked
case strings.HasPrefix(subject, leaseEventPrefix) || subject == assignmentsGone:
return routeLease
case strings.HasPrefix(subject, "$JS.EVENT.ADVISORY."):
return routeAdvisory
case strings.HasPrefix(subject, "mesh.events.dead."):
@@ -106,7 +96,6 @@ func heard(w *watch.Watchman, dead *deadKeeper, m *nats.Msg, logf func(string, .
}
if dead.isTheControllersNotices(a.ID) {
dead.check() // the controller's consumer removed: the watchman may take the notices now
w.RemovalGone(watch.RemovalConsumer)
return
}
w.Advised(a)
@@ -121,22 +110,6 @@ func heard(w *watch.Watchman, dead *deadKeeper, m *nats.Msg, logf func(string, .
kind = watch.KindConnectionClosed
}
w.Closed(kind, c)
case routeLease:
var body struct {
Altered string `json:"altered"`
Failed string `json:"failed"`
}
_ = json.Unmarshal(m.Data, &body)
switch m.Subject {
case assignmentsGone:
w.RemovalGone(watch.RemovalStream)
case leaseEventPrefix + catalogue.GrantsEventLeaseAltered:
w.LeaseAltered(body.Altered)
case leaseEventPrefix + catalogue.GrantsEventLeaseHanded:
w.LeaseHanded()
case leaseEventPrefix + catalogue.GrantsEventRemovalFailed:
w.RemovalFailed(body.Failed)
}
case routeProv:
var r watch.ProvReport
if err := json.Unmarshal(m.Data, &r); err != nil {
@@ -1,13 +1,11 @@
// Package deadletters is what a consumer gave up on, kept by the watchman from the lease write (novox/hq ADR 0564 §5,
// Package deadletters is what a consumer gave up on, kept by the watchman since the controller's end (novox/hq ADR 0564 §5,
// ADR 0264, ADR 0376, issue 1952 WM5): ported from mesh-controller internal/link/deadletters.go at e04846e9 with nothing
// changed in what is kept, aged or withheld, on the jetstream API.
//
// DEAD_LETTER_NOTICES is a work queue that holds one consumer. The controller's consumer `controller` never expires, so
// the watchman makes its own (Consumer) only when the lease names no controller and `controller` is gone (Gate); while
// `controller` still stands after the lease write it says so as a finding. Removing `controller` is a step of the lease
// write, with the bus module's tool. Before the lease write the watchman only lists and shows DEAD_LETTERS, which the
// controller still fills, and refuses to deliver or drop, naming the controller's verb; from it, delivering and dropping
// wait for the hand-act log's home (issue 1953), so they are refused naming that.
// DEAD_LETTER_NOTICES is a work queue that holds one consumer. The ended controller's consumer `controller` never
// expires, so the watchman makes its own (Consumer) only once `controller` is gone (Gate); while it stands the watchman says
// so as a finding. Removing `controller` is a hand act with the bus module's tool (novox/hq ADR 0590 clause 6). Delivering
// and dropping wait for the hand-act log's home (issue 1953), so they are refused naming that.
package deadletters
import (
@@ -317,9 +315,8 @@ func Refusal(act string) error {
}
// Gate is whether the watchman takes the notices itself: once the controller's consumer is gone. waiting says `controller`
// still stands, which is the watchman's finding: each notice waits in the queue until that consumer is removed. The lease
// is not read: the controller ended by its unassignment, never by the lease write (issue 1126), and mesh-sdk's
// buscompose.LeaseHandedAway answers false for the absent key, which would keep the notices the controller's for good.
// still stands, which is the watchman's finding: each notice waits in the queue until that consumer is removed (the
// controller ended by its unassignment, issue 1126).
func Gate(controllerStands bool) (take, waiting bool) {
return !controllerStands, controllerStands
}
@@ -8,7 +8,7 @@ import (
// The bus's advisories the watchman hears (S9, mesh-controller internal/link/advisories.go at e04846e9): a durable consumer
// the mesh defines deleted is `consumer-lost`. A message given up on is said from DEAD_LETTERS, where it is kept, for as
// long as it is kept (DeadLettersHeld), not from its advisory. The controller's own slow consumer and refused subjects end
// long as it is kept (DeadLettersHeld), not from its advisory. The controller's own slow consumer and refused subjects ended
// with it (ADR 0564 §4).
// Advisory subjects the watchman subscribes.
@@ -48,7 +48,9 @@ func MeshNamed(stream, name string) bool {
case strings.HasPrefix(stream, "SEAT_"):
return strings.HasSuffix(name, "_worker")
case name == "controller" || name == "controller-merges":
return true
// The ended controller's consumers are removed as hand acts (novox/hq ADR 0590 clause 6), never lost; its consumer on
// DEAD_LETTER_NOTICES alone is read, whose removal tells the watchman to take the notices.
return stream == "DEAD_LETTER_NOTICES" && name == "controller"
case stream == "EVENTS":
return strings.Contains(name, "_")
}
@@ -8,15 +8,15 @@ import (
)
// The dead letters' findings (novox/hq ADR 0564 §5, issue 330): a consumer DEAD_LETTERS holds a message for is said for as
// long as it holds any, `bus.<stream>.<consumer>.max-deliveries`, and the notices waiting behind the controller's consumer
// after the lease write are said as `bus.DEAD_LETTER_NOTICES.controller.notices-waiting`.
// long as it holds any, `bus.<stream>.<consumer>.max-deliveries`, and the notices waiting behind the ended controller's
// consumer are said as `bus.DEAD_LETTER_NOTICES.controller.notices-waiting`.
// DeadRecountEvery is how often DEAD_LETTERS is counted again while a consumer's finding stands: a letter delivered again
// or dropped by the controller's verb, which the watchman does not hear, ends its finding within it. The expiry of the
// watchman's own pulse, never a loop.
const DeadRecountEvery = 5 * time.Minute
// KindNoticesWaiting is the finding of notices waiting while the controller's consumer stands after the lease write.
// KindNoticesWaiting is the finding of notices waiting while the ended controller's consumer stands.
const KindNoticesWaiting = "notices-waiting"
// DeadLettersHeld is DEAD_LETTERS' count per consumer, `<stream>.<consumer>`, read whole: each consumer held writes its
@@ -73,16 +73,16 @@ func consumerMachine(stream, consumer string) string {
return ""
}
// NoticesWaiting says whether the lease is handed away while the controller's consumer on DEAD_LETTER_NOTICES still
// stands: every notice then waits in the queue, kept a week, until the lease write's step removes the consumer.
// NoticesWaiting says whether the ended controller's consumer on DEAD_LETTER_NOTICES still stands: every notice then waits
// in the queue, kept a week, until that consumer is removed (novox/hq ADR 0590 clause 6).
func (w *Watchman) NoticesWaiting(waiting bool) {
w.mu.Lock()
defer w.mu.Unlock()
f := Finding{Scope: ScopeBus, ID: "DEAD_LETTER_NOTICES.controller", Kind: KindNoticesWaiting, Severity: Warning, Source: "dead-letters",
Summary: "the lease names no controller but the controller's consumer on DEAD_LETTER_NOTICES still stands, so every " +
"notice of a message given up on waits in the queue, kept a week, and nothing keeps it in DEAD_LETTERS",
Said: "the consumer controller on DEAD_LETTER_NOTICES stands after the lease write",
Needs: "remove the consumer controller on DEAD_LETTER_NOTICES with the bus module's tool, the lease write's step."}
Summary: "the controller is unassigned but its consumer on DEAD_LETTER_NOTICES still stands, so every notice of a " +
"message given up on waits in the queue, kept a week, and nothing keeps it in DEAD_LETTERS",
Said: "the consumer controller on DEAD_LETTER_NOTICES stands after the controller's end",
Needs: "remove the consumer controller on DEAD_LETTER_NOTICES with the bus module's tool, recorded as a hand act (ADR 0590)."}
if waiting {
w.find(f)
return
@@ -59,23 +59,6 @@ func TestTheWatchmansFindings(t *testing.T) {
}
w.ModuleVerdict("node-tools", value(""), Put)
}, map[string]string{}},
{"774/S22/AC1, AC2: lease-altered and removal-failed are findings; lease-handed and the removal read gone clear them", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
w.LeaseAltered("the key holder names controller@a pid 9, not mesh-grants by lease-write-1")
if f := o.findings[FindingKey(ScopeBus, "mesh-controller_lease", KindLeaseAltered)]; !strings.Contains(f.Summary, "controller@a pid 9") {
t.Fatalf("findings = %v", o.findings)
}
w.LeaseHanded()
w.RemovalFailed("the stream ASSIGNMENTS: ASSIGNMENTS has 1 consumers")
k := FindingKey(ScopeBus, "lease-removal", KindRemovalFailed)
if f := o.findings[k]; !strings.Contains(f.Summary, "ASSIGNMENTS has 1 consumers") {
t.Fatalf("findings = %v", o.findings)
}
w.RemovalGone(RemovalConsumer) // not what it waits on
if _, ok := o.findings[k]; !ok {
t.Fatal("cleared by the consumer, while the stream it names stands")
}
w.RemovalGone(RemovalStream)
}, map[string]string{}},
{"WM4: a stuck key not moved is its finding, and its move deletes it", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
w.Step("go", "n1.ask1", runningStep("ask1", t0), Put)
expire(w, c, pulseKeyOf(t, checksKey, "ask1"), 120*time.Second)
@@ -157,6 +140,18 @@ func TestTheWatchmansFindings(t *testing.T) {
if _, ok := ReadAdvisory("$JS.EVENT.ADVISORY.CONSUMER.DELETED.KV_x.abc", []byte(`{"stream":"KV_x","consumer":"abc"}`), nil); ok {
t.Fatal("a reader's own consumer is said")
}
// 1126/B9 (ADR 0590 clause 6): the ended controller's consumers are removed as hand acts, never lost; only its
// consumer on DEAD_LETTER_NOTICES is still read, which tells the watchman to take the notices.
for _, gone := range [][2]string{{"EVENTS", "controller"}, {"EVENTS", "controller-merges"}, {"CONTROL", "controller"}} {
body := []byte(`{"stream":"` + gone[0] + `","consumer":"` + gone[1] + `"}`)
if _, ok := ReadAdvisory("$JS.EVENT.ADVISORY.CONSUMER.DELETED."+gone[0]+"."+gone[1], body, func(s, c string) string { return c }); ok {
t.Errorf("the removal of %s.%s is said as a consumer lost", gone[0], gone[1])
}
}
if _, ok := ReadAdvisory("$JS.EVENT.ADVISORY.CONSUMER.DELETED.DEAD_LETTER_NOTICES.controller",
[]byte(`{"stream":"DEAD_LETTER_NOTICES","consumer":"controller"}`), func(s, c string) string { return c }); !ok {
t.Error("the removal of the controller's consumer on DEAD_LETTER_NOTICES is not read")
}
w.Advised(a)
w.Advised(a)
k := "bus.EVENTS.ace_web.consumer-lost"
@@ -1,80 +0,0 @@
package watch
import (
"strings"
)
// **The lease write's findings** (novox/hq ADR 0582 clauses 4 and 6, issue 774): mesh-grants says lease-altered when the
// lease key or its bucket's expiry differs from what it wrote, and removal-failed when the day-later removal of the
// controller's consumer of DEAD_LETTER_NOTICES or the stream ASSIGNMENTS failed; the watchman raises each as a finding.
// lease-altered clears when lease-handed is said again (the operator asked the write again); removal-failed clears when
// what it names reads gone: the consumer's deletion advisory and ASSIGNMENTS' deletion advisory, each heard.
// The two kinds.
const (
KindLeaseAltered = "lease-altered"
KindRemovalFailed = "removal-failed"
)
// The finding's IDs: the lease's bucket, and the removal.
const (
leaseID = "mesh-controller_lease"
removalID = "lease-removal"
)
// What a failed removal still waits on.
const (
RemovalConsumer = "DEAD_LETTER_NOTICES.controller"
RemovalStream = "ASSIGNMENTS"
)
// LeaseAltered raises the finding that the lease differs from what mesh-grants wrote, naming what changed.
func (w *Watchman) LeaseAltered(what string) {
w.mu.Lock()
defer w.mu.Unlock()
w.find(Finding{Scope: ScopeBus, ID: leaseID, Kind: KindLeaseAltered, Severity: Warning, Source: "mesh-grants",
Summary: "the controller's lease differs from what mesh-grants wrote: " + what,
Said: what,
Needs: "a look at who changed the lease; mesh-grants writes nothing until lease-write is asked again.",
Resolved: "lease-write is asked and warranted again, and the lease reads as written"})
}
// LeaseHanded clears the lease-altered finding: the lease reads as written again.
func (w *Watchman) LeaseHanded() {
w.mu.Lock()
defer w.mu.Unlock()
w.unfind(FindingKey(ScopeBus, leaseID, KindLeaseAltered))
}
// RemovalFailed raises the finding that the day-later removal failed, naming what failed; it waits on each of the
// consumer and the stream the failure names.
func (w *Watchman) RemovalFailed(failed string) {
w.mu.Lock()
defer w.mu.Unlock()
if w.removalWaits == nil {
w.removalWaits = map[string]bool{}
}
for _, what := range []string{RemovalConsumer, RemovalStream} {
if strings.Contains(failed, strings.SplitN(what, ".", 2)[0]) {
w.removalWaits[what] = true
}
}
if len(w.removalWaits) == 0 { // names neither: waits on both
w.removalWaits[RemovalConsumer], w.removalWaits[RemovalStream] = true, true
}
w.find(Finding{Scope: ScopeBus, ID: removalID, Kind: KindRemovalFailed, Severity: Warning, Source: "mesh-grants",
Summary: "the day-later removal of the controller's consumer and stream failed: " + failed,
Said: failed,
Needs: "a look at why; mesh-grants tries again in a day, and grants_removal_due starts it sooner.",
Resolved: "the controller's consumer of DEAD_LETTER_NOTICES and the stream ASSIGNMENTS read gone"})
}
// RemovalGone hears one of the two removed (RemovalConsumer or RemovalStream), and clears the finding once neither waits.
func (w *Watchman) RemovalGone(what string) {
w.mu.Lock()
defer w.mu.Unlock()
delete(w.removalWaits, what)
if len(w.removalWaits) == 0 {
w.unfind(FindingKey(ScopeBus, removalID, KindRemovalFailed))
}
}
@@ -86,8 +86,6 @@ type Watchman struct {
recount func()
// waiting is, by `<consumer>.<node>`, each of its provisions in waits, with its words.
waiting map[string]map[string]string
// removalWaits is what a failed removal of the lease write still waits on to read gone (lease.go).
removalWaits map[string]bool
// health is each node's own health report, and tunnels each node's tunnel key from its record.
health map[string]delivery.NodeHealth
tunnels map[string]string
@@ -158,13 +158,6 @@ func checkTunnelKey(k string) error {
return nil
}
// issuing says whether issue is on, and why: always, since the controller ended by its unassignment, never by the lease
// write (issue 1126), so the seat is the only issuer (ADR 0587 §4). The lease is no longer read: mesh-sdk's
// buscompose.LeaseHandedAway answers false for the absent key, which would keep issuing off for good.
func (h *Holder) issuing() (bool, string) {
return true, "the seat is the only issuer: the controller ended by its unassignment (issue 1126)"
}
// openEnrolments reads every node key of the bucket.
func (h *Holder) openEnrolments() (map[string]Row, map[string]Enrolment, error) {
keys, err := h.Store.Keys()
@@ -26,12 +26,6 @@ type TokenRow struct {
Unsealed string `json:"unsealed,omitempty"`
}
// Issuing is whether issue is on, and why.
type Issuing struct {
On bool `json:"on"`
Why string `json:"why"`
}
// Fill is how full the bucket is: its keys and the bytes of their values (the state's cap is the bus's, which no bundle
// can read, so the fill is stated without it).
type Fill struct {
@@ -42,14 +36,13 @@ type Fill struct {
// TokensAnswer is what `tokens` answers.
type TokensAnswer struct {
Open []TokenRow `json:"open"`
Issuing Issuing `json:"issuing"`
Fill Fill `json:"fill"`
History []json.RawMessage `json:"history,omitempty"`
// HistoryFrom says where the history comes from, when one was asked for.
HistoryFrom string `json:"history-from,omitempty"`
}
// Tokens answers one open enrolment or all, with the bucket's fill, whether issuing is on and why, and with history the
// Tokens answers one open enrolment or all, with the bucket's fill, and with history the
// ended enrolments the holder's own events say (ADR 0587 §8).
func (h *Holder) Tokens(node string, history bool) (TokensAnswer, error) {
rows, open, err := h.openEnrolments()
@@ -57,8 +50,6 @@ func (h *Holder) Tokens(node string, history bool) (TokensAnswer, error) {
return TokensAnswer{}, err
}
var out TokensAnswer
on, why := h.issuing()
out.Issuing = Issuing{On: on, Why: why}
if keys, err := h.Store.Keys(); err == nil {
out.Fill.Keys = len(keys)
for _, k := range keys {
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"slices"
"strings"
"testing"
"time"
@@ -25,7 +26,7 @@ func (r *rig) seed(node, address string, stage string) {
}
}
// 774/S54/AC1 and AC2: tokens answers each stage with its expiry, ask and revision, the fill and whether issuing is on;
// 774/S54/AC1 and AC2: tokens answers each stage with its expiry, ask and revision, and the fill;
// for one node at live it relays the vault's sealed token and keeps nothing; and the manifest says what the code does.
func tokensAnswersEachStageAndRelaysTheSealedTokenOfOneNode(t *testing.T) {
r := newRig(t)
@@ -43,8 +44,8 @@ func tokensAnswersEachStageAndRelaysTheSealedTokenOfOneNode(t *testing.T) {
t.Errorf("row %+v lacks revision, expiry or ask, or relays a token for a list of all", row)
}
}
if !slices.Equal(stages, []string{"ace:asked", "g14:live", "novox:redeeming"}) || all.Fill.Keys != 6 || !all.Issuing.On {
t.Errorf("tokens %v, fill %+v, issuing %+v", stages, all.Fill, all.Issuing)
if !slices.Equal(stages, []string{"ace:asked", "g14:live", "novox:redeeming"}) || all.Fill.Keys != 6 {
t.Errorf("tokens %v, fill %+v", stages, all.Fill)
}
one, err := r.h.Tokens("g14", false)
if err != nil || len(one.Open) != 1 || one.Open[0].Sealed != "sealed-for-g14" {
@@ -59,11 +60,11 @@ func tokensAnswersEachStageAndRelaysTheSealedTokenOfOneNode(t *testing.T) {
}
}
}
// 1126/A2/AC1: with no lease key issuing is on, and says why.
// 1126/B9 (ADR 0590 clause 4): the seat is the only issuer since the controller's unassignment, so tokens says no
// gate of issuing any more: nothing turns it off.
on, _ := r.h.Tokens("", false)
mustContain(t, on.Issuing.Why, "unassignment")
if !on.Issuing.On {
t.Errorf("issuing is off with no lease key: %s", on.Issuing.Why)
if raw, _ := json.Marshal(on); strings.Contains(string(raw), `"issuing"`) {
t.Errorf("tokens still says a gate of issuing: %s", raw)
}
}
@@ -106,7 +106,7 @@ func Tools(h *Holder) []stdio.Tool {
Run: func(a map[string]any) (any, error) { return h.Again(arg(a, "node")) }},
{Name: TokensTool,
Description: "What the seat holds (novox/hq ADR 0587 §8): every open enrolment, or one node's, with its stage (asked, live or " +
"redeeming), address, expiry, ask, revision and age; whether issuing is on and why; the bucket's fill. With a node at stage live, the token sealed to the operator key, asked of the vault at " +
"redeeming), address, expiry, ask, revision and age; the bucket's fill. With a node at stage live, the token sealed to the operator key, asked of the vault at " +
"this moment and relayed as the vault sealed it: the one route by which the operator gets a token, which this seat never " +
"reads and keeps nowhere. With history=true the ended enrolments its events say. A machine stuck at redeeming shows here " +
"with its age; revoke ends it. Read-only.",