Skip to content

fix(ios): drop -Werror from runtime clang builds and cache the fold helper build - #2858

Merged
thymikee merged 4 commits into
mainfrom
fix/2796-runtime-clang-no-werror
Sep 24, 2026
Merged

thymikee merged 4 commits into
mainfrom
fix/2796-runtime-clang-no-werror

Conversation

@thymikee

@thymikee thymikee commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Runtime clang builds no longer use -Werror. A new SDK warning in the AX snapshot bridge or the fold helper can no longer break snapshot or fold on users' machines. Warnings still fail CI: a darwin-only test compiles each helper's exact production argv with -Werror. .github/workflows/ios.yml now runs both tests, and its path filter covers apple/fold-helper and packages/platform-apple/src/foldable.

The fold helper no longer recompiles Fold.m into a temp dir on every call. It is built once per source hash and Xcode toolchain, and cached under ~/.agent-device/fold-helper. The bridge and fold now share one cache implementation (native-build-cache.ts). FOLD_REQUEST_TIMEOUT_MS rises from 210 s to 240 s to cover the new preparation budget; its comment lists each step's budget. Help and docs describe the cache.

Closes #2796. 19 files, 896 insertions and 354 deletions (1,250 gross, over the 1,000-line budget). Most of it is extracting the shared cache from the bridge. The history is split into extract, fix, CI gate, and a simplification commit.

