package workphase import ( "strings" "testing" ) func research() Research { return Research{ Findings: []Finding{{Claim: "attribution runs per figure", Evidence: "internal/attr/attr.go:88"}}, Code: []CodePath{{Path: "internal/attr/attr.go", Why: "aggregation happens here"}}, Invariants: []string{"identity semantics must not change"}, DeadEnds: []DeadEnd{{Tried: "figure plurality", WhyFailed: "no measured gain"}}, } } func plan() Plan { return Plan{ Changes: []Change{{Target: "internal/attr/attr.go", Intent: "aggregate per person"}}, Verification: []string{"go test ./internal/attr/"}, } } func TestRoundTrip(t *testing.T) { b, err := Encode(research()) if err != nil { t.Fatal(err) } got, err := DecodeResearch(b) if err != nil { t.Fatal(err) } if got.Findings[0].Claim != "attribution runs per figure" || got.DeadEnds[0].Tried != "figure plurality" { t.Fatalf("round trip lost content: %+v", got) } pb, err := Encode(plan()) if err != nil { t.Fatal(err) } gotPlan, err := DecodePlan(pb) if err != nil { t.Fatal(err) } if gotPlan.Changes[0].Target != "internal/attr/attr.go" { t.Fatalf("round trip lost content: %+v", gotPlan) } } // The bound is the point. An artifact that can hold a transcript is a // transcript, and the next phase pays for reading it. func TestBoundsRejectUnboundedArtifacts(t *testing.T) { cases := map[string]func() error{ "no findings": func() error { return Research{}.Validate() }, "no evidence": func() error { return Research{Findings: []Finding{{Claim: "x"}}}.Validate() }, "multiline claim": func() error { return Research{Findings: []Finding{{Claim: "a\nb", Evidence: "e"}}}.Validate() }, "long claim": func() error { return Research{Findings: []Finding{{Claim: strings.Repeat("x", 501), Evidence: "e"}}}.Validate() }, "too many findings": func() error { r := Research{} for i := 0; i < 65; i++ { r.Findings = append(r.Findings, Finding{Claim: "c", Evidence: "e"}) } return r.Validate() }, "absolute path": func() error { r := research() r.Code = []CodePath{{Path: "/etc/passwd", Why: "no"}} return r.Validate() }, "blank invariant": func() error { r := research() r.Invariants = []string{" "} return r.Validate() }, "no changes": func() error { return Plan{}.Validate() }, "no change intent": func() error { return Plan{Changes: []Change{{Target: "x"}}}.Validate() }, "multiline risk": func() error { p := plan() p.Risks = []string{"a\nb"} return p.Validate() }, } for name, fn := range cases { if err := fn(); err == nil { t.Fatalf("%s: expected rejection", name) } } } func TestEncodeRejectsInvalid(t *testing.T) { if _, err := Encode(Research{}); err == nil { t.Fatal("Encode must validate before sealing") } if _, err := DecodeResearch([]byte(`{"findings":[]}`)); err == nil { t.Fatal("Decode must validate") } if _, err := DecodePlan([]byte(`not json`)); err == nil { t.Fatal("Decode must reject non-JSON") } }