caps: channel-neutrality assertion — canary/* in, canary/* out
Plan Task 9's laddercheck-style pin: nothing in this design may consult, constrain, or be constrained by a channel name, the same promise internal/release/laddercheck_test.go holds for the ladder itself. A capability change that refused (or even noticed) one of production's live channels would stop deploys. Two halves, because neither alone is the whole claim. Structural: no identifier or literal in internal/caps's own PRODUCTION source (never its tests) says "channel" or "canary" outside a comment, checked with go/parser rather than grep so a doc comment stating the rule can never trip the test that states it — currently zero hits, comments included. Behavioural: this package's inputs (Plan, Set, Grants, Ops) carry no channel field at all, and RenderService/RenderSocket take (app, *Set, Grants) only, never a Host — so converging the same declared+granted set under a Host named for each of production's four live channels produces byte-identical unit text. The channel name is sanitized into a legal hostname only so the loop has something to converge under; it is never a render input. liveChannels is copied from laddercheck_test.go's list rather than imported — internal/caps must not depend on internal/release, and importing this test package the other way would prove nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+141
−0
internal/caps/neutrality_test.go+141 −0
diff --git a/internal/caps/neutrality_test.go b/internal/caps/neutrality_test.go| new file mode 100644 |
| index 000000000..c879e6aa3 |
| --- /dev/null |
| +++ b/internal/caps/neutrality_test.go |
| @@ -0,0 +1,141 @@ |
| +package caps |
| + |
| +import ( |
| + "bytes" |
| + "go/parser" |
| + "go/token" |
| + "os" |
| + "path/filepath" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// liveChannels mirrors internal/release/laddercheck_test.go's list — the |
| +// channels production is running on RIGHT NOW. Copied rather than imported: |
| +// internal/caps must not import internal/release (and importing this test |
| +// package from there would prove the wrong direction), so four literal |
| +// strings are cheaper than restructuring either package to share them. If |
| +// laddercheck_test.go's list ever changes, this one should be re-copied by |
| +// hand — a shared constant would be one more thing this package would need |
| +// to import from outside itself. |
| +var liveChannels = []string{"canary/platform-adoption", "canary/keymail", "canary/console", "canary/rehearsal"} |
| + |
| +// TestCapabilitiesNeverConsultAChannelName is this package's half of the |
| +// design's channel-neutrality claim (spec: "Nothing in this change may |
| +// consult, constrain, or be constrained by a channel name" — the same |
| +// promise internal/release/laddercheck_test.go pins for the ladder itself). |
| +// canary/* in, canary/* out — the fleet runs on those channels and a |
| +// capability change that refused, favoured, or even NOTICED one would stop |
| +// deploys production depends on. |
| +// |
| +// Two halves, because neither alone is the whole claim: |
| +// |
| +// - structural: no identifier or literal in this package's own PRODUCTION |
| +// source (not its tests) says "channel" or "canary" outside a comment. |
| +// Parsed with go/parser rather than grepped so that a doc comment |
| +// EXPLAINING channel-neutrality (as this very comment does) can never |
| +// trip the test that states it. |
| +// - behavioural: this package's inputs — Plan, Set, Grants, Ops — carry no |
| +// channel field to begin with, and RenderService/RenderSocket take |
| +// (app, *Set, Grants) only, never a Host and never a channel. So a route |
| +// "on" any of production's four live channels converges byte-identical |
| +// unit text for the same declared and granted capabilities. Converging |
| +// the same Plan under a Host named for each channel and diffing the |
| +// rendered bytes (never the path, which DOES vary by host) is what makes |
| +// that a test rather than an assertion about code nobody re-reads. |
| +func TestCapabilitiesNeverConsultAChannelName(t *testing.T) { |
| + t.Run("structural", func(t *testing.T) { |
| + assertNoChannelWord(t, ".") |
| + }) |
| + t.Run("behavioural", func(t *testing.T) { |
| + s := &Set{ |
| + V: Version, Command: "relay", Daemon: true, Children: true, RuntimeDir: "keymail", |
| + Ports: []Port{{Port: 25, Name: "mx"}}, |
| + Data: &Data{Replicate: DataReplicateApp}, |
| + HostRole: true, |
| + } |
| + g := Grants{Ports: map[int]bool{25: true}, HostRole: true} |
| + |
| + var first []byte |
| + var firstChannel string |
| + for _, ch := range liveChannels { |
| + // The channel name is sanitized into a legal hostname ONLY so this |
| + // loop has something host-shaped to converge under. Host is not a |
| + // render input — RenderService and RenderSocket never take one — |
| + // so this is not smuggling the channel in through the back door; |
| + // it is the strongest form of "cannot reach it" available: even a |
| + // value DERIVED from the channel name, sitting in the one plan |
| + // field a channel might plausibly have leaked into, changes |
| + // nothing about what gets written. |
| + host := "relay-" + strings.NewReplacer("canary/", "", "/", "-").Replace(ch) + ".example.com" |
| + etc := t.TempDir() |
| + o := Ops{ |
| + EtcDir: etc, |
| + StateRoot: t.TempDir(), |
| + Systemctl: func(...string) error { return nil }, |
| + Query: func(...string) bool { return false }, |
| + EnsureUser: func(string) error { return nil }, |
| + } |
| + p := Plan{Host: host, App: "keymail", Set: s, Grants: g} |
| + if _, err := Converge(o, p); err != nil { |
| + t.Fatalf("converge for channel %q: %v", ch, err) |
| + } |
| + svcPath, _ := DropInPaths(etc, host) |
| + body, err := os.ReadFile(svcPath) |
| + if err != nil { |
| + t.Fatalf("read rendered drop-in for channel %q: %v", ch, err) |
| + } |
| + if first == nil { |
| + first, firstChannel = body, ch |
| + continue |
| + } |
| + if !bytes.Equal(first, body) { |
| + t.Errorf("materialized capabilities differ between channel %q and %q for an identical declared+granted set:\n%s\n---\n%s", |
| + firstChannel, ch, first, body) |
| + } |
| + } |
| + }) |
| +} |
| + |
| +// assertNoChannelWord fails if any non-test .go file directly in dir mentions |
| +// "channel" or "canary" outside a comment. Comments are found with |
| +// go/parser's ParseComments and blanked out of the source before the search, |
| +// so a doc comment stating the neutrality rule (this file's own included) |
| +// can never be mistaken for a violation of it. |
| +func assertNoChannelWord(t *testing.T, dir string) { |
| + t.Helper() |
| + entries, err := os.ReadDir(dir) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + fset := token.NewFileSet() |
| + for _, e := range entries { |
| + name := e.Name() |
| + if e.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { |
| + continue |
| + } |
| + path := filepath.Join(dir, name) |
| + src, err := os.ReadFile(path) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + file, err := parser.ParseFile(fset, path, src, parser.ParseComments) |
| + if err != nil { |
| + t.Fatalf("parse %s: %v", path, err) |
| + } |
| + sanitized := append([]byte(nil), src...) |
| + for _, cg := range file.Comments { |
| + start := fset.Position(cg.Pos()).Offset |
| + end := fset.Position(cg.End()).Offset |
| + for i := start; i < end && i < len(sanitized); i++ { |
| + sanitized[i] = ' ' |
| + } |
| + } |
| + lower := strings.ToLower(string(sanitized)) |
| + for _, word := range []string{"channel", "canary"} { |
| + if strings.Contains(lower, word) { |
| + t.Errorf("%s mentions %q outside a comment — internal/caps must never consult or constrain a channel name", name, word) |
| + } |
| + } |
| + } |
| +} |