Validation

  • Reviewed head b4ccf4b00e: pnpm check:affected --run green (all 59 checks, including both -Werror compile tests); check:production-exports green.
  • Rebased onto main (CHANGELOG-only conflict). Head 161df5d409: build, typecheck and 157 targeted tests green.
  • Rebased onto main 9fac266b59 (CHANGELOG-only conflict; the PR's own diff is unchanged from c4a71a5a20 apart from hunk offsets). Head 7421ec8322: pnpm install --frozen-lockfile, pnpm build, pnpm format (no changes), pnpm lint, pnpm typecheck and pnpm check:xctest-selection green; 157 targeted unit-core tests over snapshot-source, foldable and both timeout-policy tests green, plus the provider-integration ios-fold scenario.
  • Live: iPhone 17 simulator; two bridge snapshots with no fallback warning and no rebuild (same cache binary mtime).
  • Live fold on iPhone Duo (Xcode 27.1 beta 27A9269), starting from an empty ~/.agent-device/fold-helper: the first call built fold-helper/4542d8be…/fold-helper. Later calls, including calls after daemon restarts, made no clang exec and ran simctl spawn on the cached binary. Hinge poses 0°, 180° and 130° were verified. Head cf4d07353 produced the same cache key and still hit the cache.
  • Head cf4d07353: pnpm check:affected --run green; typecheck, lint and format green. 157 targeted tests green, including both darwin -Werror gates and the ios-fold provider scenario. As a mutation check, an unused variable added to Fold.m failed the gate.
  • Four adversarial review rounds; the final two reviewers both said ship.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.80 MB 4.80 MB +1.4 kB
Package (unpacked) 4.80 MB 4.80 MB +1.4 kB
Package (download) 1.44 MB 1.44 MB +775 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.3 ms 26.1 ms -0.1 ms
CLI --help 77.1 ms 77.1 ms +0.1 ms

@github-actions

github-actions Bot commented Sep 23, 2026 •

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

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at c4a71a5. The code looks sound, but I can't confirm this fixes the issue without a live run, so I'm holding off on approval until that evidence is in.

CI is green on all 20 checks, and the ios.yml path filter now covers apple/fold-helper and foldable, so this PR's own changes exercised both -Werror gates.

Live fold on an iPhone Duo (foldable) simulator is still missing. Run it twice against a clean ~/.agent-device/fold-helper: the first call should build ~/.agent-device/fold-helper//fold-helper, the second call should show no clang in --debug output and a simctl spawn against that cached path, and both calls should report the verified hinge pose. You noted no foldable device type is available on your host, so if that's still true, say so explicitly and we can decide whether to accept the gap or find another way to get the device.

I didn't run the darwin -Werror gates locally and I'm relying on CI for that. I also didn't exercise the live fold route myself; only the fake-provider integration test covers it in this review. And I didn't check that apple/fold-helper/Fold.m ships in the npm package so host.projectRoot() resolves it in an installed build, though the old code used the same findProjectRoot, so that risk predates this PR.

Gross diff is 1,123 lines, over the 1,000-line budget, though net production is 235. Dropping -Werror alone is two argv lines plus two unit assertions, and the cache extraction is the reuse the issue itself needs, since fold has to share the bridge's cache. Is there a smaller shape here, like landing the native-build-cache extraction as its own pure-refactor commit first, then a second commit for the fold-on-cache change plus the -Werror drop?

Not blocking: you could also tighten timeout-policy.test.ts to assert the 220_000 envelope from the shipped timeout constants instead of literals (or just drop it since command-descriptor-timeout-policy.test.ts already pins fold at 240_000), rename or repoint native-build-cache.ts so its lock description isn't fixed to 'iOS Simulator snapshot bridge cache' when fold uses it, and fold the compile argv (not just source and toolchain) into the cache key in fold-helper-cache.ts so an argv-only change can't serve a stale binary — none of these need to hold up this PR.

The next thing standing between this and merge is the live foldable-simulator run showing the cache hit on the second fold call, or your explicit call that the recorded device gap is acceptable to ship without it.

@thymikee
thymikee force-pushed the fix/2796-runtime-clang-no-werror branch from c4a71a5 to 7421ec8 Compare September 24, 2026 07:54
@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 7421ec8. The conflict from the c4a71a5 review is resolved, and I found no code defect in the delta.

One validation gap holds this. Only the fake-provider integration test shows that fold reuses the stored helper build. Please run fold twice on a foldable iOS simulator with an empty ~/.agent-device/fold-helper. The first call should build ~/.agent-device/fold-helper/<key>/fold-helper. The second call, with --debug, should show no clang invocation and a simctl spawn of that binary. Both calls should report the verified hinge pose. If no foldable device type is available on your host, say so and accept the gap explicitly.

Not blocking: the new sum-of-literals test in packages/command-registry/src/__tests__/timeout-policy.test.ts:9 hardcodes numbers instead of importing FOLD_HELPER_PREPARATION_DEADLINE_MS and the other budgets it mirrors, so it can drift without failing.

Smoke Tests was still queued, so there is no CI result to attribute yet.

@thymikee

Copy link
Copy Markdown
Member Author

Rebased onto main 9fac266b59; new head 7421ec8322. The only conflict was CHANGELOG.md (both entries kept, markers removed); the PR's own diff is unchanged from c4a71a5a apart from hunk offsets and blob hashes.

CI on 7421ec8322 is green on every lane, including the four Smoke Tests matrix entries that were still queued when you looked, and the PR now reports MERGEABLE / CLEAN. Run locally before pushing: pnpm install --frozen-lockfile, pnpm build, pnpm format (no changes), pnpm lint, pnpm typecheck, pnpm check:xctest-selection (297 declared AgentDeviceRunnerUITests methods, 0 reachable by no lane; this PR touches no Swift file), 157 targeted unit-core tests over snapshot-source, foldable and both timeout-policy tests, and the provider-integration ios-fold scenario.

On the requested live run, stating the gap explicitly: no foldable device type is available on this host, so the two-call fold run against an empty ~/.agent-device/fold-helper could not be produced here. Verified on this machine just now (Xcode 26.2, build 17C52): xcrun simctl list devicetypes returns 124 types with zero matches for duo/fold/hinge/flip, and xcrun simctl list devices available has no foldable device either. What covers the cached-helper route today is packages/platform-apple/src/foldable/fold-helper-cache.test.ts, simulator-hid.test.ts, and the fake-provider test/integration/provider-scenarios/ios-fold.test.ts.

Not run by me: the live fold route on any simulator, and the darwin -Werror compile gates locally (those are covered by the ios.yml step in CI). The timeout-policy.test.ts:9 sum-of-literals point is untouched by this rebase and still open; it is marked non-blocking.

@thymikee
thymikee force-pushed the fix/2796-runtime-clang-no-werror branch from 7421ec8 to b46ed59 Compare September 24, 2026 09:21
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the review at c4a71a5 (the branch was since rebased to 7421ec8). New head b46ed59.

1. Live Duo run: not possible on this host. Only Xcode 26.2 (build 17C52) is installed. xcrun simctl list devicetypes lists 124 device types and none matches duo, fold, hinge or flip, and xcrun simctl list devices available shows no foldable device either. So I can't do the two-call fold run against an empty ~/.agent-device/fold-helper here. Please either accept the gap or name a host that has a foldable simulator.

2. History split as you suggested, on 9fac266b59:

  • ba26d9af9e refactor(ios): extract the native build cache from the snapshot bridge. Pure refactor: the bridge manifest, cache key and -Werror argv are unchanged, and cache.test.ts and cache-identity.test.ts pass without edits.
  • 4400fb3dc7 fix(ios): drop -Werror from runtime clang builds and cache the fold helper. This covers the fold helper on the shared cache, the 240 s timeout, help/docs, and the notes below.
  • b46ed592da chore(gates): the iOS macOS CI step. It stays a separate commit under the enforcement-commit rule.

Gross diff was 1,482 lines and is now 1,597. The growth is the notes below. The pure extraction can now be reviewed on its own.

3. Non-blocking notes, all taken:

  • The compile argv is now part of both cache keys: snapshotBridgeCacheKey() and foldHelperCacheKey(). It is built from placeholder paths, so the key does not depend on the install path. The bridge key did not cover its argv before this change. A test for each key shows that an argv-only change produces a new key.
  • The lock description is now caller-supplied. The bridge keeps "iOS Simulator snapshot bridge cache"; fold uses "iOS Simulator fold helper cache".
  • I dropped timeout-policy.test.ts. command-registry cannot import platform-apple's step budgets, and command-descriptor-timeout-policy.test.ts already pins fold at 240_000.

4. Gate on b46ed59: pnpm check:affected --run passed, including both darwin -Werror compiles against apple/snapshot-bridge and apple/fold-helper. The first run had one timing flake outside this diff (remote-test-artifacts-materialization.test.ts). That file passed when rerun alone, and a second full run passed. check:xctest-selection, typecheck, lint and format all pass. check:production-exports reports only the 68 existing warn-level findings.

Move the snapshot bridge's content+toolchain-keyed build cache (lock,
atomic publish, manifest matching, source fingerprinting, and the
budgeted xcrun exec) into native-build-cache.ts as generic, reusable
primitives. The bridge's manifest shape, cache key derivation, and
compile argv (including -Werror) are unchanged; this is a pure move.
…elper

