Skip to content

docs: keep size review in CI and local feedback fast - #1842

Merged
thymikee merged 10 commits into
mainfrom
feat/pr-evidence
Aug 19, 2026
Merged

docs: keep size review in CI and local feedback fast#1842
thymikee merged 10 commits into
mainfrom
feat/pr-evidence

Conversation

@thymikee

@thymikee thymikee commented Aug 18, 2026

Copy link
Copy Markdown
Member

Summary

Keep package-size measurement in CI and keep the local developer loop focused on the smallest
relevant correctness gates.

  • The CI Size workflow remains the authoritative source for package-size deltas; local base builds
    and size comparisons are not required by default.
  • Reviewers escalate scrutiny when a PR adds roughly 700 or more net production lines or increases
    npm unpacked size by more than 3 kB. The thresholds trigger a design review, not an automatic
    rejection.
  • The review asks whether a deeper owning interface, stronger types, less ceremony, reuse of an
    existing construction path, or deletion of superseded code can make the change materially smaller.
  • Fast local feedback is recorded as a project value: informational measurements stay in CI unless
    they are needed to diagnose a reported result.

This replaces the earlier pnpm pr:evidence and pnpm size --base implementation. The PR now adds
no commands, scripts, tests, cache protocol, worktree orchestration, or workflow behavior.

Validation

pnpm check:affected --run passed on exact head c21ccbc14. Because
docs/agents/testing.md owns the affected selector's guidance, the selector deliberately failed open
to the full local gate set; all runnable checks passed. GitHub remains authoritative for the queued
CI lanes.

Scope: 3 documentation files. No runtime, package, workflow, or device-facing behavior changed.

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-19 12:23 UTC

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.31 MB 2.31 MB 0 B
JS gzip 759.3 kB 759.3 kB 0 B
npm tarball 882.6 kB 882.6 kB +36 B
npm unpacked 3.08 MB 3.08 MB +306 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.0 ms 26.0 ms -0.1 ms
CLI --help 68.6 ms 67.9 ms -0.6 ms

Top changed chunks: no changes in the largest emitted chunks.

@thymikee

Copy link
Copy Markdown
Member Author

Not ready at exact 5653f9c4.

  • P1: pr:evidence calls mkdtempSync under untracked .tmp without creating the parent, so it fails ENOENT in a fresh checkout; create it and add a real entrypoint/temp-repo regression.
  • P1: Untracked production files can affect layering/depgraph measurements while the report labels evidence clean and exact-HEAD; include untracked state or measure from an isolated HEAD tree.
  • P1: size --base force-prunes other cached base worktrees without locking, so concurrent different-base runs can delete an active build and same-base runs race partial dist; add ownership/locking or non-destructive per-SHA cache semantics plus orchestration tests.

Pure model tests do not cover these paths.

@thymikee

Copy link
Copy Markdown
Member Author

All three addressed in 159ca67 (rebased onto current main).

  • Scratch / ENOENT — the scratch now lives under os.tmpdir() (exists by construction), so the class is gone rather than patched. Real entrypoint regression scripts/pr-evidence/run.test.ts: runs pr:evidence --base HEAD --json in this repo (merge-base is HEAD, so no origin/main and no network — a depth-1 CI checkout suffices) through the whole pipeline — both worktrees, selector, layering guard, depgraph twice — and asserts the JSON (head == base, changed files empty, head graph > 500 files and equal to base) plus that both worktrees are unregistered and the scratch is gone. ~19s, wired into pr:evidence:test.
  • Untracked state vs "exact-HEAD" — head is now measured from a pristine git worktree of the head commit exactly like base (layering guard and depgraph both run with that worktree as cwd; the scripts analyze their cwd while imports resolve from this checkout), and the affected plan is asked for the head SHA — the literal HEAD folds working-tree changes in. The dirty flag now counts untracked files and reads "working tree has uncommitted/untracked changes: none of them are in this block".
  • size --base locking / cache — per-SHA .lock (pid inside, O_EXCL) held from before the worktree exists until the base report has been read; a concurrent run on the same base fails fast with the owner pid; a stale lock (dead pid) is replaced; eviction of other bases skips any worktree whose lock owner is alive; dist/.size-base-complete is written after a successful build and an unstamped worktree is rebuilt. Orchestration tests (scripts/__tests__/size-report-base.test.ts, unit-core) run the real script against a throwaway git repo with pnpm/npm shimmed on PATH: build once + reuse, live lock fails fast without touching the worktree, stale lock proceeds, interrupted build rebuilds, other-base run evicts idle but not live-locked. Those tests surfaced a real bug in the previous version — git lists worktrees by realpath (/tmp/private/tmp), so the registration check misfired and removed a live worktree; the cache root is now canonicalized and a registered-but-missing entry is pruned.

