Bug
spec.ContractDigest is documented as a stable digest of the task contract and explicitly excludes volatile projection fields ("updated timestamps ... intentionally excluded"). However it currently includes PlanningLog, whose PlanningEvent.Time entries are timestamps.
Consequence: two byte-identical task contracts that differ only in a planning-log timestamp produce different digests.
Impact
reviewgate.staleReviewAuthority compares a review's recorded ReviewedSpec digest against the current ContractDigest(model). Because the planning log mutates (timestamps) independently of the contract, a valid, unchanged review is falsely flagged review_stale_after_spec. This forces spurious re-reviews and can block scafld complete even though nothing in the contract changed.
- Class: correctness (staleness detection) / user-facing (wasted review cycles, blocked completion)
- Who hits it: anyone whose spec gains or changes planning-log entries after a review; the digest then no longer matches, regardless of contract content.
- What a fix touches:
internal/core/spec/model.go ContractDigest (drop PlanningLog from the hashed struct).
Reproduction
On current main:
m1 := spec.Model{Version:"2.0", TaskID:"t", Title:"x", Context: spec.Context{Packages:[]string{"p"}}}
m1.PlanningLog = []spec.PlanningEvent{{Time:"2026-01-01T00:00:00Z", Text:"a"}}
m2 := m1
m2.PlanningLog = []spec.PlanningEvent{{Time:"2026-06-01T00:00:00Z", Text:"a"}}
// spec.ContractDigest(m1) != spec.ContractDigest(m2) -> should be EQUAL
Proposed fix + regression tests in PR #10 (branch fix/contract-digest-planninglog-volatility).
Bug
spec.ContractDigestis documented as a stable digest of the task contract and explicitly excludes volatile projection fields ("updated timestamps ... intentionally excluded"). However it currently includesPlanningLog, whosePlanningEvent.Timeentries are timestamps.Consequence: two byte-identical task contracts that differ only in a planning-log timestamp produce different digests.
Impact
reviewgate.staleReviewAuthoritycompares a review's recordedReviewedSpecdigest against the currentContractDigest(model). Because the planning log mutates (timestamps) independently of the contract, a valid, unchanged review is falsely flaggedreview_stale_after_spec. This forces spurious re-reviews and can blockscafld completeeven though nothing in the contract changed.internal/core/spec/model.goContractDigest(dropPlanningLogfrom the hashed struct).Reproduction
On current main:
Proposed fix + regression tests in PR #10 (branch
fix/contract-digest-planninglog-volatility).