Skip to content

test(ios-runner): pin production-built runner requests in a golden table TS and Swift both verify - #2900

Merged
thymikee merged 6 commits into
mainfrom
test/2881-runner-request-golden
Sep 24, 2026
Merged

thymikee merged 6 commits into
mainfrom
test/2881-runner-request-golden

Conversation

@thymikee

@thymikee thymikee commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds contracts/fixtures/runner-requests.json, a golden table of production-built Apple runner requests (67 entries, 34 commands). TS producer tests drive every real call site (platform-apple entry points, both screen-recording modules) and assert an exact match; Swift tests decode every entry and require every CommandType case and every Command/SequenceStep/RunnerGesturePlan field to have a producer. Step 1 of #2881: proves a production site can build each request, not that the daemon sends it. Step 2 (Swift unit tests onto the table) is a separate follow-up PR.

Closes #2881 (step 1)

13 files touched. Net production lines: 0 TS, 0 Swift (one CaseIterable conformance on an existing line). Gross diff 894 lines (issue estimate 800-950; under the 1,000-line budget).

Validation

Tested at 08f33d6036.

  • pnpm check:affected --run: green (59 selected checks — format, lint, typecheck, layering, fallow, build, vitest, xctest-selection, packaged-runner-swift, and the rest). A tmpdir-leaks-model failure seen on one run traced to contention from other gates sharing the host (passes 7/7 alone); reran clean with the host idle.
  • pnpm check:xctest-selection: 322 declared methods, 0 unreachable by any lane.
  • Targeted vitest: runner/__tests__/runner-requests, __tests__/runner-requests, interactor-runner-provider, the new src/__tests__/screen-recording-runner-requests, and both root screen-recording tests — 6 files / 27 tests pass.
  • Swift: TEST BUILD SUCCEEDED (build:xcuitest:ios, unit tests included); 5/5 XCTest pass on a throwaway iOS 26.2 simulator (3 golden-table tests + 2 ErrorPayload wire tests).

Remaining risk: macOS host lane and iOS 18.x/tvOS runtimes for the new Swift tests were not run locally; CI-verifiable, not blockers.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.81 MB 4.81 MB +14 B
Package (unpacked) 4.81 MB 4.81 MB +14 B
Package (download) 1.44 MB 1.44 MB +11 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.1 ms 27.4 ms +0.3 ms
CLI --help 79.2 ms 81.0 ms +1.7 ms

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at e1f72f2. The golden table and the matching TS and Swift checks look right, and they follow the existing alert-command-traits pattern.

One question: src/__tests__/test-utils/runner-requests.ts repeats the fixture read and normalize logic from runner-requests-fixture.ts. Can the src tests use that helper, or does the package boundary require the copy?

Small notes, not blocking: the aborted-start assertion uses a bare .rejects.toThrow() that passes for any error (transport test#L576), and the new fixture exclusion in .oxfmtrc.json could use a one-line reason.

All checks pass, and there are no conflicts.

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at e1f72f2. The golden table itself is the right direction. The remaining structural issue is that the guard and the root helper duplicate the fixture contract by hand, so they can drift from the table they are supposed to verify.

);

test('runner request drives send only requests production builds', () => {
const sources = [

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This anti-literal guard scans a hand-maintained source list, but the fixture already carries the real producer paths in entry.producer. It omits the root screen-recording producers, so a raw request literal or direct send in those files would not be checked. Derive the scanned sources from the fixture producers, or otherwise close the list against the producer set the fixture itself declares.

fs.readFileSync(path.join(REPO_ROOT, 'contracts/fixtures/runner-requests.json'), 'utf8'),
) as RunnerRequestEntry[];
const produced = captured
.map(([name, sent]) => ({ name, producer, request: JSON.parse(JSON.stringify(sent)) }))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This root helper copy has already diverged from the package helper: runner-requests-fixture.ts normalizes commandId and statusCommandId through wireRunnerRequest, while this compares the raw sent request. The current root entries happen to omit command IDs, but once a root producer carries one the comparison is against a different shape than the fixture. Use the shared normalization path if the package boundary allows, or move the normalization into a shared fixture utility.

…table

Add contracts/fixtures/runner-requests.json: one entry per production
request site, captured at its real entry point. Three producer tests own
the entries: the platform-apple request sites, the runner-internal sites
(real HTTP bodies from the fake runner), and the two recording modules.
runner-contract.test.ts asserts every RunnerCommand has an entry, names
are sorted and unique, and the drive code builds no request itself.

Refs #2881
Every fixture request decodes as Command and re-encodes with the same key
set, every CommandType case has a request, and every stored field of
Command, SequenceStep and RunnerGesturePlan appears in some request.
CommandType becomes CaseIterable for the case check.

Refs #2881
… tests

Export the fixture reader as @agent-device/platform-apple/runner/requests-fixtures
so root producers pin requests through the same commandId normalization, and derive
the request-literal guard's sources from the producers the fixture declares.

Refs #2881
…lden file

The request-literal guard scans every fixture producer, so root behavior
tests that doubled as producers had to weaken their exact request
assertions. Move the root recording drives into
src/__tests__/screen-recording-runner-requests.test.ts, restore the
original literals, and pin the recordStop sent without an app bundle.

Producers must now be *runner-requests.test.ts files that call
assertProducedRunnerRequests(import.meta.filename, ...), so a comment no
longer satisfies the check. The runner-internal drive moves to
runner/__tests__/runner-requests.test.ts to follow that rule.

Refs #2881
@thymikee
thymikee force-pushed the test/2881-runner-request-golden branch from e1f72f2 to 08f33d6 Compare September 24, 2026 18:09
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the review at e1f72f2. New head 08f33d6.

Reuse point (root helper duplicating fixture read/normalize): src/__tests__/test-utils/runner-requests.ts is deleted. The fixture reader and its wireRunnerRequest commandId/statusCommandId normalization now live in one place, packages/platform-apple/src/runner/runner-requests.fixtures.ts, exported as the test-only subpath @agent-device/platform-apple/runner/requests-fixtures (same pattern as the existing host-kit/selectors -fixtures subpaths). Root producers import assertProducedRunnerRequests from it, so both sides go through the same normalization path.

Inline: guard scans a hand-kept source list: the request-literal guard now derives its scanned sources from readRunnerRequestFixture()'s producer field instead of a hand-maintained list, so it can't silently drop a producer again.

Inline: root helper skipped commandId normalization: closed by the same fixture-module move above — there's no longer a second normalization path to drift.

Follow-up finding while widening the guard: deriving sources from producers pulled the two root behavior tests (which doubled as producers) into the guard, which had weakened three request assertions to expect.anything() to pass it — a real loss of precision the review's shape was steering toward but hadn't flagged yet. Fixed by splitting concerns: a new dedicated drive, src/__tests__/screen-recording-runner-requests.test.ts, does the golden-table producing; the two root behavior tests are restored byte-for-byte to main (full request literals back, including recordStop with no appBundleId, now pinned as its own fixture entry). The producer-set rule was tightened to require both a *runner-requests.test.ts filename and a call to assertProducedRunnerRequests(import.meta.filename, ...), closing the gap where a bare comment satisfied the old check.

Rebase: onto origin/main (fa1082d); no conflicts. Confirmed no CHANGELOG.md edits (already removed on main, #2922; this branch never touched it).

Declined: fencing the new -fixtures subpath to test-only importers — none of the existing -fixtures subpaths have that fence, so a one-off rule for this one isn't warranted (a general fence is a separate layering change). Also declined merging the Swift decode helper with the general fixture-URL helper across 8 unrelated test files — out of scope here.

Gate on 08f33d6: pnpm check:affected --run green (59 checks: format, lint, typecheck, layering, fallow, build, vitest, xctest-selection, packaged-runner-swift, …). check:xctest-selection: 322 methods, 0 unreachable. Targeted vitest: 6 files / 27 tests pass. Swift: test build succeeded, 5/5 XCTest pass on a throwaway iOS 26.2 simulator.

@thymikee

Copy link
Copy Markdown
Member Author

The code at 08f33d6 looks good. The evidence gap from the earlier pass (e1f72f2) is closed: the golden table is now pinned and verified on both the TS and Swift sides.

CI failure is unrelated to this diff. The PR's logical change does not touch .github; the CI config diffs in the stat are rebase content from #2875 and #2896. In job 107767200188, "Restore and build iOS XCTest runner" reached "** TEST BUILD SUCCEEDED **", including the Swift ModelsTests change here compiling cleanly, then failed on the toolchain probe (xcrun --sdk iphonesimulator --show-sdk-version ETIMEDOUT), the known cold-toolchain flake. The "selected but skipped" assertion only follows from that failed probe step.

Re-run Smoke Tests to clear the cold xcrun timeout; no code change is needed here.

I did not re-run vitest, check:affected, or the Swift XCTest suite locally, so the author's gate report is unverified, and I can't confirm the ETIMEDOUT is transient without seeing a re-run.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
@thymikee
thymikee merged commit 6428c54 into main Sep 24, 2026
18 of 19 checks passed
@thymikee
thymikee deleted the test/2881-runner-request-golden branch September 24, 2026 18:42
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-24 18:43 UTC

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.

Pin production-built Apple runner requests in a golden table that TS and Swift both verify

1 participant