JuliusBrussee/caveman · error

proposalrun: seq %d recompute: %w

Error message

proposalrun: seq %d recompute: %w

What it means

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.

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 27d5a3981a)

Solutions

  1. 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.
  2. 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.
  3. Ensure all writes go through the package's own append/insert API so Detail is validated at write time.

Example fix

// before
err := proposalrun.VerifyChain(runs)
fmt.Println(err) // "proposalrun: seq 7 recompute: invalid JSON number exponent"

// after
err := proposalrun.VerifyChain(runs)
var inner error
for e := err; e != nil; e = errors.Unwrap(e) { inner = e }
log.Printf("seq 7 root cause: %v", inner) // then repair the Detail blob at that row
Defensive patterns

Strategy: try-catch

Try / catch

if err := proposalrun.VerifyChain(runs); err != nil {
    if strings.Contains(err.Error(), "recompute:") {
        root := err
        for errors.Unwrap(root) != nil { root = errors.Unwrap(root) }
        // root is the canonicalization failure; repair the Detail at the reported seq
        log.Printf("chain verify: root cause at seq: %v", root)
    }
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/bb782f2b337b3330. Report an issue: GitHub.