package main import ( "encoding/json" "reflect" "sort" "testing" ) // TestEscrowUploadContract pins the agent→hub escrow wire shape. It MUST match the hub's ingest // struct (felhom-hub api.escrowUploadRequest). Cross-repo, no shared module — this is the agent // half of the contract guard; the hub has the mirror in its own test. func TestEscrowUploadContract(t *testing.T) { b, _ := json.Marshal(escrowUploadRequest{BlobB64: "x", KeyFingerprint: "y", Posture: "z", CreatedAt: "t", ResticPwSHA256: "h"}) var m map[string]any if err := json.Unmarshal(b, &m); err != nil { t.Fatal(err) } got := make([]string, 0, len(m)) for k := range m { got = append(got, k) } sort.Strings(got) want := []string{"blob_b64", "created_at", "key_fingerprint", "posture", "restic_pw_sha256"} if !reflect.DeepEqual(got, want) { t.Fatalf("escrow wire contract drift: got %v want %v (must match the hub ingest struct)", got, want) } // SLICE 3: no staged password folded in → the hash field is OMITTED on the wire (the hub stores NULL → // the controller never matches → stays pending; correct — the blob doesn't cover the key). b2, _ := json.Marshal(escrowUploadRequest{BlobB64: "x", KeyFingerprint: "y", Posture: "z", CreatedAt: "t"}) var m2 map[string]any _ = json.Unmarshal(b2, &m2) if _, present := m2["restic_pw_sha256"]; present { t.Fatal("restic_pw_sha256 must be omitted when no staged password was sealed") } }