Files
orca/.github
Jinwoo Hong f949d5fcc4 ci(mobile): fail CI when the RPC recording pin leaves main's history or the corpus does not reproduce (#21156)
* test(mobile): fail CI when the RPC recording pin leaves main's history

`mobile/rpc-foundation/pilot-scenarios.json` carries the commit every golden
claims it was recorded from, and `--record` refuses on any other tree. A
behaviour-change branch pins its own last fenced commit, which stops being
reachable the moment the branch squash-merges: nobody can record on main again
until a hand-made repin lands, and until now only a human noticed. #21123 was
that, and so was the repin after #20954.

`scripts/rpc-recording-pin-guard.mts ancestry` fails when the pin is not an
ancestor of the commit under test, and prints the repin recipe. It refuses to
answer on a shallow clone rather than trusting grafted history, so the job
checks out with `fetch-depth: 0`. Ordinary product drift past a reachable pin
is not a failure.

`reproduce` makes the other claim the corpus header makes, which the recording
suites do not: they replay the goldens against the CURRENT tree, so a golden
recorded somewhere other than the pin -- a merge that auto-merged golden JSON,
a refresh copied back from a scratch directory -- passes them and is what the
header exists to deny. It checks the pin out detached, lays this tree's
recorder and manifest over it, and lets the same suites compare in place, so
the comparison is `compareGolden` with lockfile and platform masked as ever.

It runs unconditionally on a push to main, which has no `verify` job and is
where a squash lands a spliced corpus. On a pull request it runs only when the
corpus, the manifest or the recorder moved: nothing else can move the verdict
away from the one the base commit published, and `verify` replays the corpus
against the branch tree meanwhile.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): judge the recording pin against the tree it was read from

Round-1 review of the pin guard.

The pull_request ancestry check read the pin out of the merge preview and judged
it against the branch head. Those differ whenever main repins after the branch
point, so ordinary stale branches failed, and the instruction told the author to
repin to their own head -- which creates the unreachable pin the guard exists to
catch. Judge the checked-out tree instead.

`git worktree prune` in the reproduce teardown was repository-wide. This git
directory is shared by every worktree on the machine (611 registered here), so
it could deregister an unrelated one whose directory was momentarily missing.
`worktree remove --force` alone is enough; a failure to remove is now reported
rather than papered over.

Also: the concurrency group is per commit on main, because GitHub cancels a
pending run in a group whatever `cancel-in-progress` says; the skip gate fails
closed when a provenance path stops matching instead of skipping forever; the
census-boundary comment states the rule the code uses; and five exports with no
consumer are now module-private.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): let an untracked golden and the guard itself buy a reproduction

Two bot findings on the skip gate.

`git diff` sees tracked paths only, but the reproduction's overlay copy and its
census both read the corpus directory as it sits on disk, so an untracked golden
or manifest is input to the verdict and used to skip the run that would judge it.
Enumerate untracked entries under the provenance paths the way the recorder
already does, and run rather than skip: an unjudged local addition is the case
the reproduction exists for.

The guard script is now a provenance path of its own, so a change to it re-runs
the reproduction it implements. Left alone deliberately: run-process.ts and the
workflow's `paths:` scope over src/shared, which is a pre-existing gap for the
whole mobile workflow rather than this job's.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): refuse to reproduce when the suite list has drifted from the files

Round-2 review.

The suite names reach vitest as positional filename filters, and vitest exits 0
when only some of them match. A renamed census suite therefore dropped out of the
reproduction silently and the guard still printed that the corpus reproduces:
three files and 761 tests instead of four and 762, exit 0. Resolve every name
under the recorder overlay before spawning, and throw naming the drifted entry.
The unit case walks the list and omits each name in turn, so no single rename can
slip past it. This is the same fail-open shape as the renamed-pathspec finding.

Also: pass an explicit directory type to `symlink`, since Windows needs one and a
junction needs no privilege where a real symlink does; and build the throwaway
test repositories with `symbolic-ref` rather than `--initial-branch`, which needs
git 2.28 against a declared baseline of 2.25.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-17 02:34:03 -04:00
..