package caps import ( "encoding/json" "fmt" "strings" "testing" ) // TestUnknownCapabilityNameIsRefused pins the fail-closed half of spec §3b: // a capability name this binary does not know must refuse the whole block, // naming the capability and the fix — never silently drop it. func TestUnknownCapabilityNameIsRefused(t *testing.T) { var s Set err := json.Unmarshal([]byte(`{"v":1,"daemon":true,"gpu":true}`), &s) if err == nil || !strings.Contains(err.Error(), "gpu") { t.Fatalf("err = %v, want a refusal naming gpu", err) } if !strings.Contains(err.Error(), "update the platform binary") { t.Fatalf("refusal %q does not name the fix", err) } } // TestUnknownFieldInsideAKnownCapabilityIsIgnoredAndNoted pins the tolerant // half of spec §3b: room to grow a known capability additively without // breaking an older binary that already understands its other fields. func TestUnknownFieldInsideAKnownCapabilityIsIgnoredAndNoted(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":1,"data":{"replicate":"platform","dbs":["console.db"],"future":7}}`), &s); err != nil { t.Fatalf("unknown field inside a known capability must NOT fail: %v", err) } if s.Data == nil || s.Data.Replicate != DataReplicatePlatform || len(s.Data.DBs) != 1 { t.Fatalf("known fields lost: %+v", s.Data) } if n := s.Notes(); len(n) != 1 || !strings.Contains(n[0], "future") { t.Fatalf("notes = %v, want one line naming future", n) } } // TestVersionBumpIsRefused pins fail-closed on "v": the box keeps serving // what it has rather than materialize a unit missing something new. func TestVersionBumpIsRefused(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":2,"daemon":true}`), &s); err == nil { t.Fatal("v:2 must be refused — fail closed, the box keeps what it serves") } } // TestVMissingKeyIsRefusedHonestly pins review finding I3: an absent "v" // key — whether the object is entirely empty or merely misspells the key // ("V" instead of "v") — is refused with a message naming the missing key, // never the misleading "v 0" (which would suggest the block explicitly // asked for a version number that happens to be zero). func TestVMissingKeyIsRefusedHonestly(t *testing.T) { for _, raw := range []string{`{}`, `{"V":1}`} { var s Set err := json.Unmarshal([]byte(raw), &s) if err == nil { t.Errorf("%s: accepted, want a refusal", raw) continue } if !strings.Contains(err.Error(), "missing") { t.Errorf("%s: err = %v, want an honest \"missing v\" message", raw, err) } if strings.Contains(err.Error(), "v 0") { t.Errorf("%s: err = %v, must not say \"v 0\" — the key was never there to say 0", raw, err) } } } // TestVRangeAcceptedUpToVersionRefusedOutsideIt pins review finding I3's // range, generically against the Version constant rather than a hardcoded // number: every v in 1..Version decodes, and anything outside it (0, // negative, or Version+1) is refused. The day Version becomes 2, an // immutable v1 manifest already in a production bucket must still decode — // rollback depends on it. func TestVRangeAcceptedUpToVersionRefusedOutsideIt(t *testing.T) { for v := 1; v <= Version; v++ { var s Set if err := json.Unmarshal([]byte(fmt.Sprintf(`{"v":%d}`, v)), &s); err != nil { t.Errorf("v:%d (within 1..Version=%d) refused: %v", v, Version, err) } } for _, v := range []int{0, -1, Version + 1} { var s Set if err := json.Unmarshal([]byte(fmt.Sprintf(`{"v":%d}`, v)), &s); err == nil { t.Errorf("v:%d (outside 1..Version=%d) accepted", v, Version) } } } // TestValidCapabilitiesRefusesVOutsideRange pins review finding I6(b): the // same range check inside ValidCapabilities, exercised against Set literals // that never passed through UnmarshalJSON at all. func TestValidCapabilitiesRefusesVOutsideRange(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 0}); err == nil { t.Fatal("Set{V:0} accepted by ValidCapabilities") } if err := ValidCapabilities("keymail", &Set{V: 2}); err == nil { t.Fatal("Set{V:2} accepted by ValidCapabilities (Version is 1)") } } // TestBareVBlockIsValidAndCommandEmptyMeansServe pins review finding I6: // {"v":1} alone (every other field absent) decodes and validates cleanly, // and an empty Command means the template's own "serve" — not an invalid // or a refused value. func TestBareVBlockIsValidAndCommandEmptyMeansServe(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":1}`), &s); err != nil { t.Fatalf(`bare {"v":1} refused: %v`, err) } if s.Command != "" { t.Fatalf(`Command = %q, want "" (the template's own "serve")`, s.Command) } if err := ValidCapabilities("console", &s); err != nil { t.Fatalf("bare v:1 block refused by ValidCapabilities: %v", err) } } // TestKeymailSpecBlockRoundTripsAndValidates pins review finding I6(a): // decode the exact keymail capabilities block from // docs/superpowers/specs/2026-08-03-app-capabilities-design.md §3a, assert // every field, and confirm ValidCapabilities accepts it whole. func TestKeymailSpecBlockRoundTripsAndValidates(t *testing.T) { const keymailBlock = `{ "v": 1, "command": "relay", "daemon": true, "children": true, "runtime_dir": "keymail", "ports": [ { "port": 25, "name": "mx" } ], "data": { "replicate": "app" }, "host_role": true }` var s Set if err := json.Unmarshal([]byte(keymailBlock), &s); err != nil { t.Fatalf("spec §3a keymail block failed to decode: %v", err) } if s.V != 1 { t.Errorf("V = %d, want 1", s.V) } if s.Command != "relay" { t.Errorf("Command = %q, want relay", s.Command) } if !s.Daemon { t.Error("Daemon = false, want true") } if !s.Children { t.Error("Children = false, want true") } if s.RuntimeDir != "keymail" { t.Errorf("RuntimeDir = %q, want keymail", s.RuntimeDir) } if len(s.Ports) != 1 || s.Ports[0].Port != 25 || s.Ports[0].Name != "mx" { t.Errorf("Ports = %+v, want [{25 mx}]", s.Ports) } if s.Data == nil || s.Data.Replicate != DataReplicateApp || len(s.Data.DBs) != 0 { t.Errorf("Data = %+v, want {app []}", s.Data) } if !s.HostRole { t.Error("HostRole = false, want true") } if n := s.Notes(); len(n) != 0 { t.Errorf("Notes = %v, want none", n) } if err := ValidCapabilities("keymail", &s); err != nil { t.Fatalf("ValidCapabilities(keymail, spec block) = %v, want nil", err) } } // TestDocEngineDecodesAsAKnownCapability pins the wire shape of the new // capability: "doc_engine" is one of the closed vocabulary's names, so it // decodes to Set.DocEngine and is NOT refused as unknown and NOT recorded as an // ignored field. An absent doc_engine leaves it false (not declared). func TestDocEngineDecodesAsAKnownCapability(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":1,"doc_engine":true}`), &s); err != nil { t.Fatalf("doc_engine refused as unknown: %v", err) } if !s.DocEngine { t.Error("DocEngine = false, want true") } if n := s.Notes(); len(n) != 0 { t.Errorf("doc_engine recorded as an ignored field: %v", n) } if err := ValidCapabilities("gotenberg", &s); err != nil { t.Fatalf("ValidCapabilities(doc_engine) = %v, want nil", err) } var absent Set if err := json.Unmarshal([]byte(`{"v":1}`), &absent); err != nil { t.Fatalf("decode: %v", err) } if absent.DocEngine { t.Error("an absent doc_engine decoded to true, want false (not declared)") } } // TestNilSetNotesDoesNotPanic pins review finding I1: every production // manifest today decodes to a nil Set, and the first ranged Notes() call in // Task 6's converge pass must not crash RunAdopt fleet-wide. func TestNilSetNotesDoesNotPanic(t *testing.T) { var s *Set if n := s.Notes(); n != nil { t.Fatalf("nil Set.Notes() = %v, want nil", n) } } // TestPortFieldCaseInsensitiveMatchIsNotApplied pins review finding I2: a // whole-object json.Unmarshal into Port would let Go's decoder case-fold an // unrecognised "PORT" onto the Port field via its default case-insensitive // tag matching, applying exactly the value the notes scan reports as // ignored. Decoding field-by-field from the exact-key fields map must // refuse to do that: "PORT" stays ignored and noted, and Port stays 0. func TestPortFieldCaseInsensitiveMatchIsNotApplied(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":1,"ports":[{"PORT":25}]}`), &s); err != nil { t.Fatalf("an unrecognised-cased field must not fail decode: %v", err) } if len(s.Ports) != 1 || s.Ports[0].Port != 0 || s.Ports[0].Name != "" { t.Fatalf(`Ports = %+v, want [{0 ""}] — "PORT" must be ignored, not case-folded onto Port`, s.Ports) } if n := s.Notes(); len(n) != 1 || !strings.Contains(n[0], "PORT") { t.Fatalf("notes = %v, want one line naming PORT", n) } } // TestDataFieldCaseInsensitiveMatchIsNotApplied is TestPortFieldCase...'s // sibling for Data: "REPLICATE"/"DBS" must stay ignored-and-noted rather // than being case-folded onto Replicate/DBs (review finding I2). func TestDataFieldCaseInsensitiveMatchIsNotApplied(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":1,"data":{"REPLICATE":"platform","DBS":["x.db"]}}`), &s); err != nil { t.Fatalf("unrecognised-cased fields must not fail decode: %v", err) } if s.Data == nil || s.Data.Replicate != "" || len(s.Data.DBs) != 0 { t.Fatalf("Data = %+v, want the zero value — REPLICATE/DBS must be ignored, not case-folded", s.Data) } if n := s.Notes(); len(n) != 2 { t.Fatalf("notes = %v, want two lines naming REPLICATE and DBS", n) } } // TestDataExplicitNullDecodesButIsRefused pins the Data doc comment's // asymmetry (review MINOR item): an explicit "data": null still allocates a // non-nil *Data (the zero value), which ValidCapabilities then refuses for // naming neither accepted replicate value — unlike an absent "data" key, // which leaves Data nil and valid. func TestDataExplicitNullDecodesButIsRefused(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{"v":1,"data":null}`), &s); err != nil { t.Fatalf(`{"v":1,"data":null} failed to decode: %v`, err) } if s.Data == nil { t.Fatal(`explicit "data": null must still decode to a non-nil *Data (the zero value)`) } if err := ValidCapabilities("console", &s); err == nil { t.Fatal("a zero-value Data (from explicit \"data\": null) must be refused for naming no replicate value") } } // TestCapabilitiesEmptyObjectWithNoVIsRefused pins the reviewer's sharpest // edge case: "capabilities": {} must NOT be read as "no capabilities" — an // absent "v" on a PRESENT block is refused (now with an honest "missing v" // message; see TestVMissingKeyIsRefusedHonestly). Only an absent // capabilities KEY (a nil *Set, never constructed) means "none". func TestCapabilitiesEmptyObjectWithNoVIsRefused(t *testing.T) { var s Set if err := json.Unmarshal([]byte(`{}`), &s); err == nil { t.Fatal("{} (no v) must be refused, not silently mean no capabilities") } } // TestRuntimeDirCharsetAndReservedNames pins the charset and the reserved // list, including the names added on review (finding C1). NOTE: the plan's // Step-1 listing included "" in this bad list, but that contradicts both // TestNilSetIsValidAndMeansNothing and TestPortsReservedAndNonPrivilegedRefused's // final assertion (a Set with only Ports set, RuntimeDir at its zero value // "", must be valid) — an empty RuntimeDir means "not declared", not // "declared as empty". "" is dropped here and pinned separately as valid // below. func TestRuntimeDirCharsetAndReservedNames(t *testing.T) { bad := []string{ "../etc", "carlos/apps", "/run/keymail", "Keymail", "carlos", "systemd", "systemd-foo", "lock", "log", "user", "dbus", "sshd", "udev", strings.Repeat("a", 33), // added on review (C1): common OS/tool-owned /run subdirectories, // none of which is a substitute for converge's box-wide existence // check (see the note on reservedRuntimeDir and the Task 6 plan // addendum) — this list can never be exhaustive. "credentials", "sudo", "mount", "network", "host", "netns", "shm", } for _, name := range bad { s := &Set{V: 1, RuntimeDir: name} if err := ValidCapabilities("keymail", s); err == nil { t.Errorf("runtime_dir %q was accepted", name) } } if err := ValidCapabilities("keymail", &Set{V: 1, RuntimeDir: "keymail"}); err != nil { t.Fatalf("keymail refused: %v", err) } } // TestRuntimeDirEmptyMeansNotDeclared pins the deviation explained in the // comment above: the zero value is "not declared", not an invalid name. func TestRuntimeDirEmptyMeansNotDeclared(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 1, RuntimeDir: ""}); err != nil { t.Fatalf("empty runtime_dir (not declared) must be valid: %v", err) } } // TestPortsReservedAndNonPrivilegedRefused pins the port range and the // absolute reservation of 22/80/443 — an operator typo must not be able to // hand a tenant the edge's ports or the box's SSH (spec §7). func TestPortsReservedAndNonPrivilegedRefused(t *testing.T) { for _, p := range []int{0, 22, 80, 443, 1024, 8080, 65535, -1} { if err := ValidCapabilities("keymail", &Set{V: 1, Ports: []Port{{Port: p}}}); err == nil { t.Errorf("port %d was accepted", p) } } if err := ValidCapabilities("keymail", &Set{V: 1, Ports: []Port{{Port: 25, Name: "mx"}}}); err != nil { t.Fatalf("port 25 refused: %v", err) } } // TestPort1023IsAccepted pins the upper boundary of the privileged range // (review MINOR item): 1023 is the last privileged port and must be // accepted, not off-by-one refused. func TestPort1023IsAccepted(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 1, Ports: []Port{{Port: 1023}}}); err != nil { t.Fatalf("port 1023 (upper bound) refused: %v", err) } } // TestDuplicatePortIsRefused pins the plan's "Plus:" line item: the same // port twice in one ports list is refused rather than silently deduplicated. func TestDuplicatePortIsRefused(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 1, Ports: []Port{{Port: 25}, {Port: 25}}}); err == nil { t.Fatal("duplicate port 25 was accepted") } } // TestTooManyPortsIsRefused pins the len(ports) > 4 bound — a bound, not a // feature. func TestTooManyPortsIsRefused(t *testing.T) { ports := []Port{{Port: 1}, {Port: 2}, {Port: 3}, {Port: 4}, {Port: 5}} if err := ValidCapabilities("keymail", &Set{V: 1, Ports: ports}); err == nil { t.Fatal("5 ports (bound is 4) was accepted") } if err := ValidCapabilities("keymail", &Set{V: 1, Ports: ports[:4]}); err != nil { t.Fatalf("4 ports (at the bound) refused: %v", err) } } // TestPortNameCharsetIsRefused pins ports[].name's charset — it lands in // logs. func TestPortNameCharsetIsRefused(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 1, Ports: []Port{{Port: 25, Name: "Mail Exchange!"}}}); err == nil { t.Fatal("port name with spaces and punctuation was accepted") } } // TestHostRoleRefusesAnAppNameTooLongForAUser pins the account-name limit, // refused at ship where a human can rename. func TestHostRoleRefusesAnAppNameTooLongForAUser(t *testing.T) { long := strings.Repeat("a", 30) // carlos-<30> = 37 > 32 if err := ValidCapabilities(long, &Set{V: 1, HostRole: true}); err == nil { t.Fatal("host_role on an over-long app name must be refused at ship") } } // TestAppUserLengthBoundary pins the exact edge (review MINOR item): // carlos- at exactly 32 characters is accepted, 33 is refused. func TestAppUserLengthBoundary(t *testing.T) { at32 := strings.Repeat("a", 25) // "carlos-" (7) + 25 = 32 if err := ValidCapabilities(at32, &Set{V: 1, HostRole: true}); err != nil { t.Fatalf("app user at exactly 32 chars refused: %v", err) } at33 := strings.Repeat("a", 26) // "carlos-" (7) + 26 = 33 if err := ValidCapabilities(at33, &Set{V: 1, HostRole: true}); err == nil { t.Fatal("app user at 33 chars (one over the limit) was accepted") } } // TestHostRoleRefusesAppNameCharsetBeforeLength pins review finding I5: an // app name failing the token charset is refused even when it would pass // the length check — release.ValidApp's charset is checked first, as // defense in depth for the User= sink. func TestHostRoleRefusesAppNameCharsetBeforeLength(t *testing.T) { for _, bad := range []string{"Keymail", "1relay", "key_mail", "key mail"} { if err := ValidCapabilities(bad, &Set{V: 1, HostRole: true}); err == nil { t.Errorf("host_role with app name %q (fails the charset) was accepted", bad) } } } // TestNilSetIsValidAndMeansNothing pins today's every-manifest behaviour: // no capabilities key parses to a nil *Set, and a nil *Set is valid. func TestNilSetIsValidAndMeansNothing(t *testing.T) { if err := ValidCapabilities("console", nil); err != nil { t.Fatalf("nil (today's every manifest) must be valid: %v", err) } } // TestCommandCharsetIsRefused pins the plan's "Plus:" line item: a command // is one lowercase token, never flags or a capitalized subcommand. func TestCommandCharsetIsRefused(t *testing.T) { for _, bad := range []string{"relay --flag", "Relay", "1relay", "-relay", "a b", strings.Repeat("a", 33)} { if err := ValidCapabilities("keymail", &Set{V: 1, Command: bad}); err == nil { t.Errorf("command %q was accepted", bad) } } if err := ValidCapabilities("keymail", &Set{V: 1, Command: "relay"}); err != nil { t.Fatalf("command \"relay\" refused: %v", err) } } // TestDataDBsCharsetAndSeparatorRefusals pins the "used only as // filepath.Join(stateDir, name)" rail: dbs entries must be bare filenames, // never ".", "..", or containing any separator. func TestDataDBsCharsetAndSeparatorRefusals(t *testing.T) { for _, bad := range []string{".", "..", "console.db/../../etc/passwd", "a/b", "con*", strings.Repeat("a", 65)} { s := &Set{V: 1, Data: &Data{Replicate: DataReplicatePlatform, DBs: []string{bad}}} if err := ValidCapabilities("console", s); err == nil { t.Errorf("dbs entry %q was accepted", bad) } } if err := ValidCapabilities("console", &Set{V: 1, Data: &Data{Replicate: DataReplicatePlatform, DBs: []string{"console.db"}}}); err != nil { t.Fatalf("console.db refused: %v", err) } } // TestDataDBsDuplicateIsRefused pins review finding I4: a repeated dbs name // is refused, mirroring validPorts' duplicate check — two litestream // entries pointed at one WAL is the checkpoint fight §3a exists to // prevent. func TestDataDBsDuplicateIsRefused(t *testing.T) { s := &Set{V: 1, Data: &Data{Replicate: DataReplicatePlatform, DBs: []string{"console.db", "console.db"}}} if err := ValidCapabilities("console", s); err == nil { t.Fatal("duplicate dbs entry \"console.db\" was accepted") } } // TestTooManyDBsIsRefused pins the len(data.dbs) > 8 bound. func TestTooManyDBsIsRefused(t *testing.T) { dbs := make([]string, 9) for i := range dbs { dbs[i] = strings.Repeat("a", i+1) + ".db" } if err := ValidCapabilities("console", &Set{V: 1, Data: &Data{Replicate: DataReplicatePlatform, DBs: dbs}}); err == nil { t.Fatal("9 dbs (bound is 8) was accepted") } } // TestDataDBsWithAppReplicateIsRefused pins the two-replicators hazard: dbs // is meaningless when the app streams its own WAL, so it is refused rather // than silently ignored. func TestDataDBsWithAppReplicateIsRefused(t *testing.T) { s := &Set{V: 1, Data: &Data{Replicate: DataReplicateApp, DBs: []string{"x.db"}}} if err := ValidCapabilities("keymail", s); err == nil { t.Fatal("dbs alongside replicate:\"app\" must be refused") } } // TestDataReplicateUnknownValueIsRefused pins the closed pair of // replicate values. func TestDataReplicateUnknownValueIsRefused(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 1, Data: &Data{Replicate: "bogus"}}); err == nil { t.Fatal("data.replicate \"bogus\" was accepted") } } // TestGrantorReplicateValuesAreValidOnTheirOwn pins that replicate:"app" // with no dbs, and replicate:"platform" with dbs, are both accepted — // only replicate:"app" WITH dbs is refused. func TestGrantorReplicateValuesAreValidOnTheirOwn(t *testing.T) { if err := ValidCapabilities("keymail", &Set{V: 1, Data: &Data{Replicate: DataReplicateApp}}); err != nil { t.Fatalf("replicate:\"app\" with no dbs refused: %v", err) } if err := ValidCapabilities("console", &Set{V: 1, Data: &Data{Replicate: DataReplicatePlatform, DBs: []string{"console.db"}}}); err != nil { t.Fatalf("replicate:\"platform\" with dbs refused: %v", err) } }