{"record":{"id":"bb782f2b337b3330","repo":"JuliusBrussee/caveman","slug":"proposalrun-seq-d-recompute-w","errorCode":null,"errorMessage":"proposalrun: seq %d recompute: %w","messagePattern":"proposalrun: seq (.+?) recompute: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/proposalrun/proposalrun.go","lineNumber":361,"sourceCode":"\tif len(runs) == 0 {\n\t\treturn nil\n\t}\n\tsorted := make([]Run, len(runs))\n\tcopy(sorted, runs)\n\tsort.Slice(sorted, func(i, j int) bool { return sorted[i].Seq < sorted[j].Seq })\n\n\tprevHash := \"\"\n\tfor i, r := range sorted {\n\t\twantSeq := int64(i + 1)\n\t\tif r.Seq != wantSeq {\n\t\t\treturn fmt.Errorf(\"proposalrun: seq %d out of order (expected %d)\", r.Seq, wantSeq)\n\t\t}\n\t\tif r.PrevHash != prevHash {\n\t\t\treturn fmt.Errorf(\"proposalrun: seq %d prev_hash does not link to the prior row\", r.Seq)\n\t\t}\n\t\tgot, err := RowHash(r.PrevHash, r.Seq, r.Action, r.Detail, r.CostUSD, r.CreatedAt)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"proposalrun: seq %d recompute: %w\", r.Seq, err)\n\t\t}\n\t\tif got != r.RowHash {\n\t\t\treturn fmt.Errorf(\"proposalrun: seq %d row_hash mismatch (tampered)\", r.Seq)\n\t\t}\n\t\tprevHash = r.RowHash\n\t}\n\treturn nil\n}\n","sourceCodeStart":343,"sourceCodeEnd":370,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/proposalrun/proposalrun.go#L343-L370","documentation":"VerifyChain recomputes each row's hash via RowHash; this error wraps a failure from that recomputation (returned with %w, typically from CanonicalDetail failing to canonicalize the stored detail JSON). It means the stored row data itself is unusable for hashing — most often the detail column holds invalid or non-canonicalizable JSON.","triggerScenarios":"Calling VerifyChain(runs) where some row's Detail is malformed JSON or otherwise fails CanonicalDetail, causing RowHash to return an error which is wrapped as 'seq %d recompute: %w'.","commonSituations":"Detail written by non-library code paths with invalid JSON, encoding corrupted by migrations or column type changes, truncated JSON from an oversized payload, invalid JSON numbers as in errors 1510-1515.","solutions":["Inspect the wrapped cause (errors.Unwrap / %w chain) and fix or restore the offending row's detail JSON.","Re-validate detail with json.Valid before rows are written; write only via the library's Append helper.","Restore the affected rows from backup or regenerate them from the original events.","If the wrapped error is an oversized JSON number, re-serialize the detail with bounded numeric precision."],"exampleFix":"// before\nAppend(action, json.RawMessage(badBytes), cost) // badBytes is not valid JSON\n// after\nif !json.Valid(badBytes) { return fmt.Errorf(\"invalid detail\") }\nAppend(action, json.RawMessage(badBytes), cost)","handlingStrategy":"try-catch","validationCode":"func detailIsValid(runs []proposalrun.Run) bool {\n\tfor _, r := range runs {\n\t\tif !json.Valid(r.Detail) { return false }\n\t}\n\treturn true\n}","typeGuard":null,"tryCatchPattern":"if err := proposalrun.VerifyChain(runs); err != nil {\n\tvar inner error\n\terrors.As(err, &inner) // inspect wrapped recompute cause\n\treturn fmt.Errorf(\"verify failed: %w\", err)\n}","preventionTips":["Check json.Valid(detail) before every append","Restrict detail writes to the library's canonicalization path","Monitor for canonicalization errors in write telemetry"],"tags":["audit-chain","hash-chain","json","integrity"],"backgroundTag":"json-parse-error","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}