D1 (watchman): the watchman names its own module from its credential's

Its finding observer, its dead-letter consumer, its reader's module and its tools' first views take the credential's module (the tools: the module the runtime serves them as) instead of constants of its name; a guard test finds none in its code.

Builds: design 47 G A-296, §428

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 22:46:17 +01:00
parent 2a85e55d4e
commit d7b53bf2f1
14 changed files with 82 additions and 46 deletions
@@ -209,7 +209,7 @@ func Tools(states func(string) state, ask func(socket.Ask) (json.RawMessage, err
Description: "Which part waits for which view, on which machine and since when (design 47 G §83): every open condition of the " +
"kind view-waits the condition keeper holds, which each part writes when it has waited for a view past its bound. A view " +
"a part holds is not listed. Ask the part's <module>_first_view_again to ask a view again. Read-only."},
}, views.StatusTools(watch.Observer, ToolFirstViews, ToolFirstViewAgain, stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...)
}, views.StatusTools(os.Getenv(stdio.ServedAs), ToolFirstViews, ToolFirstViewAgain, stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...)
}
// waitingView is one part waiting for one view, as watchman_views answers it.
@@ -67,7 +67,7 @@ accounts {
// families (DLV-M5, ADR 0615): the watchman (n1) follows both, never their buckets.
func serveOwners(t *testing.T, ctx context.Context, js jetstream.JetStream, nc *nats.Conn) {
t.Helper()
viewstest.Serve(t, ctx, js, nc, watch.AssignerModule, wantsState, watch.AssignerModule+".assigner_read_wants", "n1_"+watch.WatchmanModule)
viewstest.Serve(t, ctx, js, nc, watch.AssignerModule, wantsState, watch.AssignerModule+".assigner_read_wants", "n1_"+watchmanModule)
}
@@ -95,12 +95,12 @@ func sayAsk(t *testing.T, ctx context.Context, js jetstream.JetStream, event, id
// own and firstViewsBucket are the beds' watchman's own buckets, named from the module its credential names (A-296).
var (
own = watch.BucketsOf(watch.WatchmanModule)
firstViewsBucket = watch.WatchmanModule + "_" + views.StatusState
own = watch.BucketsOf(watchmanModule)
firstViewsBucket = watchmanModule + "_" + views.StatusState
)
// findingEventsSubject is the subject the stand-in seat mesh-broker's show answers for the watchman's finding-events.
const findingEventsSubject = "mesh.bus." + watch.WatchmanModule + ".finding-events"
const findingEventsSubject = "mesh.bus." + watchmanModule + ".finding-events"
// findingsStream is the watchman's own stream of its findings' events, its requires entry finding-events (design 47 G
// §383, §529), which its finding writer says into under the names a stand-in of the seat mesh-broker's show answers (A-274).
@@ -172,7 +172,7 @@ func TestWatchmanWhole(t *testing.T) { // 1135/AC8
{Bucket: own.Stuck, History: 20},
{Bucket: own.Pulse, LimitMarkerTTL: time.Second},
{Bucket: watch.AssignerWantsBucket, History: 20},
{Bucket: watch.FindingsBucket, History: 5},
{Bucket: (watchmanModule + "_findings"), History: 5},
{Bucket: firstViewsBucket, History: 1},
} {
kv, err := js.CreateKeyValue(ctx, c)
@@ -315,7 +315,7 @@ func TestWatchmanWhole(t *testing.T) { // 1135/AC8
}
// 1952/WM4: what stopped moving is the watchman's own finding: the ask no node ended, and node n1 silent.
finding := func(key string) bool { _, ok := has(watch.FindingsBucket, key); return ok }
finding := func(key string) bool { _, ok := has((watchmanModule + "_findings"), key); return ok }
until("the stuck ask's finding", func() bool {
return finding(watch.FindingKey(watch.ScopeDelivery, stuckOf(askStream, ask1), watch.KindStuck))
})
@@ -334,14 +334,14 @@ func TestWatchmanWhole(t *testing.T) { // 1135/AC8
// wait; the consumer removed, it makes its own and keeps a notice with its headers.
waits := watch.FindingKey(watch.ScopeBus, "DEAD_LETTER_NOTICES.controller", watch.KindNoticesWaiting)
until("the notices said waiting behind the controller's consumer", func() bool { return finding(waits) })
if _, err := js.Consumer(ctx, buscompose.DeadLetterNoticesStream, deadletters.Consumer); err == nil {
if _, err := js.Consumer(ctx, buscompose.DeadLetterNoticesStream, watchmanModule); err == nil {
t.Fatal("the watchman made its consumer while the controller's stands")
}
if err := js.DeleteConsumer(ctx, buscompose.DeadLetterNoticesStream, buscompose.ControllerName); err != nil {
t.Fatal(err)
}
until("the watchman's own consumer on the notices", func() bool {
_, err := js.Consumer(ctx, buscompose.DeadLetterNoticesStream, deadletters.Consumer)
_, err := js.Consumer(ctx, buscompose.DeadLetterNoticesStream, watchmanModule)
return err == nil
})
until("the wait no longer said", func() bool { return !finding(waits) })
@@ -420,7 +420,7 @@ func anAskRunningBeforeTheStartGetsItsPulse(t *testing.T) { // 1909
{Bucket: own.Stuck, History: 20},
{Bucket: own.Pulse, LimitMarkerTTL: time.Second},
{Bucket: watch.AssignerWantsBucket, History: 20},
{Bucket: watch.FindingsBucket, History: 5},
{Bucket: (watchmanModule + "_findings"), History: 5},
{Bucket: firstViewsBucket, History: 1},
} {
kv, err := js.CreateKeyValue(ctx, c)
@@ -501,7 +501,7 @@ func aWantIsMirroredAsAPulseAndTheHaveNamingItEndsIt(t *testing.T) {
{Bucket: own.Stuck, History: 20},
{Bucket: own.Pulse, LimitMarkerTTL: time.Second},
{Bucket: watch.AssignerWantsBucket, History: 20},
{Bucket: watch.FindingsBucket, History: 5},
{Bucket: (watchmanModule + "_findings"), History: 5},
{Bucket: firstViewsBucket, History: 1},
} {
kv, err := js.CreateKeyValue(ctx, c)
@@ -255,7 +255,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
return fmt.Errorf("the watchman's finding writer: %w", err)
}
defer held.w.Close()
reader := &views.ProcessReader{Module: watch.WatchmanModule, Call: calls.Call, Logf: logger.Printf, Hub: hub,
reader := &views.ProcessReader{Module: cred.Module, Call: calls.Call, Logf: logger.Printf, Hub: hub,
Findings: func() views.WireWriter { return held.w }}
wants, err := reader.Follow(watch.AssignerModule, wantsState, watch.AssignerModule+".assigner_read_wants")
if err != nil {
@@ -369,7 +369,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
}()
})
}
dead := &deadKeeper{ctx: ctx, js: js, w: w, logf: logger.Printf}
dead := &deadKeeper{ctx: ctx, js: js, w: w, logf: logger.Printf, module: cred.Module}
w.SetRecount(dead.count)
dead.count()
dead.ages()
@@ -54,7 +54,7 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
})
t.Run("DLV-W1: the consumed events reach the watchman by their subjects", func(t *testing.T) {
out := &findingsOut{findings: map[string]bool{}}
w := watch.New(watch.WatchmanModule, out, time.Now)
w := watch.New(watchmanModule, out, time.Now)
say := func(string, ...any) {}
// The bus module's own stream is its history, never heard here: only the seat's subject (ruling 13:0x).
consumed(w, "mesh.stream.nats.event.refused", []byte(`{"module":"m","local":"l","why":"no room"}`), say)
@@ -77,7 +77,7 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
})
t.Run("design 47 G §500: the indexer's manifest-refused reaches the watchman by its subject", func(t *testing.T) {
out := &findingsOut{findings: map[string]bool{}}
w := watch.New(watch.WatchmanModule, out, time.Now)
w := watch.New(watchmanModule, out, time.Now)
consumed(w, "mesh.seat.mesh-catalog.event.manifest-refused", []byte(`{"module":"m","digest":"d","problems":["x"]}`), func(string, ...any) {})
if !out.findings[watch.FindingKey(watch.ScopeModule, "m", watch.KindManifestRefused)] {
t.Fatalf("a refused manifest reached %v", out.findings)
@@ -85,7 +85,7 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
})
t.Run("design 47 G §499, §541: a node-engine's wait-held and wait-ended reach the watchman by their subjects", func(t *testing.T) {
out := &findingsOut{findings: map[string]bool{}}
w := watch.New(watch.WatchmanModule, out, time.Now)
w := watch.New(watchmanModule, out, time.Now)
say := func(string, ...any) {}
body := fmt.Sprintf(`{"step":"compose","reason":"entry-missing","since":%q}`, time.Now().Add(-2*time.Hour).UTC().Format(time.RFC3339))
consumed(w, "mesh.seat.node-engine.event.wait-held.ace.web", []byte(body), say)
@@ -99,7 +99,7 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
})
t.Run("design 47 G A-192: a module's wait-held on its stream, naming a part, reaches the watchman", func(t *testing.T) {
out := &findingsOut{findings: map[string]bool{}}
w := watch.New(watch.WatchmanModule, out, time.Now)
w := watch.New(watchmanModule, out, time.Now)
body := fmt.Sprintf(`{"step":"building","part":"mesh-sdk@v0.1.14","module":"web","fingerprint":"src2:f","bound":"1h0m0s","since":%q}`, time.Now().Add(-2*time.Hour).UTC().Format(time.RFC3339))
consumed(w, "mesh.stream.mesh-delivery-builder.event.wait-held.web.src2:f", []byte(body), func(string, ...any) {})
if len(out.findings) != 1 {
@@ -120,7 +120,7 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
"unreadable": func(string, any) (json.RawMessage, error) { return json.RawMessage(`{"waits":`), nil },
} {
out := &findingsOut{findings: map[string]bool{}}
w := watch.New(watch.WatchmanModule, out, time.Now)
w := watch.New(watchmanModule, out, time.Now)
const key = "m.00000000000000000042"
old := watch.Finding{Scope: watch.ScopeDelivery, ID: key, Kind: watch.KindTakeInWaits, Severity: watch.Warning, Source: "take-in",
Since: time.Now().Add(-3 * time.Hour), Summary: "held", Said: "held", Headline: "held", Explanation: "held", Needs: "a take-in"}
@@ -146,7 +146,7 @@ func TestRunNeedsItsCredential(t *testing.T) { // 1135/AC6
// The watchman's want wait starts at the entry's time (createdOr): through mesh-sdk's views.ProcessReader it is
// the time the owner's bus wrote the key, never the watchman's start.
written := time.Date(2026, 11, 4, 9, 0, 0, 0, time.UTC)
r := &views.ProcessReader{Module: watch.WatchmanModule, Call: func(string, any) (json.RawMessage, error) {
r := &views.ProcessReader{Module: watchmanModule, Call: func(string, any) (json.RawMessage, error) {
return json.Marshal(views.Answer{V: views.Version, Owner: watch.AssignerModule, State: wantsState, Revision: 3, Created: written,
At: written.Add(time.Hour), Values: map[string]json.RawMessage{"ace.mail": json.RawMessage(`{}`)}, Written: map[string]time.Time{"ace.mail": written}})
}}
@@ -0,0 +1,4 @@
package main
// watchmanModule is the module the tests run the watchman as, as its credential would say it.
const watchmanModule = "mesh-delivery-watchman"
@@ -145,6 +145,8 @@ type deadKeeper struct {
js jetstream.JetStream
w *watch.Watchman
logf func(string, ...any)
// module is the watchman's module, its credential's: the name of its own consumer on DEAD_LETTER_NOTICES.
module string
mu sync.Mutex
taking jetstream.ConsumeContext
@@ -218,7 +220,7 @@ func (d *deadKeeper) check() {
if !take || d.taking != nil {
return
}
cc, err := deadletters.TakeNotices(d.ctx, d.js, func(deadletters.DeadLetter) { go d.count() }, d.logf)
cc, err := deadletters.TakeNotices(d.ctx, d.js, d.module, func(deadletters.DeadLetter) { go d.count() }, d.logf)
if err != nil {
d.logf("the dead-letter notices cannot be taken yet: %v", err)
return
@@ -3,7 +3,7 @@
// changed in what is kept, aged or withheld, on the jetstream API.
//
// 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
// expires, so the watchman makes its own (its module's name) 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
@@ -23,9 +23,6 @@ import (
"git.novox.be/novox/mesh-sdk/go/buscompose"
)
// Consumer is the watchman's own consumer on DEAD_LETTER_NOTICES, made only by Gate's leave.
const Consumer = "mesh-delivery-watchman"
// The headers a kept message carries beside its own (mesh-controller link.DeadHeader*: the same names, so the
// controller's copies and the watchman's are read alike).
const (
@@ -332,11 +329,11 @@ func ControllerStands(ctx context.Context, js jetstream.JetStream) (bool, error)
// TakeNotices makes the watchman's own durable consumer on DEAD_LETTER_NOTICES (never touching `controller`) and keeps
// each notice it takes; a notice not kept is handed back to be taken again. kept hears each one kept.
func TakeNotices(ctx context.Context, js jetstream.JetStream, kept func(DeadLetter), logf func(string, ...any)) (jetstream.ConsumeContext, error) {
func TakeNotices(ctx context.Context, js jetstream.JetStream, consumer string, kept func(DeadLetter), logf func(string, ...any)) (jetstream.ConsumeContext, error) {
c, err := js.CreateOrUpdateConsumer(ctx, buscompose.DeadLetterNoticesStream, jetstream.ConsumerConfig{
Durable: Consumer, AckPolicy: jetstream.AckExplicitPolicy, AckWait: time.Minute})
Durable: consumer, AckPolicy: jetstream.AckExplicitPolicy, AckWait: time.Minute})
if err != nil {
return nil, fmt.Errorf("the consumer %s on %s cannot be made: %w", Consumer, buscompose.DeadLetterNoticesStream, err)
return nil, fmt.Errorf("the consumer %s on %s cannot be made: %w", consumer, buscompose.DeadLetterNoticesStream, err)
}
return c.Consume(func(m jetstream.Msg) {
d, err := Keep(ctx, js, m.Data())
@@ -16,12 +16,6 @@ import (
// through mesh-sdk's finding writer (design 47 G §689), only when what it says changes, and it deletes the key when it no longer observes it. The condition keeper (mesh-conditions) raises and
// clears the conditions from them; the watchman writes no condition and reads none (Limits 11).
// FindingsBucket is the watchman's findings.
var FindingsBucket = Observer + "_findings"
// Observer is the name a finding of the watchman carries.
const Observer = "mesh-delivery-watchman"
// The scopes and severities a finding of the watchman names, as the controller's conditions name them, so the keeper's
// `compare` meets the same keys.
const (
@@ -91,7 +85,7 @@ func (f Finding) Contract() condseat.Finding {
id = id + "." + KeyID(ref)
}
v := condseat.Finding{Scope: f.Scope, ID: id, Kind: f.Kind, Ref: ref, Severity: f.Severity, Summary: f.Summary,
Headline: f.Headline, Explanation: f.Explanation, Needs: f.Needs, Source: f.Source, Observer: Observer,
Headline: f.Headline, Explanation: f.Explanation, Needs: f.Needs, Source: f.Source, Observer: f.Observer,
At: f.Since.UTC(), Since: f.Since.UTC(),
Subject: condseat.Subject{Scope: f.Scope, ID: id, Machine: f.Machine, Also: f.Also}}
if f.Said != "" {
@@ -124,6 +118,7 @@ func DecodeFinding(data []byte) (Finding, error) {
// find writes a finding when what it says differs from what the bucket holds (ADR 0289 §1).
func (w *Watchman) find(f Finding) {
f.Observer = w.module // the watchman of the module its credential names
value, err := EncodeFinding(f)
if err != nil {
w.say("no finding: %v", err)
@@ -196,10 +191,10 @@ func (w *Watchman) Rewrite() int {
func (w *Watchman) NotHeard(family, consequence string, why error) {
w.mu.Lock()
defer w.mu.Unlock()
summary, headline, explanation := heard.Words(Observer, family, consequence, why)
summary, headline, explanation := heard.Words(w.module, family, consequence, why)
w.say("%s", summary)
w.find(Finding{Scope: ScopeModule, ID: heard.ID(Observer, family), Token: heard.Kind, Kind: heard.Kind, Severity: Warning,
Source: Observer + "." + heard.Kind, Since: w.now().UTC().Truncate(time.Second), Summary: summary, Headline: headline,
w.find(Finding{Scope: ScopeModule, ID: heard.ID(w.module, family), Token: heard.Kind, Kind: heard.Kind, Severity: Warning,
Source: w.module + "." + heard.Kind, Since: w.now().UTC().Truncate(time.Second), Summary: summary, Headline: headline,
Explanation: explanation})
}
@@ -207,5 +202,5 @@ func (w *Watchman) NotHeard(family, consequence string, why error) {
func (w *Watchman) Heard(family string) {
w.mu.Lock()
defer w.mu.Unlock()
w.unfind(heard.Key(Observer, family))
w.unfind(heard.Key(w.module, family))
}
@@ -95,7 +95,7 @@ func TestTheWatchmansFindings(t *testing.T) {
}, map[string]string{FindingKey(ScopeDelivery, revKey, KindTakeInWaits): Warning}},
{"1966/AC195-5: a seed ends the finding and the held wait of a key indexer_pending no longer names, and keeps one it names with its own since", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
// A previous run's finding for revKey stands in the bucket (read at the start); a wait of staleKey is held from an event.
old := Finding{Scope: ScopeDelivery, ID: revKey, Kind: KindTakeInWaits, Severity: Warning, Source: "take-in", Since: t0.Add(-3 * time.Hour), Observer: Observer, Summary: "held", Said: "held", Headline: "held", Explanation: "held", Needs: "a take-in"}
old := Finding{Scope: ScopeDelivery, ID: revKey, Kind: KindTakeInWaits, Severity: Warning, Source: "take-in", Since: t0.Add(-3 * time.Hour), Observer: testModule, Summary: "held", Said: "held", Headline: "held", Explanation: "held", Needs: "a take-in"}
value, err := EncodeFinding(old)
if err != nil {
t.Fatal(err)
@@ -262,7 +262,7 @@ func TestTheWatchmansFindings(t *testing.T) {
}, map[string]string{refusedKey("mesh-knowledge", "gone"): Urgent}},
{"ADR 0615 §5 (review round 1): a consumer the watchman cannot bind is family-not-heard naming its stream, until it binds", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
w.NotHeard("EVENTS", "what it carries is heard once the grants part has made the consumer n1_mesh-delivery-watchman", errors.New("consumer not found"))
f := o.findings[heard.Key(Observer, "EVENTS")]
f := o.findings[heard.Key(testModule, "EVENTS")]
if f.Kind != heard.Kind || f.Severity != Warning || !strings.Contains(f.Summary, "does not hear EVENTS") || !strings.Contains(f.Summary, "consumer not found") {
t.Fatalf("findings = %v", o.findings)
}
@@ -475,8 +475,8 @@ func TestTheWatchmansFindings(t *testing.T) {
}, map[string]string{}},
{"WM4: a finding read at the start and not observed again is deleted; a signal's stays", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
for _, f := range []Finding{
{Scope: ScopeDelivery, ID: "x.y", Kind: KindStuck, Severity: Warning, Summary: "old", Source: "stuck"},
{Scope: ScopeMachine, ID: "ace", Kind: KindToolsSilent, Severity: Warning, Summary: "silent", Source: "S11"},
{Scope: ScopeDelivery, ID: "x.y", Kind: KindStuck, Severity: Warning, Summary: "old", Source: "stuck", Observer: testModule},
{Scope: ScopeMachine, ID: "ace", Kind: KindToolsSilent, Severity: Warning, Summary: "silent", Source: "S11", Observer: testModule},
} {
v, _ := EncodeFinding(f)
o.findings[f.Key()], _ = DecodeFinding(v)
@@ -535,7 +535,7 @@ func TestTheWatchmansFindings(t *testing.T) {
got := map[string]string{}
for k, f := range o.findings {
got[k] = f.Severity
if f.Key() != k || f.Observer != Observer {
if f.Key() != k || f.Observer != testModule {
t.Errorf("finding %s = %+v", k, f)
}
}
@@ -8,7 +8,6 @@ import (
// states and the tools it invokes) and the seats' state names; a unit row checks them against it (TestManifestStateAndReads).
// No contract names a holder (novox/hq ADR 0625 §4, ADR 0634 §1).
const (
WatchmanModule = "mesh-delivery-watchman"
AssignerModule = "mesh-delivery-assigner"
// ProvisionsBucket is the seat mesh-provisioner's state provisions (reads: mesh-provisioner.provisions).
ProvisionsBucket = "mesh-provisioner_provisions"
@@ -56,6 +56,7 @@ type pulse struct {
// safe to call from several watches at once.
type Watchman struct {
mu sync.Mutex
module string // the module it runs as, its credential's (A-296)
own OwnBuckets // its own buckets, named from its credential's module (A-296)
out Out
now func() time.Time
@@ -123,7 +124,7 @@ type Watchman struct {
// New is the watchman of module (its credential's), writing to out, reading the time from now.
func New(module string, out Out, now func() time.Time) *Watchman {
return &Watchman{own: BucketsOf(module), out: out, now: now, counts: map[string]int{}, presence: map[string]presence{},
return &Watchman{module: module, own: BucketsOf(module), out: out, now: now, counts: map[string]int{}, presence: map[string]presence{},
pulses: map[string]*pulse{}, stuck: map[string]meshwatchdogsclient.Stuck{}, wants: map[string]string{}, haves: map[string]string{}, held: map[string]bool{}, starts: map[string]startMark{}, bound: meshwatchdogsclient.PulseMoveBound, stepBound: StepBound, progressBound: ProgressBound,
findings: map[string][]byte{}, seededFindings: map[string]bool{}, signals: map[string]signalDue{}, busCreated: map[string]bool{}, takeInHeld: map[string]Finding{}, pendingBranch: map[string]string{}, takenEntries: map[string][]string{}, recordMilestone: map[string]string{}, away: map[string]bool{},
advised: map[string]int{}, advisedSince: map[string]time.Time{},
@@ -168,7 +168,7 @@ func TestManifestStateAndReads(t *testing.T) { // 1135/AC6
return e
}
for name, row := range map[string]struct{ got, want string }{
"module": {watch.WatchmanModule, m.Module},
"module": {watchmanModule, m.Module},
// A-296: its own buckets are named from its credential's module, each a state its manifest keeps.
"presence": {watch.BucketsOf("a-watchman").Presence, "a-watchman_" + m.State[0].Name},
"pulse": {watch.BucketsOf("a-watchman").Pulse, "a-watchman_" + m.State[1].Name},
@@ -0,0 +1,4 @@
package manifest
// watchmanModule is the module the manifest names, which the tests run the watchman as (its credential would say it).
const watchmanModule = "mesh-delivery-watchman"
@@ -0,0 +1,34 @@
package manifest
import (
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
// No code of the watchman names its own module: its buckets, its consumer, its finding observer and its tools' names take the
// module its credential (or the runtime that serves its tools) names (design 47 G A-296, §428). The manifest alone says it.
func TestNoCodeNamesTheWatchmansOwnModule(t *testing.T) {
name := regexp.MustCompile(`"` + regexp.QuoteMeta(watchmanModule) + `(_[^"]*)?"`)
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return err
}
src, err := os.ReadFile(path)
if err != nil {
return err
}
for i, line := range strings.Split(string(src), "\n") {
if !strings.HasPrefix(strings.TrimSpace(line), "//") && name.MatchString(line) {
t.Errorf("%s:%d names the watchman's own module: %s", path, i+1, strings.TrimSpace(line))
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}