Also fixed the two [size]-only pieces the earlier review didn't call out but the tests forced: git's worktree add stderr is now surfaced instead of swallowed. Lint/typecheck/fallow clean; pr:evidence:test 7/7; the real pnpm size --base origin/main run rebuilt the new base and evicted the old one.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Not ready at exact 159ca676.

  • P1: Lock creation/stale takeover can unlink a live lock during the transient empty-file or check/unlink race. Use atomic owner identity and compare before unlink, with real overlapping-process tests.
  • P1: Cross-base eviction only checks the victim lock, then removes without owning it, so a run can acquire it after the check. Evict only while atomically holding the victim claim.
  • P1: pr:evidence records worktrees only after both adds, so second-add failure leaks the first, and one cleanup failure skips remaining resources. Register immediately and make cleanup exhaustive with planted failures.

Also refresh the stale evidence SHAs and scope disclosure (13 files, not 10). The pristine-head/untracked correction is sound.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed in 48761c7 (rebased onto current main; PR body evidence block regenerated on this head, scope disclosure now 15 files).

  • Lock races — the lock is a symlink whose target is the owner identity pid:nonce, so it is created with its identity in place by one syscall (no empty-file window to misread as stale) and fails EEXIST while held. Stale takeover is compare-then-unlink on the exact identity judged stale (a link that has since come to name someone else is left alone), every acquisition verifies the link names this run before returning, and release unlinks only a link that still names this run. Real overlapping-process tests: two concurrent runs on one base with a slowed shim build → exactly one build, the other fails fast on the live lock; and a takeover race against a simulated other taker (also compare-then-unlink) fired at delays straddling the acquire window — across 3×4 rounds both orders occurred (run won 3, C won 1), the live lock was never unlinked, and both never proceeded.
  • Cross-base eviction — a victim is evicted only while holding its lock, acquired through the same path (a live-locked victim is skipped), so a run wanting that base after the check finds it locked and fails fast rather than finding it half-removed.
  • pr:evidence worktreeswithWorktrees registers each worktree the moment its add succeeds and sweeps every resource on the way out, collecting failures and throwing them together (with the original error as cause) instead of stopping at the first. Planted reds: second add fails → the first is removed (the old shape leaked it); the middle removal throws → the others still go and the scratch is still attempted (the old shape stopped).

Lint/typecheck/fallow clean; pr:evidence:test 11/11; size-report-base 7/7 stable across three runs; the real pnpm size --base origin/main run rebuilt the new base under lock. [size]'s regular-file/garbage at the lock path is removed rather than wedging.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Housekeeping: my earlier fix push (48761c7c2, ~17:18) reached the branch but GitHub never attached it to this PR — the PR head stayed at 159ca6767 with 0 check runs on the new commit — which is why the 16:36 findings looked unaddressed on the PR page. Rebased onto current main; the PR now points at 388e0271a, which carries all of the second-round fixes described in my previous comment (symlink-identity locks with compare-then-unlink and verified acquire, eviction under the victim's lock, withWorktrees registering on add and sweeping exhaustively, real overlapping-process + takeover-race tests). Local gates on this head: lint/fallow clean, pr:evidence:test 11/11, size-report tests 8/8, gate manifest ok. CI is running on it now.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 388e0271. The immediate worktree cleanup and victim-claim blockers are fixed, but this is not ready yet.

  • P1 — stale lock removal is still non-atomic. unlinkIfIdentity() does readlink then unlink, and removeIfNotSymlink() does lstat then rm. Another taker can replace the observed stale/garbage entry with its live symlink between those syscalls, after which this run deletes the replacement live lock. The takeover test does not deterministically pause in that window or require both orderings, so it can remain green without proving this race. Use an ownership/removal primitive that cannot delete a replacement and add a planted overlapping-process regression for the exact interleaving.
  • Architecture — extract before adding more behavior. scripts/size-report.mjs is now 719 LOC versus 512 on the exact base; AGENTS.md requires extraction past 500. The lock/cache ownership protocol is a distinct module question and should move to a focused module with mirrored tests.
  • Refresh the validation/body evidence: it is still stamped 48761c7c2 / base 294654a3a, not exact 388e0271 / b12a3e3, and it says --base orchestration is intentionally unautomated despite the newly wired subprocess tests.

