fix(replay): escape recorded interpolation, run scripts off the event loop, harden Maestro and test flows

Merged
TH
5h ago
+1687-535

Summary

Fixes 12 audit findings in replay, Maestro and the test runner. REPLAY-8 is dropped: upstream Maestro's repeat while is also unbounded, and the real gap is a missing default test timeout (REPLAY-19).

  • Placeholder escaping: literal ${…} text in a recorded script is escaped when the script is written. The only placeholder left live is the one in the fill that --record-as parameterized, tracked per action.
  • runScript: runs in a child process (internal/run-script-child) that the daemon can cancel and time out. The 8 MiB HTTP response cap and the 30 s timeout now apply to both paths.
  • Input limits: eraseText is capped at 10000 characters. Maestro -e keys and AD_VAR_* accept mixed case and dots, and __proto__ is rejected. The key grammar is chosen by the input's resolved format.
  • iOS launch arguments: map-form keys get a leading -, matching Maestro. Android is unchanged.
  • replay export: handles wait stable, rejects @ref and malformed waits, and keeps multi-token text and selectors whole.
  • Refs and classification: divergence refs stay within the published scope. Test-infrastructure classification uses typed details only.
  • Companion process lock: companion ensure and stop run under a process lock.
  • Cleanup: the unused actionSourcePaths is removed, and plan digests are unchanged.

About 60 files touched.

Validation

  • Tested df9d76d with check:affected (exactCommit=true):
    • Format, lint, typecheck, layering, Fallow, build and package passed.
    • integration-node fails 2 smoke-daemon-clean tests, which fail the same way on base 093341a in this container. Checks after it did not run locally.
  • The installed-package test runs the packed run-script-child. Focused coverage on the runScript files is 90–98%.
  • CI must confirm unit, Coverage, maestro-conformance and provider-integration.
  • Risks:
    • The iOS launch-argument change needs a device check.
    • replay export keeps its own copy of the wait grammar, so it can drift from command-registry. It can't import that parser without breaking the eager-closure budget.

šŸ¤– Generated with Claude Code

https://claude.ai/code/session_016qAu53rMak1bXMuR5U2gF4