go-struct-digest-must-exclude-volatile-and-self
Diagnosed and verified. Solution written to ~/SOLUTION.md, with a runnable reference implementation in ~/structdigest/.
Root cause — A digest stored on the record must exclude three fields, on every computation path:
- Compiled (volatile timestamp) — must be set to time.Time{}, not a truncated copy
- ReqID (volatile request id)
- Digest (self-reference)
The classic miss is the JSON recomputation path: json.Unmarshal restores a real time.Time, so the zeros do not survive the round-trip and must be re-cleared explicitly.
Fix — One canonical() helper (value receiver + copy, zero all three) used by ComputeDigest, and DigestFromJSON clears the same three fields before hashing.
Verification performed
- 4 tests pass on the fixed code: volatile-ignoring, self-exclusion, JSON round-trip, content-tracking.
- Mutation proof:
- Compiled-zeroing → no-op ⇒ TestDigestIgnoresVolatileFields red.
- Self-exclusion → self-reference ⇒ TestDigestExcludesItself and TestDigestRoundTripsThroughJSON red.
- Bonus finding on the map caveat: I measured it — encoding/json sorts map keys, so plain string-keyed maps under the struct are stable (distinct digests: 1 across 200 iterations × 8 processes). The map hazard applies to custom/alternative encoders, not standard json.Marshal. I documented this correction with the evidence.
# Evidence - Problem class: go-struct-digest-must-exclude-volatile-and-self - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T23:58:30.724Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A stable digest over a struct that is stored ON a record must exclude (a) the volatile per-generation fields (request id, timestamp) and (b) the digest field itself, or it is neither comparable nor recomputable. Recipe that passed: copy the value, zero the volatile fields to their ZERO values (time.Time{} not a truncated copy), zero the digest field, json.Marshal (deterministic ONLY while no map lives anywhere under the struct - Go randomizes map iteration), sha256, hex. A JSON-side recomputation path must clear the same three fields itself, because after unmarshalling a time.Time is a real timestamp again and reaches the zero value only when something explicitly zeroes it. Proof that both exclusions are load-bearing: mutating compiledAt-clearing into a no-op and the self-exclusion into a self-reference each turns the determinism/round-trip tests red.", "environment": "", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-struct-digest-must-exclude-volatile-and-self", "provider": "openrouter", "solved_at": "2026-09-17T23:58:30.724Z", "version": ""}Diagnosed and verified. Solution written to ~/SOLUTION.md, with a runnable reference implementation in ~/structdigest/.
Root cause — A digest stored on the record must exclude three fields, on every computation path:
- Compiled (volatile timestamp) — must be set to time.Time{}, not a truncated copy
- ReqID (volatile request id)
- Digest (self-reference)
The classic miss is the JSON recomputation path: json.Unmarshal restores a real time.Time, so the zeros do not survive the round-trip and must be re-cleared explicitly.
Fix — One canonical() helper (value receiver + copy, zero all three) used by ComputeDigest, and DigestFromJSON clears the same three fields before hashing.
Verification performed
- 4 tests pass on the fixed code: volatile-ignoring, self-exclusion, JSON round-trip, content-tracking.
- Mutation proof:
- Compiled-zeroing → no-op ⇒ TestDigestIgnoresVolatileFields red.
- Self-exclusion → self-reference ⇒ TestDigestExcludesItself and TestDigestRoundTripsThroughJSON red.
- Bonus finding on the map caveat: I measured it — encoding/json sorts map keys, so plain string-keyed maps under the struct are stable (distinct digests: 1 across 200 iterations × 8 processes). The map hazard applies to custom/alternative encoders, not standard json.Marshal. I documented this correction with the evidence.
# Evidence - Problem class: go-struct-digest-must-exclude-volatile-and-self - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T23:58:30.724Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A stable digest over a struct that is stored ON a record must exclude (a) the volatile per-generation fields (request id, timestamp) and (b) the digest field itself, or it is neither comparable nor recomputable. Recipe that passed: copy the value, zero the volatile fields to their ZERO values (time.Time{} not a truncated copy), zero the digest field, json.Marshal (deterministic ONLY while no map lives anywhere under the struct - Go randomizes map iteration), sha256, hex. A JSON-side recomputation path must clear the same three fields itself, because after unmarshalling a time.Time is a real timestamp again and reaches the zero value only when something explicitly zeroes it. Proof that both exclusions are load-bearing: mutating compiledAt-clearing into a no-op and the self-exclusion into a self-reference each turns the determinism/round-trip tests red.", "environment": "", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-struct-digest-must-exclude-volatile-and-self", "provider": "openrouter", "solved_at": "2026-09-17T23:58:30.724Z", "version": ""}