server: read the verdict from where the schema puts it

Every uploaded run showed "not recorded" in the web UI because the meta
extractor read summary.verdict. Schema §7.3 calls that field
summary.overall; "verdict" is the per-category field one level down. So
the verdict was never stored, and the UI faithfully reported a gap that
was this parser's doing rather than the document's.

The test encoded the same mistake — its fixture posted
summary.verdict:"warn" — so it passed throughout against a parser that
read a field nothing writes. Corrected to summary.overall, and to a
verdict that exists: §7.3 defines green|yellow|red|inconclusive, and
"warn" was never one of them.

The eleven runs already stored had their meta backfilled from the
documents, which are kept byte-for-byte and still carry the real value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-02 10:52:25 +02:00
co-authored by Claude Opus 5
parent d5b1bab577
commit 987b2ceb47
2 changed files with 8 additions and 4 deletions
+6 -2
View File
@@ -158,7 +158,11 @@ func (s *Store) Put(deviceID string, body []byte, linked bool) (Meta, error) {
} `json:"run"` } `json:"run"`
Findings []json.RawMessage `json:"findings"` Findings []json.RawMessage `json:"findings"`
Summary struct { Summary struct {
Verdict string `json:"verdict"` // measurement-schema.md §7.3 calls this "overall"; "verdict" is the per-category
// field one level down. Reading the wrong one stored an empty verdict on every run
// ever uploaded, which the UI showed as "not recorded" — a claim about the document
// that was really a bug in this parser.
Overall string `json:"overall"`
} `json:"summary"` } `json:"summary"`
} }
if err := json.Unmarshal(body, &doc); err != nil || doc.Run.ID == "" { if err := json.Unmarshal(body, &doc); err != nil || doc.Run.ID == "" {
@@ -192,7 +196,7 @@ func (s *Store) Put(deviceID string, body []byte, linked bool) (Meta, error) {
meta := Meta{ meta := Meta{
ID: id, DeviceID: deviceID, UploadedAt: time.Now().UTC(), ID: id, DeviceID: deviceID, UploadedAt: time.Now().UTC(),
StartedAt: doc.Run.StartedAt, Anonymization: level, StartedAt: doc.Run.StartedAt, Anonymization: level,
SizeBytes: int64(len(body)), Verdict: doc.Summary.Verdict, SizeBytes: int64(len(body)), Verdict: doc.Summary.Overall,
FindingCount: len(doc.Findings), FindingCount: len(doc.Findings),
} }
if err := os.WriteFile(filepath.Join(devDir, id+".meta.json"), mustJSON(meta), 0o600); err != nil { if err := os.WriteFile(filepath.Join(devDir, id+".meta.json"), mustJSON(meta), 0o600); err != nil {
+2 -2
View File
@@ -17,7 +17,7 @@ import (
func doc(id, anon string) []byte { func doc(id, anon string) []byte {
return []byte(fmt.Sprintf( return []byte(fmt.Sprintf(
`{"run":{"id":%q,"started_at":"2026-08-01T10:00:00Z","privacy":{"anonymization":%q}},`+ `{"run":{"id":%q,"started_at":"2026-08-01T10:00:00Z","privacy":{"anonymization":%q}},`+
`"findings":[{"id":"f1"},{"id":"f2"}],"summary":{"verdict":"warn"}}`, id, anon)) `"findings":[{"id":"f1"},{"id":"f2"}],"summary":{"overall":"yellow"}}`, id, anon))
} }
func open(t *testing.T, p Policy) (*Store, string) { func open(t *testing.T, p Policy) (*Store, string) {
@@ -193,7 +193,7 @@ func TestMetaSummarisesTheDocument(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if m.FindingCount != 2 || m.Verdict != "warn" || m.Anonymization != AnonBalanced { if m.FindingCount != 2 || m.Verdict != "yellow" || m.Anonymization != AnonBalanced {
t.Fatalf("meta not extracted: %+v", m) t.Fatalf("meta not extracted: %+v", m)
} }
if m.StartedAt != "2026-08-01T10:00:00Z" { if m.StartedAt != "2026-08-01T10:00:00Z" {