Runtime clang builds no longer use -Werror. A new SDK warning in the AX
snapshot bridge or the fold helper can no longer break snapshot or fold
on a user's machine. A darwin-only compile of each helper's exact
production argv with -Werror appended keeps the warning gate in CI
instead.

The fold helper no longer recompiles Fold.m into a temp dir on every
call: it now shares the bridge's content- and toolchain-keyed build
cache (native-build-cache.ts) and is built once per source hash and
Xcode toolchain, cached under ~/.agent-device/fold-helper. Both cache
keys now also fold in the compile argv, so a compiler-flag or
framework-list change that touches neither the source nor the
toolchain cannot serve a stale binary. The cache's process-lock
description is caller-supplied, since the bridge and the fold helper
now share one lock implementation over two different resources.

FOLD_REQUEST_TIMEOUT_MS rises from 210s to 240s to cover the new
preparation budget; its comment lists each step's budget, mirrored in
command-descriptor-timeout-policy.test.ts's pinned envelope. Help and
docs describe the cache.
…I step

ios.yml's clean-installed preparation step now also runs the fold
helper's -Werror conformance test and watches apple/fold-helper and
packages/platform-apple/src/foldable for changes, matching the
existing bridge gate.
@thymikee

Copy link
Copy Markdown
Member Author

Live fold evidence on iPhone Duo, head 40e4fb217d

I ran this on a different host that does have the foldable runtime: Xcode 27.1 beta (27A9269), macOS 27.0 (26A428), arm64. The simulator was iPhone Duo 9542F4F5-51D1-4F5D-B3C9-02D78A69F0D9, booted through simctl. Before the run I did pnpm install --frozen-lockfile && pnpm build && pnpm clean:daemon, and ~/.agent-device/fold-helper did not exist.

