JuliusBrussee/caveman · error

proposalrun: seq recompute

Error message

proposalrun: seq %d recompute: %w

What it means

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.

Solutions

  1. Inspect the wrapped cause (errors.Unwrap / %w chain) and fix or restore the offending row's detail JSON.
  2. Re-validate detail with json.Valid before rows are written; write only via the library's Append helper.
  3. Restore the affected rows from backup or regenerate them from the original events.
  4. If the wrapped error is an oversized JSON number, re-serialize the detail with bounded numeric precision.

Example fix

// before
Append(action, json.RawMessage(badBytes), cost) // badBytes is not valid JSON
// after
if !json.Valid(badBytes) { return fmt.Errorf("invalid detail") }
Append(action, json.RawMessage(badBytes), cost)
Defensive patterns

Strategy: try-catch

Validate before calling

func detailIsValid(runs []proposalrun.Run) bool {
	for _, r := range runs {
		if !json.Valid(r.Detail) { return false }
	}
	return true
}

Try / catch

if err := proposalrun.VerifyChain(runs); err != nil {
	var inner error
	errors.As(err, &inner) // inspect wrapped recompute cause
	return fmt.Errorf("verify failed: %w", err)
}

Prevention

When it happens

Trigger: 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'.

Common situations: 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.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/bb782f2b337b3330. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/proposalrun/proposalrun.go:361

	if len(runs) == 0 {
		return nil
	}
	sorted := make([]Run, len(runs))
	copy(sorted, runs)
	sort.Slice(sorted, func(i, j int) bool { return sorted[i].Seq < sorted[j].Seq })

	prevHash := ""
	for i, r := range sorted {
		wantSeq := int64(i + 1)
		if r.Seq != wantSeq {
			return fmt.Errorf("proposalrun: seq %d out of order (expected %d)", r.Seq, wantSeq)
		}
		if r.PrevHash != prevHash {
			return fmt.Errorf("proposalrun: seq %d prev_hash does not link to the prior row", r.Seq)
		}
		got, err := RowHash(r.PrevHash, r.Seq, r.Action, r.Detail, r.CostUSD, r.CreatedAt)
		if err != nil {
			return fmt.Errorf("proposalrun: seq %d recompute: %w", r.Seq, err)
		}
		if got != r.RowHash {
			return fmt.Errorf("proposalrun: seq %d row_hash mismatch (tampered)", r.Seq)
		}
		prevHash = r.RowHash
	}
	return nil
}

View on GitHub (pinned to 3ee70a1026)