Exact-head deterministic, integration, and smoke lanes are green. Coverage is the inherited #1860/main snapshot-handler ratchet, not introduced by this diff.

The Size workflow already compares base and PR builds; locally that needed a
manual checkout, install, build, --json, and --compare dance, so budgets were
negotiated late. --base <ref> does the workflow's recipe in a detached worktree
under .tmp/size-base/<sha> (kept for reuse, other bases pruned) and compares
against it: first run ~1-2 min, later runs against the same base ~3s.

Documents the local caveat: npm tarball/unpacked rows compare a fresh base
against a working tree that may carry locally built helper artifacts.
…ce block for PR bodies

Composes what the repo already measures instead of hand-transcribing it after
every rebase: exact merge-base and head, changed-file areas, the affected
selector's plan (local vs GitHub-authoritative, fail-open summarized), the
layering guard verdict, depgraph counts with a real delta against the base (a
throwaway git worktree, no install — the script analyzes its cwd while its
imports resolve from this checkout), and, behind flags, the changed-line
coverage table and pnpm size --base. It claims nothing about CI: the last line
links the head's checks. ~20s default tier.

The pure model (grouping, report parsing, rendering) has node:test coverage
registered as the pr-evidence-model gate, run in the Affected-check Selector
job next to the selector it reads.
…an os.tmpdir scratch; size --base gets a per-SHA lock, completion stamp, and non-destructive eviction

Review (three P1s):
- pr:evidence created its scratch under an untracked .tmp/ that a fresh
  checkout lacks (ENOENT). Scratch now lives under os.tmpdir(), which exists
  by construction; a real entrypoint regression runs the whole pipeline with
  --base HEAD (no origin/main needed) and asserts JSON shape plus cleanup of
  both worktrees and the scratch.
- Untracked or uncommitted production files could move the layering/depgraph
  numbers the block labels as HEAD's. Head is now measured from a pristine
  worktree of the head commit exactly like base, and the affected plan takes
  the head SHA (the literal HEAD folds the working tree in). The dirty flag
  now counts untracked files and says they are not in the block.
- size --base force-pruned other cached bases without locking and trusted a
  dist/src that could be half-built. Per-SHA .lock (pid, O_EXCL) held from
  before the worktree exists until the base report is read; a live lock on
  the same base fails fast, a stale one is replaced; eviction skips worktrees
  whose lock owner is alive; dist/.size-base-complete marks a finished build.
  Orchestration tests run the real script against a throwaway git repo with
  pnpm/npm shimmed on PATH (build once, reuse, live lock, stale lock,
  interrupted build, guarded vs idle eviction). Also fixes the /tmp →
  /private/tmp realpath mismatch those tests surfaced (git lists worktrees by
  real path, so the registration check removed a live worktree).
…under the victim's lock; pr:evidence registers worktrees on add and cleans up exhaustively

Review (three P1s):
- Lock creation/takeover races: the lock is now a symlink whose target is
  the owner identity (pid:nonce), created with its identity in one syscall
  (no empty-file window), taken over only by compare-then-unlink on the exact
  identity judged stale, and verified after creation; release unlinks only a
  link that still names this run. Real overlapping-process tests: two runs on
  one base (exactly one builds, the other fails fast), and a takeover race
  against a simulated other taker across delays straddling the acquire window
  (a live lock is never unlinked, both never proceed).
- Cross-base eviction: a victim is removed only while holding its own lock,
  acquired through the same path, so a run wanting it after the check finds
  it locked rather than half-removed; a live-locked victim is skipped.
- pr:evidence worktrees: withWorktrees registers each worktree the moment its
  add succeeds and sweeps every resource on the way out, collecting failures
  instead of stopping at the first; planted reds for both (second add fails →
  first removed; removal of the middle one throws → the others still go).
…er real spawners

Caught running the full unit suite on the rebased branch: the file passed in
isolation but intermittently failed under broad file parallelism, where it took
14s versus ~5.5s alone. It spawns node scripts/size-report.mjs per case, which
spawns git and the shimmed package managers under it — the SUBPROCESS_STUB_TESTS
class exactly (starved spawns surface as a vitest test timeout instead of the
orchestration assertion the case is about), so it joins that serialized project
with its spawn named at the entry, per docs/agents/testing.md. No rerun layer is
involved: the flake is removed, not retried. Two full-suite runs green after.
@thymikee

Copy link
Copy Markdown
Member Author