Call Daemon clang exec simctl spawn binary Result
1. fold closed --debug fresh yes, 892 ms ~/.agent-device/fold-helper/4542d8be74c0854cef35046ba417e61c/fold-helper hinge 0°, LCD 466x678
2. fold open --debug same none same path hinge 180°, LCD-1 669x951
3. fold half-open same, after clean:daemon and a new open none same path hinge 130°, LCD-1
4. fold closed --debug same, after clean:daemon and a new open none same path hinge 0°, LCD
  • Cache entry: written once. fold-helper and manifest.json both have mtime 11:25:50 and were not touched by calls 2–4.
  • Manifest: the compileArgv has no -Werror, toolchain.xcode is Xcode 27.1\nBuild version 27A9269, and a binarySha256 is recorded.
  • Exec sequence on a cache hit (from the session request log, call 2):
xcrun devicectl device info displays      105 ms
xcodebuild -version                         82 ms
sw_vers -productVersion / -buildVersion     30 ms
uname -m                                     6 ms
xcrun simctl spawn <udid> ~/.agent-device/fold-helper/4542d8be…/fold-helper open   351 ms
xcrun devicectl device motion hinge-angle  5088 ms
xcrun devicectl device info displays       153 ms

Wall time was 7.3 s cold and 5.9 s warm. Most of that is the pre-existing ~5 s CoreDevice hinge read. A cache hit still runs the toolchain probe, about 120 ms warm. That is by design, since it is what notices a DEVELOPER_DIR switch.

This closes the live-evidence gap from the earlier reviews. I closed the session afterwards, and device status shows no claims.

Simplification review

There are no blocking correctness issues. I checked the lock, the atomic publish, the temp dir cleanup on failure, and the sha256 recheck on a cache hit. One small bug and several simplifications follow. Together the simplifications would bring the PR back under the 1,000-line budget.

  1. Error wrapping drops typed details (bug). asFoldHelperCacheError (fold-helper-cache.ts:189) replaces every SnapshotSourceError's details with a fixed {reason, hint, cause}. That loses:

    • the specific native-build-stalled hint and its timeoutMs;
    • the toolchain-probe-failed stderr and command;
    • the native-source-missing filePath.

    Its doc comment also says "every cache failure", but a lock timeout (AppError) and fs errors pass through unwrapped. Suggested fix: one factory used by both throw sites, which also removes the duplicated message and hint:

    function foldHelperBuildFailed(details: Record<string, unknown>, cause?: unknown) {
      return new AppError('COMMAND_FAILED', 'Unable to build the simulator fold helper',
        { hint: FOLD_HELPER_HINT, ...details, reason: 'fold-helper-build-failed' }, cause);
    }
  2. Manifest-field matching repeats the cache key (about 150 lines).

    • Why it's redundant: the entry directory is named by cacheKey, and cacheKey already hashes every field in both MANIFEST_FIELDS lists. So once manifest.cacheKey === cacheKey, comparing each field again only rebuilds the key.
    • Change: have ensureNativeBuildCacheEntry take the key inputs, derive the key itself, write {...keyInputs, cacheKey, binarySha256}, and count a hit only when cacheKey matches and a binarySha256 is present.
    • What this deletes:
      • nativeBuildManifestFieldsMatch, describesReusableEntry, and the manifest/manifestMatches params;
      • both MANIFEST_FIELDS lists, snapshotBridgeCacheKey and foldHelperCacheKey;
      • the "argv change changes the key" tests, which only prove that sha256 of different JSON differs.
    • Behavior: identical short of a 128-bit hash collision.
  3. simulator-hid.test.ts tests the cache a fourth time through exec fakes (about 90 lines). Mock ./fold-helper-cache.ts instead, the same way pose.test.ts mocks simulator-hid. That also removes the test-only options.cacheRoot parameter on sendSimulatorFoldPose. Move the "clang exit 1 → fold-helper-build-failed" case into fold-helper-cache.test.ts; today it only covers the stall.

  4. Assert that the argv has no -Werror directly (about 30 lines). For example, assert.ok(!buildSnapshotBridgeCompileArgv({...}).includes('-Werror')) replaces the exec-recording host in cache.test.ts.

  5. In the darwin gates, append -Werror instead of splicing it in after -Wextra (about 12 lines). [...argv, '-Werror'] does the same job, since clang doesn't care where the flag sits.

  6. Drop execNativeBuildClang's deadlineReason param. Every step around the compile already uses 'native-build-deadline', so fold's separate 'fold-helper-build-deadline' is inconsistent. You could also derive lockDescription from label.

