Skip to content

schemaTreeIsStale and bundlesAreStale carry the same mtime false refusal #14985 fixed for distIsStale — measured, both refuse on a tree whose bytes never moved #16175

Description

@huangyiirene

Found while implementing #14985 (branch claude/issue-14985-dist-freshness-false-refusal). Out of scope there and deliberately not fixed in that PR: the two sibling rules need different evidence from the one that card supplies, and one of them has no evidence to read at all.

What #14985 fixed, and what it did not

scripts/check-regen-pending.mjs exports three freshness predicates, all built on the same newestMtime(artifact) < newestMtime(src) comparison:

predicate artifact it measures fixed by #14985
distIsStale packages/spec/dist/**/*.d.ts yes
schemaTreeIsStale packages/spec/json-schema/**/*.json no
bundlesAreStale packages/spec/dist/**/*.mjs, *.js no

All three share the blind spot #14985 measured: a git merge, git checkout or git worktree add re-checks-out a source file with identical bytes and bumps its mtime; the build that follows correctly does not run, because turbo's cache hashes content (measured on that card: Cached: 1 cached, 1 total, >>> FULL TURBO, 56ms, and every dist/ mtime unmoved); the rule then reports stale for an artifact that is exactly current.

Measured, not inferred

On the #14985 branch, after a single touch packages/spec/src/data/query.zod.ts with no content change (git status empty), running the derived gate set:

pnpm --filter @objectstack/spec run check:docs                       exit 1
  packages/spec/json-schema is older than packages/spec/src.          (schemaTreeIsStale)

pnpm --filter @objectstack/spec run check:browser-reachable-entries  exit 1
  packages/spec/dist's .mjs/.js bundles are OLDER than packages/spec/src   (bundlesAreStale)

Both cleared only by a full pnpm --filter @objectstack/spec build. Same cost as the card: minutes under the shared verify lock, for an artifact that needed nothing.

Why it was not fixed alongside

#14985's remedy is a stamp recording the digest of the build inputs a declaration-emitting build consumed, read as an acquittal the mtime rule can be answered with. The two siblings do not inherit it, and not for the same reason:

  • bundlesAreStale is the cheap one. The existing dist/.build-input-hash is written by every build, OS_SKIP_DTS=1 included — and that flag is exactly the one that does emit fresh bundles, so the hole that ruled that stamp out for the declarations is not a hole here at all. The evidence already exists and is already correct for this axis; what is missing is the wiring and its pins. One caveat to measure rather than assume: this rule also counts tsup.config.ts, which is in the digest's input set, so that half looks covered — worth confirming.
  • schemaTreeIsStale has no evidence to read. Nothing records which sources json-schema/ was generated from. gen:schema runs as the build's first step, so a matching dist/.build-input-hash does imply the tree was regenerated from these inputs — but gen:schema is also run standalone, and it writes no stamp of its own, so the common case would degrade to "no evidence" and change nothing. Making this axis work means giving gen:schema a stamp, which is a new artifact and a new decision.

So this is two changes with two different soundness arguments and two sets of pins, not a mechanical extension of the one that just landed — which is why it is filed rather than ridden in.

The direction that must be preserved

Whatever is done here, the property #14985 protects is the one to keep: the digest may only ever acquit a tree the mtime rule has already accused. A missing, unreadable or mismatched stamp must leave the refusal standing, so nothing that passes today can start failing and no rule can be talked into believing an artifact it never measured (#4690). Read distIsStale's docblock in scripts/check-regen-pending.mjs and inspectDeclarationStamp in scripts/build-input-hash.mjs before touching either sibling.

Dedupe

search_issues, repo-scoped, with a positive control run first in the same session: the control gate refuses because dist is older than src mtime false staleness returned #14985 and #7122, so the instrument was answering. The query for this finding returned #14985, #15822, #13560, #9068, #7681, #7181 — no twin. #15822 is a different script (newestSourceMtime walking gitignored paths); #7181 is the already-landed adoption of the .d.ts precondition by three more gates.

Not labelled: this is a concrete defect, left for the triage seat to rank.

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

    bugSomething isn't workingdomain:devxpm:queuepm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchpriority:p3tooling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions