301c84d9b5
HashResticPassword = sha256 hex over the trimmed password (pinned cross-repo vector). escrowUploadRequest gains restic_pw_sha256,omitempty — set only when a staged password was sealed into the blob. Contract test updated; hub mirrors next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
37 lines
1.4 KiB
Go
37 lines
1.4 KiB
Go
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")
|
|
}
|
|
}
|