Worth keeping as is:

  • the placeholder-argv key: it's what catches a change to SNAPSHOT_BRIDGE_COMPILE_FILENAMES, which sourceHash doesn't cover;
  • the HostToolchainIdentity split;
  • the run override in createFoldHelperCacheHost;
  • the timeout plumbing;
  • the location of native-build-cache.ts: moving it would mean renaming the SnapshotSource* host, deadline and error types too, which is follow-up material.

Once (1) is fixed, this is fine to merge. (2) to (6) are optional, but they would roughly halve the insertions.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 40e4fb2. The -Werror drop and the fold helper build cache look right, and the shared native build cache keeps the snapshot bridge lock, verify and publish steps unchanged. Your iPhone Duo run on this head answers the earlier evidence question: the first fold builds the helper and later folds reuse it without clang.

Not blocking: asFoldHelperCacheError still drops the stall and probe details, but that behavior comes from main.

Smoke Tests, Repo Guards and Coverage were still running. They exercise the changed snapshot-source and foldable code, so a failure there is likely related to this diff.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
…fold build error details

ensureNativeBuildCacheEntry now takes the build's key inputs, derives
the cache key itself, and returns it. A hit only needs the manifest's
cacheKey and binary hash to match, because the key already hashes every
field the per-caller MANIFEST_FIELDS lists compared again. That removes
nativeBuildManifestFieldsMatch, both field lists, and the
snapshotBridgeCacheKey/foldHelperCacheKey wrappers. Keys are
byte-identical to before, so existing cache entries still hit.

