7 Commits

Author SHA1 Message Date
Michał Pierzchała 4af1307024 test: cap Vitest workers for parallel worktrees (#1710)
* test: cap vitest workers for parallel worktrees

* perf: leave Vitest workers uncapped in CI
2026-08-10 15:19:54 +02:00
Michał Pierzchała ac9e4d0f04 test: measure oracle liveness suite-wide; pin the one dead-path oracle (#1679)
* test: measure oracle liveness suite-wide; pin the one dead-path oracle

- docs/agents/oracle-negation-spike.md: assertion-negation sweep over 677
  test files (5,687 verdicts). Zero vacuous tests: all 150 negation
  survivors decompose into assert.rejects-validator artifacts (112),
  helper-oracles (31), in-file-fake breakage (6), and one conditional
  oracle. Records the companion mock-coupled coverage-uniqueness numbers
  and the follow-ups they motivate (diff-scoped mutation gate, provider
  seam closures, transcript provenance).
- watchos-sentinel: the non-watchOS test's only assertion sat in a catch
  block that never fires (tvOS interactor creation succeeds), so no
  assertion executed on the observed path. Pin creation success instead.
  Red-run proof: the old shape survived the negation sweep; the new shape
  fails under it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(android): inject fake adb through the provider scope, not PATH stubs

Adds withFakeAdb to test-utils: a scripted in-process AndroidAdbProvider
installed through the production withAndroidAdbProvider seam — the same
scope the daemon installs per request — replacing PATH-stub shell scripts
that spawn a real subprocess per adb call. No PATH mutation, no spawns,
no real subprocess waits.

Converts settings.test.ts (15 tests, 23ms; waiver said "waits real
settings-apply poll time") and notifications.test.ts (2 tests, 9ms).
Assertions move from args-log regex greps to structural checks on the
recorded call list; the fake receives device-scoped args with the
-s serial pair stripped, so serial routing is enforced by the scoped
provider matching device.id instead of asserted per call.

Remaining PATH-stub files convert next; their contention-retry waiver
entries lift together with the conversions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(android): convert device-input-state to fake adb provider injection

10 PATH-stub cases move to withFakeAdb through the production provider
scope; the 2 tests that already inject an executor directly are
unchanged. Cross-invocation shell STATE_FILE state becomes a closure
boolean; args-log regex asserts become structural checks on recorded
calls. 12/12 green at 386ms — the residue is dismissAndroidKeyboard's
two fixed 120ms retry sleeps, not stub subprocess waits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(android): convert app-lifecycle-install adb stubbing to fake provider

The adb half of every case moves to withFakeAdb through the production
provider scope; installs take the documented exec-shaped fallback
(exec(['install','-r',...])), matching what the PATH stub saw minus the
serial pair. bundletool/zip/unzip stay real or PATH-stubbed — they run
via runCmd outside the adb seam, so this file remains in the serialized
subprocess-stub lane with its waiver reason to be corrected from adb to
bundletool. 13/13 green at ~130ms; no case enters a retry/poll loop.

Conversion note: manifest identity's `unzip -p` failure is silently
swallowed (readZipEntry catch -> undefined, aapt fallback) — an
invisible degradation path worth a future explicit diagnostic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(android): convert input-actions adb stubbing to fake provider

9 PATH-stub cases move to withFakeAdb; the 3 tests already injecting
providers directly are unchanged. Chunked shell-input assertions become
ordered deepEqual on the recorded calls; never-called negatives and
call-count checks preserved 1:1. 12/12 green.

File time drops to 2.2s, all of it production sleeps:
verifyAndroidFilledText unconditionally waits its [0,150,350]ms
verification cadence even when the first inspection matches, so each
fill verification pass costs ~500ms with an instant fake. A
budget-derived cadence there (testing.md pattern 1) would put this file
near 25ms; flagged as follow-up rather than changed here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(android): extract shared oracles; make fake adb failure-faithful

Three test-utils extractions applied across the six converted files:

- assertRejectsAppError collapses the hand-rolled AppError code+message
  rejection validator (10 sites here; ~30 more repo-wide can adopt it
  incrementally). Validators asserting details or multiple differently-
  flagged regexes stay explicit on purpose.
- withFakeAdb gains a `provider` option for extra capabilities
  (snapshotHelperArtifact, reverse, ...), replacing input-actions'
  nested re-scoping bridge.
- withFakeAdb now mirrors the local executor's contract: a scripted
  nonzero exit throws androidAdbResultError unless the call site passed
  allowFailure. Provider-scoped exec bypasses exec.ts's throw-on-close-
  failure, so returning {exitCode:1} took a different production path
  than the PATH-stub `exit 1` these fakes replaced. All 75 tests hold
  under the corrected semantics.

Also swaps settings' inline emulator DeviceInfo literals for the shared
ANDROID_EMULATOR fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test: lift five converted Android files from the contention-retry waiver

settings, notifications, device-input-state, input-actions, and
app-lifecycle-open no longer stub binaries on PATH or spawn
subprocesses, so their contention mechanism is gone: they leave
CONTENTION_RETRY_FILES and, through the derived SUBPROCESS_STUB_TESTS
constant, the serialized subprocess-stub project (17 -> 12 files).
app-lifecycle-install stays with its reason corrected: adb is now
in-process, but bundletool stays PATH-stubbed and zip/unzip spawn for
.aab packaging paths.

Full unit suite green at the new membership: 638 files, 5,724 tests,
with the five files running at unit-core's default parallelism.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test: apply review findings to the fake-adb conversion batch

- app-lifecycle-open: the missing-package launch failure returns
  {stderr, exitCode: 1} and lets withFakeAdb's throw path produce the
  production-shaped androidAdbResultError instead of hand-modeling the
  thrown AppError — the drift the helper exists to eliminate.
- withScriptedAdb deleted: the six converted files were its only
  callers, and a live PATH-stub export invites new tests back into the
  serialized lane this batch shrank. withMockedAdb stays (dispatch and
  runtime-hints tests still stub other binaries).
- android-snapshot-helper gains androidSnapshotHelperScriptResponse so
  the version-probe detection and versionCode reply have one source of
  truth; input-actions' local copy delegates to it.
- withFakeAdb's provider option becomes a distributed Omit over the
  AndroidAdbProvider union, so touch without gestureViewport is a
  compile error at the fake's boundary (planted and verified) instead
  of a TypeError inside production gesture planning.
- spike-doc re-run checklist restores wider than the codemod globs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test: drop the consumer-less FakeAdbScript barrel re-export

Fallow's dead-code gate flagged it: scripts are always passed as inline
lambdas, so only FakeAdbResponse needs a name at the barrel. The type
stays exported from fake-adb.ts where the withFakeAdb signature uses it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(apple): inject fake xcrun through the tool-provider scope, not PATH stubs

withFakeAppleTool mirrors withFakeAdb for the Apple seam: a scripted
provider installed via the production withAppleToolProvider scope, flat
simctl/devicectl invocations recorded exactly as the PATH-stub shell
scripts saw them, throw-on-nonzero fidelity matching exec.ts unless the
call site passed allowFailure, and the canned `simctl privacy help`
listing served by default (the block withMockedXcrun injected into
every script). screenshot-status-bar.test.ts converts as the exemplar:
3/3 green at 9ms with deepEqual call-sequence assertions replacing the
args-log regexes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test(apple): convert apps.test.ts xcrun stubbing to fake tool provider

All withMockedXcrun scripts and hand-rolled PATH stubs move to
withFakeAppleTool; args-log regexes become structural call assertions
(exact deepEqual where order is deterministic, presence checks where
the 5s simulatorBootedMemo TTL makes boot-probe order test-dependent).
12 hand-rolled AppError validators collapse into assertRejectsAppError.
54/54 green; file test time 1172ms -> ~400ms with no test over 201ms.

Five .ipa install tests keep a minimal PATH stub for unzip only:
install-artifact.ts:112 and install-source.ts:438 call runCmd('unzip')
directly, outside the Apple tool provider seam — the file therefore
stays in the serialized subprocess-stub lane with its waiver reason
corrected from xcrun to unzip.

Also observed: getSimctlPrivacyServices caches per PATH+simulatorSetPath
and simulatorBootedMemo keys on deviceId|setPath, so neither cache
accounts for the provider scope — worked around per test, follow-up
worthy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

* test: lift six Apple waivers; fix format and fallow findings from CI

- interactions, simulator, screenshot, physical-device-screenshot,
  devicectl, and screenshot-status-bar leave CONTENTION_RETRY_FILES:
  the first five stopped stubbing PATH binaries in earlier refactors
  (measured 3-64ms per file, no subprocess activity), and
  screenshot-status-bar now injects through the fake tool provider.
  apps.test.ts stays with its reason corrected to the unzip PATH stub
  (xcrun is in-process; install-artifact.ts:112 / install-source.ts:438
  call runCmd('unzip') outside the Apple seam). Serialized lane 12 -> 6.
- oxfmt: fake-apple-tool.ts and contention-retry.ts were pushed
  unformatted (local check piped through tail masked the failure).
- fallow complexity: the three fake-script arrows in apps.test.ts drop
  under threshold via shared predicates (isSimctlMainScreenScale,
  isSimctlScreenshot, isDevicectlDevice), which also deduplicate the
  screenshot pair.

Full unit suite green at the new membership: 638 files, 5,724 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019S5sZnmPn4A9Ct7sTJdfAf

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-08 09:01:05 +02:00
Michał Pierzchała 80feff42d6 build: verify the published tarball instead of grepping the bundle (#1578)
* build: verify the published tarball instead of grepping the bundle

Replaces the bundle-dependency grep with one gate that packs the tarball npm
would publish and proves it sound from a clean consumer install: publint and
attw on the tarball, a two-way dependency-closure audit, an import of every
`exports` subpath, and the CLI smoke run — all from outside the workspace,
where no pnpm link can mask an unresolvable specifier.

Also stops the build from emitting a publishable bundle in the first place: a
missing workspace link now fails `pnpm build` instead of warning and exiting 0,
which is how 0.20.4 shipped an unresolvable `@agent-device/ad-script` import.

publint found 12 real defects in the current package — every `exports` entry
listed `types` after `import`, so TypeScript resolved declarations by accident
rather than by condition. The dependency audit found `pngjs` declared as a
runtime dependency while tsdown inlines it, an install every user paid for and
no shipped code reached; it moves to devDependencies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NamFJUgn9DGHrT2za11JbD

* fix(ci): run the package gate without pnpm on the Node floor

pnpm 11.17 requires Node >= 22.13, so `pnpm check:package` could not start on
the 22.12 floor the Packaged CLI job exists to cover. The gate needs only `node`
and `npm`, so the job invokes the script directly.

Splits the dependency-closure audit into a collector and a message builder to
clear Fallow's complexity threshold, and classifies both packaging linters in
ignoreDependencies: they are subprocess CLIs with no importable API here, which
dependency analysis cannot follow to an import.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NamFJUgn9DGHrT2za11JbD

* fix(publishing): read every literal resolution form in the closure audit

The dependency-closure audit derived shipped imports from the ESM module
record alone, so it could not see a package resolved through `require` or a
`createRequire` result: neither produces a module-record entry. A lazy
`createRequire('@agent-device/…')` would therefore clear the audit, the
all-export probe and the exercised CLI paths, reintroducing the 0.20.4
published-install failure class for another command.

Measuring the built bundle turned up a second, larger hole in the same
reader. The shipped files are minified, and the minifier rewrites every
string literal to a no-substitution template literal, so the dynamic-import
extraction — which accepted quoted strings only — matched 0 of the 99
dynamic imports the bundle contains. The lazy `import()` path that broke
0.20.4 was reported as covered while checking nothing.

Specifiers now come from the module record plus an AST walk over every
literal runtime-resolution form: `import()`, `require()`,
`require.resolve()`, an immediately-invoked `createRequire(...)`, and calls
through a `createRequire` result under any import or minified alias. Both
spellings of a string literal count everywhere, and `.cjs` joins the
scanned extensions.

Computed specifiers stay explicitly out of scope, and are pinned as such.
Rejecting them is not available: minifiers reuse short identifiers across
scopes, and the packed bundle really does contain an unrelated
`a(h[t],f,g,l,e,m)` that no name-based match can distinguish from a require
call. Those are covered by the gate's runtime half instead, which resolves
them for real. Bare-identifier calls need the one-string-argument shape for
the same reason.

The audit moves to scripts/lib/shipped-imports.ts so fixture packages can
exercise it. The gate needs a real `npm pack` behind minutes of Swift and
Android builds, so every check that runs it can only watch a healthy
package pass — which is how a reader that matched nothing looked covered.
The new fixtures assert the failure direction per resolution form: 16 of
the 22 fail against the previous reader, and the 6 that pass are the
quoted-spelling and pinned-limitation cases. A wiring assertion keeps the
audit and both runtime probes attached to the gate, since fixtures alone
would stay green if the call were deleted.

Verified against the real built bundle: the closure resolves to exactly the
two declared dependencies, so the stricter reader adds no false positives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NamFJUgn9DGHrT2za11JbD

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-04 12:28:05 +02:00
devin-ai-integration[bot] 885c1486bb test(ci): single-retry policy for enumerated contention-flaky files (timeouts only) (#1448)
* test(ci): single-retry policy for enumerated contention-flaky files

* fix: satisfy fallow

* test(ci): read failures through a lane reporter so timeouts stay distinguishable

* test(ci): cover the lane reporter and drop its duplicated boilerplate

* chore(fallow): own the retry lane's tool-loaded export seams

* test(ci): block retries on non-test failures and classify timeouts structurally

* test(ci): decide retry eligibility from runner metadata and route gate verdicts through blockers

* refactor(ci): name the retry policy's rules in code instead of comments

* test(ci): mark runner-aborted timeouts inside the runner instead of inferring them

* test(ci): make timeout provenance a per-run secret, not a writable flag

Cover direct task.meta mutation in the real child-Vitest fixture gate.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(ci): retry the failed files in the first run's project and coverage modes

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: drop deleted repo-health file from the retry list after #1480

Rebase onto main post-#1480: the SkillGym/repo-health descope deleted
scripts/repo-health/run.test.ts, whose CONTENTION_RETRY_FILES entry
would now fail this PR's own missing-file check, and inlined the
slow-test budgets into the reporter, resolving the budgets-module
import. Envelope comments now point at scripts/lib/lane-envelope.ts
instead of the closed #1430.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-29 12:53:33 +02:00
devin-ai-integration[bot] 6544e9a0c5 obs: repo-health snapshot command aggregating existing analyzers into one JSON (#1471)
* obs: add repo-health snapshot command aggregating existing analyzers into one JSON

Adds `pnpm repo-health [--json]`, an offline command that aggregates the
signals this repo already computes into one deterministic JSON snapshot by
reusing each analyzer (depgraph, layering ratchets, coverage json-summary,
size-report, fallow baselines, slow-test ratchet, bench/skillgym registries)
rather than reimplementing any metric.

Carries mandatory v1 provenance (schemaVersion, commit, per-analyzer content
hashes, input provenance) so #1424 can persist history. Component metrics
(instability/abstractness/main-sequence distance) are observatory-only. The
only gating behaviour is the depgraph-vs-layering R6 consistency assertion,
already wired into the Layering Guard job via scripts/depgraph/model.test.ts.

Closes #1423

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* repo-health: hash read artifacts and flag staleness in provenance

Coverage and size are artifacts repo-health reads but does not produce, so
they carry no producing-commit stamp. Previously provenance recorded only
their paths and the current HEAD, so a snapshot taken after a source edit
without rerunning those producers would pair prior metrics with the new SHA —
#1424 would persist a false commit-indexed history entry.

Now each read artifact is bound via artifactProvenance() to its content hash
(so history keys on the bytes the metrics came from, not a commit they may
predate) and an explicit `stale` flag (true when a production source file is
newer than the artifact). The coverage/size analyzer-config hashes are added
to provenance.tool, and the human summary marks stale artifacts. Adds a pure
stale-artifact regression test.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* repo-health: bind artifact freshness to the whole producer input set

The stale check derived freshness only from listSourceFiles() (src/*.ts) and
applied it to both coverage and size. Coverage also depends on tests and vitest
config; size-report.mjs reads package.json and runs npm pack — so a change to a
non-src producer input could leave an old artifact marked stale:false while
provenance recorded the current HEAD, a commit the metrics predate.

Each read artifact now declares its full producer input set (PRODUCER_INPUTS)
and is flagged stale when ANY tracked input in that set is newer than the
artifact (via git ls-files mtimes). Freshness that cannot be proven — an empty
observable input set — is reported stale, never falsely fresh. The input set is
recorded as provenance.inputs.*.producerInputs. Adds isArtifactStale() with a
regression covering a non-src input newer than the artifact and the unprovable
case.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* repo-health: prove artifact freshness from a stamped producer commit; route git through runCmdSync

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-28 20:07:45 +02:00
devin-ai-integration[bot] 8cce0ef6b8 test: ratchet mutation score over enumerated decision kernels (#1441)
* test: ratchet mutation score over enumerated decision kernels

Adds a Stryker (vitest runner) mutation lane scoped to the decision kernels,
a per-module baseline with tool/config provenance, and a ratchet that only
lets scores rise. Non-gating until two consecutive stable weekly sweeps.

Refs #1415

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore: declare the mutation test-scope seam for production-export analysis

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: own kernel tests in the mutation registry and ship the #1430 lane envelope

- restore bench:help-conformance, broken by a formatting-path edit
- kernel test files select their module on PRs (registry `tests` + workflow paths),
  asserted to reach the kernel through the import graph
- every mutation run writes the standard scheduled-lane artifact envelope
- move src/utils/__tests__/errors.test.ts beside its source per the mirror rule

Refs #1415, #1430

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: derive kernel test ownership and land the scheduled-lane health monitor

Ownership of a kernel's tests is now computed from the static import graph
(scripts/mutation/ownership.ts) instead of a hand-listed set, so a test that
reaches a kernel indirectly -- src/__tests__/daemon-error.test.ts through
src/daemon.ts -- selects that kernel on a PR. The PR lane triggers on every src
test and shards the derived modules, keeping wall clock at one module.

The lane envelope (#1430) is now written on every exit path with the stage it
reached, so a crash before any mutant runs is distinguishable from a lane that
never ran. Adds the derived cadence monitor (scripts/lane-health, daily
workflow): scheduled lanes are enumerated from .github/workflows/ and reported
dark, failing, or never-run against their own cron cadence.

* fix: merge only Stryker reports from a shard directory

The shard artifacts now carry the lane envelope beside mutation.json, and the
merge globbed every .json under the download path, so the ratchet job fed the
envelope to the report parser and died after the mutants had already run.

* fix(mutation): fail on incomplete shard sets and envelope pre-run failures

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mutation): downgrade a passing envelope when a later lane step fails

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(mutation): shard by registry, defer the PR lane, drop the bundled watcher

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* docs: describe registry sharding and the deferred PR mutation lane

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mutation): make the pre-graduation tooling exception select real mutants

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(mutation): give the worktree fixture commits their own identity

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-28 10:07:14 +02:00
Michał Pierzchała d237bc555d chore: remove verified dead code and migration scaffolding (~700 LOC) (#1367)
* chore: remove verified dead code and migration scaffolding

Multi-agent audit of accumulated waste, every finding adversarially
verified against call sites, git history, and the published surface
before removal. Net -710 lines.

- delete src/core/platform-descriptor/ (superseded ADR-0009 migration
  scaffold; parity tests now assert an inline table)
- remove test-only seams: registry introspection exports,
  CommandFacet.extraDaemonWriters, MaestroEngineOptions.timing
- remove dead flexibility: backend capability allow-list,
  screenshot-diff maxRegions, CloudWebDriverSupportLevel 'partial',
  clearFirst on the TS+Swift runner wire contract
- remove dead deprecated surface: --session-locked /
  --session-lock-conflicts aliases (hard migration error now points at
  --session-lock), replay export --format single-value enum,
  unused Lease*Payload contract types, runtime-layer rotate duplicate
- collapse pass-throughs/duplication: withRetry adapter,
  default-cloud-artifact-provider, connect-profile client-id hashing
  (3x sha256 impls -> one helper, byte-identical output), shared
  scripts walker, cloneValue -> structuredClone, fill-diagnostics
  moved into android/

BREAKING CHANGE: --session-locked and --session-lock-conflicts now fail
with a migration error pointing at --session-lock; replay export
--format is removed (Maestro was the only value); Lease*Payload types
are dropped from the ./contracts subpath.

* chore: satisfy fallow gates tightened by #1363/#1364 after rebase

- drop the consumer-less AndroidFillVerificationNode re-export
- reuse requireSnapshotSession in resolveSnapshotForRef instead of
  inlining the same authorized-frame resolution (fallow clone group);
  the helper's return type now guarantees the session it already
  throws for

* chore: address review — keep cloud-webdriver partial capability metadata

The partial/supported/unsupported levels and their notes are part of the
lease-response capability contract for genuinely limited operations
(Appium page-source snapshots, upload-then-install), not dead
scaffolding. Restore them and the asserting tests unchanged from main.

Also add the missing CHANGELOG entry for the Lease*Payload type removal
from agent-device/contracts.
2026-07-23 07:36:52 +02:00