Rebased onto current main (past #1860, so the inherited ratchet red is gone) and fixed one thing I caught myself while validating the rebase.

size-report-base.test.ts was flaky under the full suite. It passes in isolation (7/7, repeatedly) but intermittently failed in a full vitest run, where the file took 14s versus ~5.5s alone. Cause is the documented one: it spawns node scripts/size-report.mjs per case, which spawns git and the shimmed pnpm/npm under it, and under broad file parallelism those spawns get starved past their budget — surfacing as a vitest test timeout instead of the orchestration assertion the case is about. So it joins SUBPROCESS_STUB_TESTS (serialized, fileParallelism: false), with the concrete spawn named at the entry as docs/agents/testing.md requires, and the doc's membership note records the addition. No retry layer involved — per #1781 A4 the flake is removed, not rerun.

Evidence: two consecutive full-suite runs green afterwards (919 files / 6,990 tests each); subprocess-stub project 4 files / 46 tests green; pr:evidence:test 11/11; gate manifest ok (47 checks); lint/typecheck/fallow clean.

Everything from the previous round is unchanged and now actually attached to the PR head (52f025df1) — GitHub had silently dropped my 17:18 push, which is why the second-round fixes looked missing on the PR page.

🤖 Addressed by Claude Code

…takeover atomic

Review (P1 + architecture):

Stale-claim removal was compare-then-unlink (readlink then unlink; lstat then
rm for a stray file), so another taker could replace the observed entry with
its live claim between the two syscalls and this run would delete the
replacement. Removal now happens only while holding the entry's takeover mutex
— an atomically created directory — and re-verifies the claim inside it. A
replacement can appear only by creating one on a free path (the abandoned
claim occupies it until the unlink) or by another takeover (needs the mutex),
so removal cannot delete a replacement. A mutex leaked by a process killed
inside its sub-millisecond critical section is reclaimed by age, and even a
wrong reclamation is contained: both takers re-verify inside, and the winner
is still decided by the atomic symlink() that follows.

The protocol moves out of size-report.mjs into scripts/size-base-cache.mjs
(AGENTS.md: extract past 500 LOC) — 719 → 536, with the entry lifecycle
(claim → evict others → ensure worktree → build if unstamped → measure →
release) owned by the module behind withPreparedBaseWorktree. Mirrored tests
in scripts/__tests__/size-base-cache.test.ts plant every dangerous
interleaving directly on the filesystem: replacement-after-observation, a
takeover held by another run, age reclamation, release-after-retarget, and a
stray non-symlink. They need no subprocess and run in 9ms, so the raced
single-process case was dropped from the orchestration file, which keeps only
what real processes can show. Planted red: removing the mutex makes the
contended case delete the claim it must not touch.
@thymikee

Copy link
Copy Markdown
Member Author

All three addressed at exact 81544f326. My previous comment only covered the rebase and the flake — apologies, the three findings from 388e0271 are answered here.

P1 — non-atomic stale removal. Correct, and removeIfNotSymlink's lstatrm had the same shape. Removal of an abandoned claim (and of a stray non-symlink) now happens only while holding the entry's takeover mutex, an atomically created directory, and re-verifies the claim inside it. A replacement can appear only by creating a claim on a free path — impossible, the abandoned claim occupies it until the unlink — or by another takeover, which needs that same mutex. So the window the finding names no longer exists rather than being narrowed. A mutex leaked by a process killed inside its sub-millisecond critical section is reclaimed by age, and even a wrong reclamation is contained: both takers re-verify inside, at most one unlink lands, and the winner is still decided by the atomic symlink() that follows.

The regression is now planted rather than raced, which is why it's deterministic: size-base-cache.test.ts drives the protocol directly on the filesystem — replacement-after-observation returns changed with the replacement intact and acquire then refuses; a takeover mutex held by another run returns busy with the claim untouched and acquire reporting contention. Planted red: drop the mutex and the contended case deletes the claim it must not touch (× removal cannot run at all while another run holds the takeover mutex). The single-process "takeover race" case in the orchestration file was removed — it raced for a window it could not deterministically hit, exactly your point.

Architecture — extract past 500. Done: scripts/size-base-cache.mjs owns the claim/cache ownership protocol and the entry lifecycle behind one withPreparedBaseWorktree seam; size-report.mjs is 536 (was 719, base 512) and its measureBaseRef is now a two-line recipe. Mirrored tests as asked: protocol tests beside the module (8 tests, 9ms, no subprocess), orchestration tests keeping only what real processes can show. Module surface is exactly what's consumed — fallow flagged my over-exports and they're gone, no test-only exports.

Body evidence. Regenerated at exact 81544f326 / base fda81c512 (via pnpm pr:evidence --size — dogfooded), and the stale "intentionally unautomated" line is gone: it now names both automated lanes and why the orchestration file is serialized.

Full unit suite 920 files / 6,997 tests; gate manifest ok; lint/typecheck/fallow clean; a real pnpm size --base origin/main rebuilt the new base under claim and evicted the old entry.

🤖 Addressed by Claude Code

…t stays whole

The Size workflow measures the base commit with the PR's reporter, so it
copies the reporter out of the tree before checking the base out. Extracting
size-base-cache.mjs made the reporter a two-file graph while the step still
copied one file, and the base measurement died with ERR_MODULE_NOT_FOUND —
after every deterministic gate had passed, because nothing local reproduces
that copy.

The step now copies the scripts directory, so a further split cannot leave an
import behind, and size-report-preserved-closure.test.ts holds it to the
reporter's real relative-import closure and to running the preserved copy
rather than the checked-out tree. Planted red: restoring the single-file copy
fails both cases, naming scripts/size-base-cache.mjs. Verified by running the
reporter from a copied directory exactly as the workflow does.
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head dcf88ae8: still not ready.

  • P1 — takeover ownership can still split. The direct claim check/unlink race is prevented only while the takeover directory has one owner, but enterTakeover() force-removes that directory solely because its mtime is older than 30s, with no owner identity. A live holder paused or SIGSTOPed past that threshold can have its mutex removed and replaced by B. A and B then both execute the supposedly exclusive readlink/unlink section; A’s unconditional pathname-based leaveTakeover() can also delete B’s replacement mutex. One taker can consequently unlink the other’s newly created live claim, recreating the original race. The age-reclamation test covers only a genuinely abandoned mutex, and the real-process overlap covers ordinary live claims—not a live delayed takeover holder crossing the reclamation threshold. Use ownership/release semantics that cannot force-remove or release a replacement (or do not auto-reclaim), and plant that exact delayed-holder/two-owner interleaving.
  • Architecture blocker remains. Extracting size-base-cache.mjs improved locality, but scripts/size-report.mjs is still 536 LOC versus 512 on base. AGENTS.md requires extraction before adding behavior past 500; split another coherent reporter/base-comparison question with mirrored tests.
  • Refresh the body/evidence again: it is stamped 81544f326 and says 17 files, while exact head is dcf88ae8 and GitHub reports 19 files.

The latest workflow reporter-closure fix and its regression look sound. Exact-head deterministic, Coverage, Integration, Android, and macOS lanes are green; iOS and Linux smoke were still pending at review time.

…ublishing out of the reporter

Review (P1 + architecture):

Age-based reclamation of the takeover mutex reintroduced the split ownership
the mutex exists to prevent: a holder that is merely slow — paused or
SIGSTOPed past any threshold — could have its mutex force-removed and
replaced, putting two takers inside the supposedly exclusive section, where
either could unlink the claim the other had just created; the unconditional
pathname-based release could also delete the replacement mutex. The mutex is
now a symlink naming its holder, created in one syscall, never reclaimed at
any age, and released only by the run that owns it. A mutex leaked by a
process killed inside a three-syscall critical section wedges one cache entry
with the path to clear in the message, rather than silently deleting another
run's live claim. Planted red: restoring age reclamation displaces a
day-old delayed holder, which the new case pins.

Publishing the report to a PR is a separate question from measuring and
formatting it, so it moves to scripts/size-report-comment.mjs with the marker
and retry policy it owns; its existing regression drives it through the real
script unchanged. scripts/size-report.mjs is 386 LOC — under the 500 tripwire
and below the 512 it had on base.
@thymikee

Copy link
Copy Markdown
Member Author

All three addressed at exact f6dd6f505.

P1 — split takeover ownership. You're right, and the fault was mine for adding age reclamation to close a leak: it recreated exactly the hazard the mutex removes, since "old" cannot distinguish an abandoned holder from a slow one (paused, SIGSTOPed, a loaded runner). The mutex is now a symlink naming its holder, created in one syscall, never reclaimed at any age, and released only by the run that owns it — so it has one holder for its whole life, and it can neither force-remove nor release a replacement. The two-owner section, and with it the "A's release deletes B's mutex" path, are gone by construction rather than narrowed.

The cost is the leak you'd expect me to name: a process killed inside a three-syscall critical section with no I/O between them wedges one cache entry, loudly, with the path to clear in the error message. I chose that over the alternative deliberately — a wedged entry is a self-describing error, a wrong reclamation is a silently deleted live claim.

Planted interleaving, as asked: a holder whose mutex is a day old is still never displaced — removeAbandonedClaim returns busy, the holder's mutex is intact, the claim is untouched, and acquireBaseClaim reports contention naming the mutex path. Planted red: restoring age-based reclamation fails exactly that case (× a delayed takeover holder is never displaced, however old its mutex looks). A second case pins that a leaked mutex wedges only its own entry — a different entry still claims normally.

Architecture. Second split done: publishing the report to a PR is a different question from measuring and formatting it, so it moved to scripts/size-report-comment.mjs with the comment marker and retry policy it owns; its existing regression drives it through the real script unchanged. scripts/size-report.mjs is now 386 LOC — under the 500 tripwire and below the 512 it had on base.

Body/evidence. Regenerated at exact f6dd6f505 / base fda81c512, and the scope line now says 20 files with the breakdown (and that no src/ is touched).

Full suite 921 files / 7,000 tests; size lanes 14/14; lint, typecheck, fallow clean; a real pnpm size --base origin/main run and the preserved-copy path (cp -R scripts /tmp/… && node …/size-report.mjs) both verified after the split.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Updated review at exact head bb22b4606: the previous production and architecture blockers are fixed, and the last regression-proof gap is now corrected.

The delayed-holder regression now plants the actual pre-fix takeover shape—an aged directory mutex. It passes 9/9 on current code and fails against pre-fix dcf88ae8 with removed !== busy, proving that the old age-based reclamation displaced a merely delayed holder while the new owner-identity/no-reclamation protocol leaves it intact. scripts/size-report.mjs is 386 LOC with the cache and comment-publishing questions extracted. I have no remaining code finding.

Local required gate passed before push: pnpm check:affected --run && git push (affected Coverage: 986 files / 7,256 tests; all runnable gates green). This is not merge-GTG yet only because restarted GitHub checks on exact bb22b4606 must complete green; do not apply ready-for-human while they are pending.

Minor body cleanup after CI: the claim-protocol paragraph says 8 tests but now has 9, and the evidence block is stamped to the prior head, so refresh both on the final exact head.

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact bb22b46067980d516e0e40bdef5363d6b0230be3: no code finding remains. The corrected aged-directory takeover fixture now reproduces the actual pre-fix mutex shape and deterministically fails against dcf88ae8, so the regression is non-vacuous. Not merge-ready yet: exact-head Android Smoke failed with unrelated wait_capture_stalled and needs a green rerun; other required lanes were still running. Refresh the body’s evidence SHA/CI link and test count (9, not 8). If exact-head checks complete green, this is GTG.

@thymikee

Copy link
Copy Markdown
Member Author

Both body items done at exact bb22b4606: the evidence block and CI link are regenerated on this head, and the claim-protocol line now says 9 tests (~10ms). Android Smoke re-run is queued — same wait_capture_stalled on the wait for landscape rotation step I verified in the log, matching your read.

On the fixture correction (bb22b4606): thank you, and the methodology point is mine to own. My planted red used a symlink mutex with an aged mtime, which is not the shape dcf88ae8 actually had — so I proved the red against a hybrid of my own making rather than against the real pre-fix code, exactly the vacuity the repo's "prove it against the pre-fix code" rule exists to catch. I re-verified your version independently: restoring scripts/size-base-cache.mjs from dcf88ae8 and running the file fails with 'removed' !== 'busy' on the delayed-holder case (and the leaked-mutex case too), green again on restore. That is a genuine regression proof now.

I'm not touching the ready-for-human label while checks are pending, per your note.

🤖 Addressed by Claude Code

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 19, 2026
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed readiness at exact head bb22b46067980d516e0e40bdef5363d6b0230be3. The body now carries the current evidence SHA and the corrected 9-test claim-protocol count. The Android Smoke rerun is green, all other exact-head required/authoritative lanes are green, and GitHub reports the branch mergeable with a clean merge state. Prior code review remains clean; this is ready for human review and merge.

@thymikee thymikee changed the title feat(tooling): pnpm pr:evidence composer and pnpm size --base <ref> docs: keep size review in CI and local feedback fast Aug 19, 2026
@thymikee
thymikee merged commit ef2094c into main Aug 19, 2026
8 checks passed
@thymikee
thymikee deleted the feat/pr-evidence branch August 19, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant