{"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":"validation","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/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/proposalrun/proposalrun.go#L343-L370","documentation":"A wrapper, not a root cause: VerifyChain calls RowHash(PrevHash, Seq, Action, Detail, CostUSD, CreatedAt) for every row, and RowHash failed for this row before any comparison happened. RowHash canonicalizes the Detail JSON (number re-encoding, key sorting) and hashes; its failures are the JSON canonicalization errors — malformed number exponents, exponent/coefficient overflow, or an unmarshalable Detail value. The %w preserves the underlying error for errors.Is/As inspection.","triggerScenarios":"A stored Detail blob contains a JSON number this package cannot canonicalize (e.g. exponent beyond 147455 or a corrupt lexeme), or Detail was written by a different/older canonicalizer version whose output the current one rejects.","commonSituations":"Upgrading the proposalrun canonicalization rules after rows were already persisted; DB dump/load that mangled JSONB numeric spellings; a writer that bypassed the package's validation when inserting.","solutions":["Unwrap the error (errors.Unwrap / inspect the message after 'recompute:') to see which canonicalization rule failed, then fix that field's value in the offending row.","After changing canonicalization rules, run a migration that re-writes Detail through the new canonicalizer and re-chains rows — old and new hash outputs are not interchangeable.","Ensure all writes go through the package's own append/insert API so Detail is validated at write time."],"exampleFix":"// before\nerr := proposalrun.VerifyChain(runs)\nfmt.Println(err) // \"proposalrun: seq 7 recompute: invalid JSON number exponent\"\n\n// after\nerr := proposalrun.VerifyChain(runs)\nvar inner error\nfor e := err; e != nil; e = errors.Unwrap(e) { inner = e }\nlog.Printf(\"seq 7 root cause: %v\", inner) // then repair the Detail blob at that row","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := proposalrun.VerifyChain(runs); err != nil {\n    if strings.Contains(err.Error(), \"recompute:\") {\n        root := err\n        for errors.Unwrap(root) != nil { root = errors.Unwrap(root) }\n        // root is the canonicalization failure; repair the Detail at the reported seq\n        log.Printf(\"chain verify: root cause at seq: %v\", root)\n    }\n}","preventionTips":["Route all Detail writes through the package API so canonicalization is validated at write time.","After upgrading canonicalization rules, migrate and re-hash existing rows in the same release.","Keep unwrap-friendly wrapping (%w) in your own wrappers so the root cause stays reachable."],"tags":["hash-chain","error-wrapping","json"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}