mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
* fix(mobile): move the last six reply-enum pins where tsc looks mobile/tsconfig.json excludes *.test.ts, so a `Record<HostUnion, true>` coverage record in a schema test is never typechecked: the two that existed (SshConnectionStatus, GitHubProjectOwnerType) checked nothing, and the four closed enums beside them had only a doc citation of the host type. Each arm list moves into its schema module as hostUnionArms<Union>(), which #21269 introduced for the same reason, and each test iterates the exported list instead of holding its own copy: - SSH_CONNECTION_STATUS to SshConnectionStatus - PROJECT_OWNER_TYPE to GitHubProjectOwnerType - DETAIL_FILE_STATUS to GitHubPRFile['status'] - PUSH_TEST_REFUSAL_REASONS and PUSH_REGISTER_REFUSAL_REASONS to the refusal arms of MobilePushTestResult and MobilePushRegisterResult - SETUP_RUN_POLICIES to SetupRunPolicy openEnum's parameter widens from a non-empty tuple to `readonly string[]` so a hostUnionArms list can feed it. z.enum already accepts the same, so the tuple constraint only excluded callers zod itself takes; behaviour unchanged. Twelve mutations prove the pins: dropping one arm and adding a bogus one each fail mobile tsc in all six places. Zero goldens move, the schemas' behaviour being unchanged, and the 21 recording suites pass at the existing baseline. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): fix the type errors in eighteen test files Found by typechecking the tests for the first time (see the config that follows). All mechanical, none weakens a product type: - 67 `act(() => vi.advanceTimersByTime(...))` callbacks return VitestUtils where act wants void, so each becomes a block. The async ones await only a genuinely promise-returning call, so no extra microtask tick is introduced. - Four fixtures were stale against a product type that gained a required member: MobileViewState.alwaysShowDefaultBranch, PrSidebarData.checksError, the branch-compare summary's errorMessage, and SessionOptionDescriptor's transport, which #20884 added precisely so a producer could not inherit the wrong lane's rendering by omission. - `getLastConnectedAt` on the shared relay fake was typed `() => null`, which refused the timestamp two escalation suites assign to it. - Two holders used before assignment take `!`, one `advance!.kind === ...` becomes `advance?.kind`, one widened status arm takes `as const`, and the Expo notification fixture keeps `data` required because the dismissal cases assign through it. 631 test files pass, 6222 tests, unchanged. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): typecheck the test files, on a ratchet mobile/tsconfig.json excludes *.test.ts so Metro never compiles tests into the release bundle, and vitest transpiles without checking types. Nothing had ever typechecked a mobile test, which is why a `Record<HostUnion, true>` pin written in one proved nothing and why 144 of the 630 test files had drifted. tsconfig.test.json is that program with the tests put back, behind `typecheck:tests`. Four files stay out: they import the desktop main process or src/shared/child-process, which are written against @types/node, and this program's libs are React Native's, where setTimeout answers a number rather than a NodeJS.Timeout. Pulling that graph in reports ~280 errors about the desktop rather than about mobile; vitest runs those four under Node, which is where they belong. The CI gate is a ratchet rather than the raw typecheck, modelled on check-ts-nocheck-ratchet.mjs: 126 files still fail, so the gate freezes that set and fails when a file that checks today stops checking, or when a baseline entry starts checking and was not pruned. The list may only shrink. Why not zero: 180 of the remaining 510 errors are one seam — tests locate mocked react-native components by string name, which `ElementType` does not admit — and closing it means either 180 casts or a global JSX declaration for the mocked names. That is a design decision, not a mechanical fix, so it is left for a follow-up rather than made here. The rest are smaller clusters of the same kind: vi.fn mocks assigned into typed slots, call-arg tuple indexing, and createElement props fixtures. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile-recorder): correct the corpus counts and the salvage claim The oracle section still quoted the corpus as 368 scenarios and 727 goldens; it is 393 and 778, and the three replay suites report 781 tests. Each number now names the command that measures it. "No golden carries one" was the load-bearing error: 44 goldens carry a recorded `reply-salvage` today, starting with the push-test unknown-reason scenario #21176 added for exactly that purpose. The paragraph claimed the observation pins an absence when on those families it pins a recorded drop. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin the tests-typecheck ratchet's parser The gate reads tsc's output, and tsc indents the "Overload 1 of 2, ..." detail under an error. Counting those as filenames would write unparseable entries into the baseline and leave the gate unprunable, so the parser is pinned on that shape as well as on the added/stale diff. Written against the gate itself: it flagged this file before the directive it carried was removed, which is the end-to-end proof the spawn half works. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): await the timer advances the act() rewrite dropped Rewriting `await act(async () => vi.advanceTimersByTimeAsync(n))` into a braced body left the returned promise floating at 27 sites, so the advance was no longer ordered before the assertions that follow it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): unshadow MobileHostCard's .tsx suite A wildcard `include` keeps only the higher-priority extension, so MobileHostCard.test.tsx sat outside every tsc program while MobileHostCard.test.ts existed beside it. Its one error is the same react-test-renderer seam its sibling is baselined for. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): census every test file into the typecheck program The ratchet diffs only files that error, so a test excluded from tsconfig.test.json or shadowed by a sibling extension left the gate silently. Every *.test.ts(x) on disk must now be in the program or named in TESTS_OUTSIDE_PROGRAM with its reason. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(shared): make the enum helpers refuse the ways they can prove nothing openEnum takes a `const` T so a bare literal keeps its arms rather than widening to string. hostUnionArms blocks inference of U with NoInfer and defaults it to never, so a call that omits the host union — where the record would only pin itself — no longer compiles. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): describe the census and correct the baseline count Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): give the push fixture cast its SAFETY rationale Widening the pre-existing cast made the changed-code gate attribute it as a new finding. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): build the push fixtures as typed notifications Replaces the `as unknown as` cast with Expo's own types, filling FirebaseRemoteMessage and its notification once in two builders, and passes the data payload in rather than mutating through an optional member. Typing the fixture showed one assertion comparing the scheduled content against the whole arriving content, which only held while the cast let the fixture omit the two members the presenter drops; it now names the four members the presenter forwards. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): keep the grouped-question advance read non-optional `advance?.kind` let an absent advance take the null-draft branch instead of failing. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): run the tests-typecheck ratchet on Windows Spawns tsc's JS entry on this Node instead of the node_modules/.bin shim, which is a POSIX shell script that Windows resolves to tsc.CMD and then appends .exe to. Parsed paths are normalised to POSIX so a Windows run does not read every baseline entry as both stale and added. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): close the ratchet's @ts-nocheck hole and read tsc once tsc exits 0 on a @ts-nocheck file, so a baselined test could be "fixed" with one line, pruned, and never checked again; the census now names any program test file whose leading comment carries the directive. `--noEmit --listFiles` answers both questions in one pass, so the gate spawns tsc once rather than twice. Corrects the two stale counts, and states hostUnionArms' real reason for living in the schema module now that tests are typechecked. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
184 lines
7.3 KiB
YAML
184 lines
7.3 KiB
YAML
name: Mobile Checks
|
|
|
|
on:
|
|
pull_request:
|
|
types:
|
|
- opened
|
|
- synchronize
|
|
- reopened
|
|
- ready_for_review
|
|
paths:
|
|
- 'mobile/**'
|
|
# Mobile launch contracts exercise the real host dispatcher and durable receipt store.
|
|
- 'src/main/agent-launch/**'
|
|
- 'src/main/runtime/rpc/**'
|
|
- 'src/main/runtime/runtime-rpc/**'
|
|
- 'src/main/runtime/runtime-rpc.ts'
|
|
- 'src/main/runtime/device-registry.ts'
|
|
- 'src/main/runtime/orca-runtime.ts'
|
|
- 'src/main/runtime/agent-session-*.ts'
|
|
- 'src/main/native-chat/agent-session-wire/**'
|
|
- 'src/shared/agent-launch-*.ts'
|
|
- 'src/shared/agent-session-*.ts'
|
|
- 'src/shared/new-workspace/worktree-create-collision.ts'
|
|
# Why: the mobile terminal link parsers are conformance-tested against
|
|
# these shared fixtures; desktop-side fixture edits must re-run this suite.
|
|
- 'src/shared/terminal-file-link-conformance.ts'
|
|
# Why: mobile imports the negotiated capability names directly and records
|
|
# the whole capability read verbatim in its goldens, so a capability added
|
|
# desktop-side rewrites a mobile fixture and must re-run this suite.
|
|
- 'src/shared/protocol-version.ts'
|
|
# Why: mobile's rpc-params-contract.ts is a type-only re-export of the
|
|
# generated params catalog, and mobile/tsconfig.json includes **/*.ts. A
|
|
# schema edit anywhere under here changes mobile's types, so a desktop-only
|
|
# change can break mobile's typecheck with no other mobile signal.
|
|
- 'src/shared/rpc-contract/**'
|
|
# Why: this job holds the only checks that load the Fastfile, so edits to
|
|
# it or to the release workflow it guards must re-run them.
|
|
- '.github/workflows/mobile.yml'
|
|
- '.github/actions/install-node-dependencies/**'
|
|
- '.github/workflows/mobile-ios-release.yml'
|
|
# Why main too: a behaviour-change branch legitimately pins its own last fenced commit, and that
|
|
# commit only stops being reachable when the branch squash-merges. The pull_request run cannot
|
|
# see that; this one is where the pin guard finds it.
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- 'mobile/**'
|
|
- '.github/workflows/mobile.yml'
|
|
|
|
concurrency:
|
|
# Per commit on main, not per branch. GitHub cancels any PENDING run in a group when a new one
|
|
# queues, whatever `cancel-in-progress` says, so one shared main group drops the middle merge of
|
|
# three -- and a pin that breaks there is exactly what this workflow now checks for.
|
|
group: mobile-${{ github.event.pull_request.number || github.sha }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
verify:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
|
|
env:
|
|
# Why: an unfrozen bundler silently re-resolves when Gemfile.lock drifts
|
|
# from the Gemfile, which is how the release jobs could land on different
|
|
# fastlane versions in the first place. Fail here instead.
|
|
BUNDLE_FROZEN: 'true'
|
|
|
|
defaults:
|
|
run:
|
|
working-directory: mobile
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
cache-dependency-path: |
|
|
pnpm-lock.yaml
|
|
mobile/pnpm-lock.yaml
|
|
|
|
# bundler-cache installs mobile/Gemfile.lock, so this job is also what
|
|
# proves the pinned fastlane the release workflow depends on still
|
|
# resolves — before a release run finds out.
|
|
- name: Setup Ruby and fastlane
|
|
uses: ruby/setup-ruby@v1
|
|
with:
|
|
ruby-version: '3.3'
|
|
bundler-cache: true
|
|
working-directory: mobile
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Typecheck
|
|
run: pnpm typecheck
|
|
|
|
# Why a ratchet and not the raw typecheck: mobile/tsconfig.json excludes test files, so until
|
|
# tsconfig.test.json existed nothing checked them, and at introduction 127 of the 632 had
|
|
# drifted. This fails when a test file that checks today stops checking, when a test leaves
|
|
# the program, and on @ts-nocheck; the baseline may only shrink.
|
|
- name: Typecheck tests (ratchet)
|
|
run: pnpm run check:tests-typecheck
|
|
|
|
- name: Test
|
|
run: pnpm test
|
|
|
|
- name: Test iOS release version resolution
|
|
run: ruby fastlane/ios_release_version_test.rb
|
|
|
|
- name: Test TestFlight lane arguments
|
|
run: ruby fastlane/fastfile_testflight_arguments_test.rb
|
|
|
|
# Why: nothing else in CI loads the Fastfile, so a syntax error, a broken
|
|
# require, or an undefined constant only surfaces mid-release — the
|
|
# ios-distribute job failed every run for six days that way. `lanes` just
|
|
# loads and lists, so it needs no App Store Connect credentials and makes
|
|
# no network calls to Apple.
|
|
- name: Smoke-check the Fastfile
|
|
env:
|
|
FASTLANE_SKIP_UPDATE_CHECK: '1'
|
|
FASTLANE_OPT_OUT_USAGE: '1'
|
|
run: bundle exec fastlane lanes
|
|
|
|
- name: Lint
|
|
run: pnpm lint
|
|
|
|
- name: Check formatting
|
|
run: pnpm format:check
|
|
|
|
recording-pin:
|
|
name: RPC recording pin
|
|
runs-on: ubuntu-latest
|
|
|
|
defaults:
|
|
run:
|
|
working-directory: mobile
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
# The ancestry verdict is read straight off history. On a shallow checkout
|
|
# `git merge-base --is-ancestor` answers from grafted parents, so the guard refuses to
|
|
# answer at all rather than reporting a pass it has no evidence for -- and the pinned tree
|
|
# below has to be checkable out.
|
|
fetch-depth: 0
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
cache-dependency-path: |
|
|
pnpm-lock.yaml
|
|
mobile/pnpm-lock.yaml
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# Seconds. No `--ref`, so the pin is judged against the same tree it was read out of. On a
|
|
# pull request that is the merge preview, which already carries main's repins; judging the
|
|
# branch head instead fails every branch cut before the day's repin, and its instruction would
|
|
# tell the author to pin their own head -- creating the break this guard exists to catch. A
|
|
# branch that pins its own commit passes here and fails on the push after the squash, which is
|
|
# where the pin actually leaves the history.
|
|
- name: Check the recording pin is reachable
|
|
shell: bash
|
|
run: pnpm exec tsx scripts/rpc-recording-pin-guard.mts ancestry
|
|
|
|
# ~2 min locally for the record itself, so it is gated rather than run twice over. A pull
|
|
# request that moves none of the corpus, the manifest or the recorder cannot move this
|
|
# verdict away from the one the base commit already published, and `verify` replays the
|
|
# corpus against the branch tree in the meantime. A push to main has no `verify` job and is
|
|
# where a squash lands a spliced corpus, so there it always runs.
|
|
- name: Reproduce the corpus from the pinned tree
|
|
shell: bash
|
|
env:
|
|
PIN_GUARD_BASE: ${{ github.event.pull_request.base.sha }}
|
|
run: |
|
|
if [ -n "$PIN_GUARD_BASE" ]; then
|
|
pnpm exec tsx scripts/rpc-recording-pin-guard.mts reproduce --if-changed-since "$PIN_GUARD_BASE"
|
|
else
|
|
pnpm exec tsx scripts/rpc-recording-pin-guard.mts reproduce
|
|
fi
|