A fold helper cache failure now keeps the underlying failure's hint and
typed details (the native-build-stalled hint and timeoutMs,
toolchain-probe stderr, a missing source's filePath) instead of
replacing them with a fixed hint.

Tests: simulator-hid.test.ts mocks the fold helper cache instead of
retesting it through exec fakes, which drops sendSimulatorFoldPose's
test-only cacheRoot option. The -Werror absence checks assert the argv
directly, and the darwin gates append -Werror instead of splicing it in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@thymikee

thymikee commented Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Applied the review above in cf4d07353, a single commit on top of the existing three.

  • Error details kept (the bug). A fold helper cache failure is still rewrapped as COMMAND_FAILED / fold-helper-build-failed, but it now keeps the underlying hint and typed details: timeoutMs and the native-build-stalled hint, the toolchain probe's stderr, and a missing source's filePath. Both throw sites share one foldHelperBuildFailed factory. fold-helper-cache.test.ts now covers the clang exit-1 path and checks that the stall hint survives.
  • A hit is decided by the cache key alone. ensureNativeBuildCacheEntry({ keyInputs, ... }) derives the key itself and returns { path, cacheKey }. A hit requires manifest.cacheKey === cacheKey and a matching binary sha256.
    • Removed: nativeBuildManifestFieldsMatch, both MANIFEST_FIELDS lists, snapshotBridgeCacheKey and foldHelperCacheKey, and the single-use compileSnapshotBridge wrapper.
    • Keys unchanged: they come out byte-identical, so existing ~/.agent-device/snapshot-source and fold-helper entries still hit. Checked live: the fold key is still 4542d8be…, and no new bridge entry appeared after a daemon restart.
  • Tests:
    • simulator-hid.test.ts mocks ./fold-helper-cache.ts, which drops the test-only cacheRoot option on sendSimulatorFoldPose.
    • The no--Werror checks assert the argv directly.
    • Both darwin gates use [...argv, '-Werror']. As a mutation check, an unused variable added to Fold.m fails the gate.
  • Shared deadline reason. execNativeBuildClang lost its deadlineReason param, so both callers use native-build-deadline.

The diff is now 896+/354− (1,250 gross), down from about 1,600. Checks on cf4d07353:

  • pnpm typecheck, pnpm lint and pnpm format pass.
  • 157 targeted tests pass across foldable, snapshot-source, the timeout policy and the ios-fold provider scenario. That includes both darwin -Werror gates, which really ran on this host.
  • Live on iPhone Duo: a fold helper build on the first call, cache hits after that, and hinge poses 130° and 180° verified. Bridge snapshots also succeeded via simulator-ax-bridge.

pnpm check:affected --run passes ("all runnable checks passed").

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at cf4d073. The delta looks good: a fold helper cache failure now keeps the underlying hint and typed details (such as timeoutMs), and the cache keys stay the same, so existing cache entries still hit. I found no blocking issue.

Not blocking: a fold helper deadline now reports the cause native-build-deadline instead of fold-helper-build-deadline, because deadlineReason was dropped from execNativeBuildClang. That reason code was never released, so this is fine either way.

Smoke Tests was still running at review time. This PR changes the iOS snapshot bridge build cache, which iOS smoke can reach on a cold cache, so a failure there needs a look at its log before we call it unrelated.

@thymikee

Copy link
Copy Markdown
Member Author

Looked at the Smoke Tests failure on cf4d073 (job 107578273158), as the review asked. It does not trace to the bridge build cache.

  • Failing step: the fixture E2E, step 13, wait text Automation lab. It failed with wait_deadline_exceeded after its 10 s budget: the first poll took 8.7 s, then the deadline hit.
  • Where the time went (request log 72de5027b686dfbb): the runner findText took 4.2 s. After that, the snapshot route fell back with target-resolution-failed / simulator-target-discovery-pending, and the capture went to XCTest. The request never reached the AX bridge, so it could not have paid a cold bridge build.
  • The cache itself was fine: this job's own "Verify clean-installed Simulator snapshot bridge preparation and the fold-helper -Werror gate" step passed, and no snapshot-source or native-build-cache failure appears anywhere in the run artifacts.
  • No repeat elsewhere: among today's failed iOS runs, only this one has this signature. It looks like the launch-discovery timing around deep links (fix(ios): never launch a not-running session app from an observation #2852), not this diff.

I reran the failed job once.

The non-blocking note: I'm keeping native-build-deadline. Both helpers now share it, and fold-helper-build-deadline was never released.

@thymikee
thymikee merged commit 7286206 into main Sep 24, 2026
19 of 21 checks passed
@thymikee
thymikee deleted the fix/2796-runtime-clang-no-werror branch September 24, 2026 11:41
thymikee added a commit that referenced this pull request Sep 24, 2026
FOLD_REQUEST_TIMEOUT_MS was a literal whose basis lived only in a
comment, and that comment kept drifting from the route: it first cited
one 30s helper build and four 20s hinge reads, missing two 5s
display-inventory reads and the HID-dispatch guard/grace, then #2858
hand-summed the route again for its own preparation-phase change and
landed a fresh hand-summed figure with the same failure mode.

Add a ledger test that drives fold through the public client and
daemon against a fake Apple tool provider recording each call's
timeoutMs, including the fold-helper cache's toolchain probes and
build. A calibration run learns the route's hinge-settle-attempt count
from an oscillating angle that never settles; a measured run uses the
same oscillation but lands the last allowed read on target, so it
succeeds after making exactly as many reads. Summing every call's
timeout (minus 1ms, plus any kill grace) gives the route's worst case
that still succeeds, asserted against the resolved command envelope
with the usual 30s daemon-result margin. The test imports no
platform-apple step figure other than MAX_FOLD_DURATION_MS, so it
keeps proving the bound however the route's steps change next.

Widen FOLD_REQUEST_TIMEOUT_MS to 255_000 (the smallest 5s multiple at
least ledger + margin) and point both the constant's comment and the
descriptor-timeout-policy pinning test at the ledger test instead of a
hand-summed figure.
thymikee added a commit that referenced this pull request Sep 24, 2026
FOLD_REQUEST_TIMEOUT_MS was a literal whose basis lived only in a
comment, and that comment kept drifting from the route: it first cited
one 30s helper build and four 20s hinge reads, missing two 5s
display-inventory reads and the HID-dispatch guard/grace, then #2858
hand-summed the route again for its own preparation-phase change and
landed a fresh hand-summed figure with the same failure mode.

Add a ledger test that drives fold through the public client and
daemon against a fake Apple tool provider recording each call's
timeoutMs, including the fold-helper cache's toolchain probes and
build. A calibration run learns the route's hinge-settle-attempt count
from an oscillating angle that never settles; a measured run uses the
same oscillation but lands the last allowed read on target, so it
succeeds after making exactly as many reads. Summing every call's
timeout (minus 1ms, plus any kill grace) gives the route's worst case
that still succeeds, asserted against the resolved command envelope
with the usual 30s daemon-result margin. The test imports no
platform-apple step figure other than MAX_FOLD_DURATION_MS, so it
keeps proving the bound however the route's steps change next.

Widen FOLD_REQUEST_TIMEOUT_MS to 255_000 (the smallest 5s multiple at
least ledger + margin) and point both the constant's comment and the
descriptor-timeout-policy pinning test at the ledger test instead of a
hand-summed figure.
thymikee added a commit that referenced this pull request Sep 24, 2026
…2916)

