Skip to content

ContractDigest is unstable: includes volatile PlanningLog timestamps, causes false review_stale_after_spec #11

Description

@mamonisme

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions