carlos / platform Public

release: pin the pointer wire shape before touching the signer

Scoped signing keys add a field to the signed wrapper. Every deployment
bucket holds pointers written by binaries that predate it, and the rule
this repo has paid for is that a wire-shape change is additive: those
objects must still parse and still verify, byte for byte.

So the shape gets pinned FIRST, from the signing path as it stands, over
a payload carrying every optional field a real pointer has ever had
(history and an audit trail). Three properties: the fixture verifies and
decodes to exactly what it says; a root-signed wrapper — fixture or
freshly promoted through the store — has exactly the keys payload and
sig, which is what catches a forgotten omitempty on whatever is added
next; and the signature covers the base64 payload STRING rather than the
decoded JSON, asserted in both directions so a "canonicalise then sign"
refactor cannot pass.

The fixture is not regenerated when the shape changes. A diff in it means
live pointers stopped verifying.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 03350852ee4d163b839b9c2eba245a05caa2bb01 parent f1cc914
1 file changed, +143 −0
  • internal/release/golden_test.go +143 −0
diff --git a/internal/release/golden_test.go b/internal/release/golden_test.go
new file mode 100644
index 000000000..02a478959
--- /dev/null
+++ b/internal/release/golden_test.go
@@ -0,0 +1,143 @@
+package release
+
+import (
+ "context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
+ "reflect"
+ "sort"
+ "testing"
+ "time"
+)
+
+// The wire shape of a channel pointer, pinned. Every deployment bucket in
+// production holds objects that look EXACTLY like this right now, written by
+// binaries that predate whatever is being changed today; they have to keep
+// parsing and keep verifying, byte for byte, forever. So this fixture is not
+// regenerated when the shape changes — a diff here means live pointers stopped
+// verifying, which is a fleet that cannot resolve a channel.
+//
+// Captured from the signing path on f1cc914 with a deterministic seed
+// (bytes 1..32) and a frozen promoted_at, over a payload carrying every
+// optional field a real pointer has ever had: history and an audit trail.
+const goldenRootSignedWrapper = `{
+ "payload": "ewogICJzY2hlbWEiOiAxLAogICJhcHAiOiAia2FzcyIsCiAgImNoYW5uZWwiOiAiY2FuYXJ5L3BsYXRmb3JtLWFkb3B0aW9uIiwKICAidmVyc2lvbiI6ICI5ZjNjMWFkIiwKICAicHJvbW90ZWRfYXQiOiAiMjAyNi0wNy0zMFQxMTowNDoxN1oiLAogICJoaXN0b3J5IjogWwogICAgIjFiMmU0YzciLAogICAgIjBhYzkxZGUiCiAgXSwKICAiYXVkaXQiOiBbCiAgICB7CiAgICAgICJ2ZXJzaW9uIjogIjlmM2MxYWQiLAogICAgICAiZnJvbSI6ICJlZGdlIiwKICAgICAgImFjdG9yIjogInBhdWxAcnNsdy5jb20iLAogICAgICAiYXQiOiAiMjAyNi0wNy0zMFQxMTowNDoxN1oiCiAgICB9CiAgXQp9",
+ "sig": "yiutCm6KdqvIc4jqX2fCo9cYY3R6OITWoHtPNk8xO0mUFFxD4RZ5XLT4CHUAlUlRQfwEOKUm7ii46qlhjdQFCg=="
+}`
+
+// goldenRootPub is the public half of the fixture's signer.
+const goldenRootPub = "ebVWLo/mVPlAeLES6KmLp5AfhTrmlb7X4OORC60ElmQ="
+
+// A pointer written by an older binary — no grant field at all — verifies
+// against the root public key and decodes to exactly what it said.
+//
+// Mutation-mindedness: if grant handling were ever made mandatory (require a
+// grant, or reject a wrapper without one), this test goes red immediately,
+// because the entire live fleet is on wrappers shaped like this one.
+func TestGoldenRootSignedWrapperStillVerifies(t *testing.T) {
+ pub, err := ParsePublicKey(goldenRootPub, "golden")
+ if err != nil {
+ t.Fatalf("ParsePublicKey: %v", err)
+ }
+ p, err := verifyPointer(pub, []byte(goldenRootSignedWrapper), "kass", "canary/platform-adoption")
+ if err != nil {
+ t.Fatalf("golden root-signed pointer no longer verifies: %v", err)
+ }
+ want := &ChannelPointer{
+ Schema: 1,
+ App: "kass",
+ Channel: "canary/platform-adoption",
+ Version: "9f3c1ad",
+ PromotedAt: time.Date(2026, 7, 30, 11, 4, 17, 0, time.UTC),
+ History: []string{"1b2e4c7", "0ac91de"},
+ Audit: []Promotion{{
+ Version: "9f3c1ad",
+ From: "edge",
+ Actor: "paul@rslw.com",
+ At: time.Date(2026, 7, 30, 11, 4, 17, 0, time.UTC),
+ }},
+ }
+ if !reflect.DeepEqual(p, want) {
+ t.Fatalf("golden pointer decoded as\n%+v\nwant\n%+v", p, want)
+ }
+}
+
+// The wrapper a root key writes TODAY has exactly two keys. A new wrapper
+// field that is not omitted when empty would change every pointer the fleet
+// writes and break old readers' strictness assumptions; this is the assertion
+// that catches a forgotten `omitempty`.
+//
+// Mutation-mindedness: drop `,omitempty` from any wrapper field added later
+// and this fails with the extra key named.
+func TestRootSignedWrapperHasOnlyPayloadAndSig(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ wrapper []byte
+ }{
+ {"golden fixture", []byte(goldenRootSignedWrapper)},
+ {"freshly signed", freshRootWrapper(t)},
+ } {
+ var keys map[string]json.RawMessage
+ if err := json.Unmarshal(tc.wrapper, &keys); err != nil {
+ t.Fatalf("%s: %v", tc.name, err)
+ }
+ got := make([]string, 0, len(keys))
+ for k := range keys {
+ got = append(got, k)
+ }
+ sort.Strings(got)
+ if want := []string{"payload", "sig"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("%s: wrapper keys = %v, want %v", tc.name, got, want)
+ }
+ }
+}
+
+// freshRootWrapper promotes with a root key through the real store path and
+// returns the bytes that landed in the bucket — the shape a fleet reads.
+func freshRootWrapper(t *testing.T) []byte {
+ t.Helper()
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, _ := testKeys(t)
+ shipRelease(t, s, "bab", "kass", "9f3c1ad", "linux-arm64", []byte("bytes"))
+ if err := Promote(ctx, s, priv, "bab", "kass", "canary/platform-adoption", "9f3c1ad"); err != nil {
+ t.Fatalf("Promote: %v", err)
+ }
+ raw, _, err := s.Get(ctx, ChannelKey("bab", "kass", "canary/platform-adoption"))
+ if err != nil {
+ t.Fatalf("read back pointer: %v", err)
+ }
+ return raw
+}
+
+// The signature covers the base64 payload STRING, not the decoded JSON, so no
+// canonicalisation question can ever arise. Pinned because a refactor to
+// "verify the decoded bytes" would silently invalidate every live pointer.
+//
+// Mutation-mindedness: change signPointer/verifyPointer to sign the decoded
+// payload and the golden fixture stops verifying; this test says why.
+func TestSignatureCoversTheBase64PayloadString(t *testing.T) {
+ pub, err := ParsePublicKey(goldenRootPub, "golden")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var sw signedWrapper
+ if err := json.Unmarshal([]byte(goldenRootSignedWrapper), &sw); err != nil {
+ t.Fatal(err)
+ }
+ sig, err := base64.StdEncoding.DecodeString(sw.Sig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !ed25519.Verify(pub, []byte(sw.Payload), sig) {
+ t.Fatal("golden signature does not cover the base64 payload string")
+ }
+ decoded, err := base64.StdEncoding.DecodeString(sw.Payload)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ed25519.Verify(pub, decoded, sig) {
+ t.Fatal("golden signature covers the DECODED payload — the scheme changed")
+ }
+}