* fix(ios): derive the fold request envelope from a worst-case ledger test

FOLD_REQUEST_TIMEOUT_MS was a literal whose basis lived only in a
comment, and that comment kept drifting from the route: it first cited
one 30s helper build and four 20s hinge reads, missing two 5s
display-inventory reads and the HID-dispatch guard/grace, then #2858
hand-summed the route again for its own preparation-phase change and
landed a fresh hand-summed figure with the same failure mode.

Add a ledger test that drives fold through the public client and
daemon against a fake Apple tool provider recording each call's
timeoutMs, including the fold-helper cache's toolchain probes and
build. A calibration run learns the route's hinge-settle-attempt count
from an oscillating angle that never settles; a measured run uses the
same oscillation but lands the last allowed read on target, so it
succeeds after making exactly as many reads. Summing every call's
timeout (minus 1ms, plus any kill grace) gives the route's worst case
that still succeeds, asserted against the resolved command envelope
with the usual 30s daemon-result margin. The test imports no
platform-apple step figure other than MAX_FOLD_DURATION_MS, so it
keeps proving the bound however the route's steps change next.

Widen FOLD_REQUEST_TIMEOUT_MS to 255_000 (the smallest 5s multiple at
least ledger + margin) and point both the constant's comment and the
descriptor-timeout-policy pinning test at the ledger test instead of a
hand-summed figure.

* fix(ios): keep a daemon-result margin over prepare's default runner budget

readPrepareIosRunnerTimeoutMs's fallback and the client envelope both
resolved to PREPARE_REQUEST_TIMEOUT_MS (240_000), so with no
--timeout the daemon-side runner budget and the client envelope were
identical: a slow cold runner build ended in a client-side timeout and
daemon reset instead of the daemon's own typed runner_phase_budget
result. Every other bounded command keeps a 30s margin between its
daemon-side budget and its envelope; prepare's explicit-timeout case
already widened past its own base by that same margin
(widenToUserBudget), only the default case had none.

Split the daemon-side runner budget out as PREPARE_STARTUP_BUDGET_MS
(240_000, unchanged) and derive PREPARE_REQUEST_TIMEOUT_MS from it
plus the margin (270_000). The handler falls back to the startup
budget, not the now-wider request timeout.

Add a handler-level test that calls handlePrepareCommand directly with
a fake runner binding recording the timeoutMs it receives, then checks
that value plus the margin against the same request's resolved client
envelope for the no-flag, --timeout above default, and --timeout below
default cases - the rule the fix proves, not just the constants it
compiles to.

* fix(ios): fail the fold ledger test on an unbounded provider call

The fold worst-case ledger fake defaulted an unset timeoutMs to 0, so a
provider call reaching it with no bound cost 0 virtual ms instead of
exposing the true unbounded worst case. Its runCommand handler also
skipped `open -a Simulator` before recording, dropping any such call
from the ledger with no error.

Build the ledger on the shared recording provider and assert every
call carries a finite, positive timeoutMs, so an unbounded call fails
the test instead of passing silently.

* test(ios): give the fold ledger test a cold-import budget

The ledger test runs two cold folds through a fresh daemon each. After a
platform-apple source change the first import is untransformed, and the
test ran past vitest's 5 s default, which hid the ledger printout behind a
timeout. Match the provider-scenario precedent of an explicit budget.
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.

fix(ios): runtime clang builds must not use -Werror; cache the fold helper build

1 participant