Skip to content

refactor(contracts): additive capability facts pilot on touch family - #2420

Open
thymikee wants to merge 4 commits into
mainfrom
claude/2409-2412-additive-facts
Open

refactor(contracts): additive capability facts pilot on touch family#2420
thymikee wants to merge 4 commits into
mainfrom
claude/2409-2412-additive-facts

Conversation

@thymikee

@thymikee thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member

What changed

Pilots additive capability facts on the touch operation family (per #2412, child of #2409).

touchRuntimeOperationFacts used to take one required cell per operation, so an operation only
one owner implements still cost a hand-written denial in every other owner. The builder now takes
the family's denial once, as a required unsupported cell, and every per-owner operation
(tapRef, hover, hoverRef, fillRef, tapElementSelector) is optional — an owner names what
it implements and omission reports unsupported verbatim, with the reason and hint the owner
would otherwise have repeated by hand. tap, longPress and fill stay required: every owner
implements them, so dropping them would be a silent regression, not a legitimate omission.

A follow-up commit restored that guard after the first pass made all cells optional and a real
regression slipped through: platform-web's touch facts had silently lost their explicit
longPress: readinessUnavailable cell, reporting the family default instead of the platform's own
readiness reason. Fixed, along with the other now-required call sites (the fully-unavailable owner
fixtures and the screenshot-runtime-fixture test double).

Proof

  • Facts are unchanged for every owner: the per-platform admission tests that assert the
    unavailable reason for each non-supporting platform pass with no edits
    (packages/contracts/src/touch-runtime.test.ts, platform runtime tests).
  • pnpm check:affected --run: 407 test files / 2898 tests passed.
  • pnpm check:layering: green (R2, R6, R7, R9, R10, R11, R13 etc. all hold).

Closes #2412
Part of #2409

Replay: historical single-platform addition (#2412 acceptance)

Measured at 0eca306610 against base e7d97f7dfe; the later commits only re-required
tap/longPress/fill and ported the hover evidence, neither touches the replayed addition.
Full record in the PR comment of 14:37.

Replayed addition: one Apple/iOS-only touch operation (longPressElementSelector, shaped like
tapElementSelector, the way #1987 introduced the family). Files the compiler forces:

shape files packages unsupported-owner fact files
old (main) 16 8 7
new (this branch) 5 2 0

The seven unsupported-owner edits that disappear:

  1. packages/platform-android/src/runtime.ts (denial cell)
  2. packages/platform-harmonyos/src/runtime.ts (denial cell)
  3. packages/platform-linux/src/runtime.ts (denial cell)
  4. packages/platform-web/src/runtime.ts (denial cell)
  5. packages/provider-webdriver/src/platform-runtime.ts (denial cell)
  6. packages/provider-limrun/src/interaction-operations.ts (denial cell)
  7. packages/contracts/src/platform-runtime-unavailable.ts (shared unavailable builder, which platform-vega and Linux non-desktop spread)

Also gone: four test fixtures that enumerated every touch key
(src/__tests__/test-utils/runtime-operation-facts.ts, src/daemon/__tests__/screenshot-runtime-fixture.ts,
src/daemon/handlers/__tests__/install-source.test.ts, test/integration/provider-scenarios/provider-device-runtime.fixtures.ts).
The family builder's own exhaustive test (packages/contracts/src/touch-runtime.test.ts) still changes on both shapes.

Stop rule: fewer than two unsupported-owner edits avoided → stop. Seven avoided; the pilot continues.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.51 MB 4.51 MB -660 B
Package (unpacked) 4.51 MB 4.51 MB -660 B
Package (download) 1.34 MB 1.34 MB -195 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.9 ms 28.5 ms -0.4 ms
CLI --help 79.9 ms 77.7 ms -2.2 ms

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Fixed both failing checks in 2371ba2f72.

Root cause (shared by both checks): the touch family's additive-facts refactor (this branch) dropped every owner's explicit hover: unavailable cell in favor of the family-level unsupported fallback. Four coverage manifests asserted evidence by a literal source-line substring check against packages/platform-apple/src/runtime.ts (macOS, iOS, tvOS rows) and packages/platform-linux/src/runtime.ts (Linux row) — but that hover: unavailable / hover: unsupportedPlatformLeaf line no longer exists in either file, so fs.readFileSync(owner.path).includes(owner.test) failed.

  1. macOS / Smoke Tests — "Run macOS command coverage contract": same root cause, macOS/tvOS/iOS hover rows.
  2. CI / Integration Tests — "Execute integration tests": the same four hover rows (macOS, iOS, tvOS, and also Linux) failing inside the combined test/integration/*.test.ts run — confirmed via the job log (AssertionError: hover owner does not contain named evidence, 4 occurrences, all hover-shaped). Not a separate/unrelated failure.

Fix: rather than re-adding the retired hand-written hover: unavailable production line, added a small real test to each owner's existing unit test file that asserts the typed unavailable denial directly against the bound facts:

  • packages/platform-apple/src/runtime.test.ts: new test 'hover has no Apple interactor route on macOS, iOS, or tvOS; the touch family reports its typed denial', asserting hoverPoint/hoverRef facts and bound operations for all three Apple leaves.
  • packages/platform-linux/src/runtime.test.ts: new test 'hover has no Linux interactor route; the touch family reports its typed denial'.

Repointed the four manifest rows (macos-e2e, ios-simulator-e2e, tvos-e2e, linux-e2e coverage-manifest.ts) at these new tests. Verified locally: test:integration:macos-coverage, the iOS and tvOS smoke coverage tests, the full test/integration/*.test.ts node run (95 pass/0 fail/9 skipped-by-design), the touched vitest unit suites, and pnpm check:layering (clean).

Merge-order note: PR #2418 (issue #2411) is concurrently replacing these six coverage manifests with test/integration/command-coverage/declarations.ts. Whichever of #2418/#2420 merges second will need to re-apply this hover-row evidence change on rebase — the manifest file(s) will have moved/been restructured.

Pushed as 2371ba2f72; CI should re-run automatically on the new push.

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

The code looks sound at 2371ba2, and CI is green. Before closing #2412, please add the required historical single-platform addition replay and count the unsupported-owner edits it avoids. That measurement decides whether the pilot meets its two-owner stop rule.

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Historical single-platform addition replay requested above. Measured on this branch at 0eca306610 (the follow-up 36f5a22a34 only re-required tap/longPress/fill, which the replayed addition does not touch) against base e7d97f7dfe. Full record, unedited:

Base: origin/main @ e7d97f7dfea648e141e61b62312738dfb0bf447c.
Branch: claude/2409-2412-additive-facts @ 0eca306610 (one commit).

Family chosen: touch

touchRuntimeOperationFacts (packages/contracts/src/touch-runtime.ts:77 on main) is the family the
issue nominated, and it is the one where a typed unavailable default is most defensible:

  • It already half-did it. tapRef, hoverRef and fillRef were optional with a shared
    NATIVE_REF_UNAVAILABLE default (owner-capability-missing, no hint).
  • That optional-with-shared-default design avoided zero owner edits: on main, all seven owners
    (platform-apple, platform-android, platform-harmonyos, platform-linux, platform-web,
    provider-webdriver, provider-limrun) plus the shared unavailable builder passed those three
    optional keys explicitly, precisely to override the shared reason with their own
    (unsupported-platform-leaf, unsupported-device-kind, unsupported-provider-mode, …).
    The default was wrong-by-construction, so nobody used it.
  • The family is also the one with a genuine single-platform member already in it:
    tapElementSelector is Apple/iOS-only (platform-apple/src/runtime.ts:322 gates it on
    isIosFamily); every other owner writes a denial cell for it and nothing else.

The fix keeps the shared default but makes it owner-supplied: the input takes one required
unsupported: RuntimeOperationUnavailability, and every operation is optional. An owner names the
operations it implements; each operation it left out reports that owner's own denial — the same
reason and hint it wrote out by hand today. Omission is a classified denial, never an unclassified
cell and never an implied success: the output type still enumerates all eight operations, and the
input type refuses a call that does not carry unsupported.

Historical single-platform addition available to replay

There is no post-facts-era commit that adds a new runtime operation:
git log main -- packages/contracts/src/platform-runtime-operations.test.ts has no entry newer than
d26b0786fb (#2329, no key added), and platform-runtime-unavailable.ts has gained no operation
since c77bc40d48 (#2021). Every touch key entered the fact set in one migration commit,
759f175332 refactor: move Wave 5 touch commands to platform runtime (#1987), which is where the
seven non-Apple owners each wrote a tapElementSelector denial for an operation only Apple/iOS has
(packages/platform-apple/src/runtime.ts, iOS-family gate; the operation itself dates to
094c290703 "perf: speed up iOS replay runner (#557)").

So the replay is that addition re-run forward on both shapes: add one Apple/iOS-only touch
operation
(longPressElementSelector, shaped exactly like tapElementSelector) and let the
compiler enumerate the files it forces.

Replay A — old shape (origin/main, checked out in the worktree)

Probe: add the operation to TouchRuntimeOperations, to TouchRuntimeOperationFacts, to the
builder input as a required key (as tapElementSelector is on main), to RUNTIME_OPERATION_NAMES,
to platform-apple/src/runner-demand.ts, and declare it Apple-side.

pnpm typecheck + tsc -p tsconfig.json errors — one per forced edit:

# file why it must change
1 packages/contracts/src/platform-runtime-unavailable.ts shared unavailable builder (the fact surface platform-vega and Linux non-desktop spread) — 5 errors
2 packages/platform-android/src/runtime.ts denial cell only
3 packages/platform-harmonyos/src/runtime.ts denial cell only
4 packages/platform-linux/src/runtime.ts denial cell only
5 packages/platform-web/src/runtime.ts denial cell only
6 packages/provider-webdriver/src/platform-runtime.ts denial cell only
7 packages/provider-limrun/src/interaction-operations.ts denial cell only
8 packages/contracts/src/touch-runtime.test.ts family builder's own exact-facts assertion
9 src/__tests__/test-utils/runtime-operation-facts.ts fixture
10 src/daemon/__tests__/screenshot-runtime-fixture.ts fixture
11 src/daemon/handlers/__tests__/install-source.test.ts fixture
12 test/integration/provider-scenarios/provider-device-runtime.fixtures.ts fixture

Plus the addition's own four files: contracts/src/touch-runtime.ts,
contracts/src/runtime-operation-names.ts, platform-apple/src/runtime.ts,
platform-apple/src/runner-demand.ts.

Total: 16 files, 8 packages (contracts, platform-apple, platform-android,
platform-harmonyos, platform-linux, platform-web, provider-webdriver, provider-limrun)
plus root src/ and test/.

Replay B — new shape (this branch)

Same probe, minus every denial cell (scratchpad/2412-probe-newshape.patch). pnpm typecheck
passes with four files changed:

  • packages/contracts/src/runtime-operation-names.ts
  • packages/contracts/src/touch-runtime.ts
  • packages/platform-apple/src/runtime.ts
  • packages/platform-apple/src/runner-demand.ts

Test runs (packages/contracts packages/platform-* packages/provider-* +
runtime-binding-conformance-completeness): 331 files, only
packages/contracts/src/touch-runtime.test.ts fails, on its own enumerate-every-key assertion —
the same file the old shape forces. No owner test file needs an edit; every non-Apple owner
reports the new operation as unavailable with its own reason and hint, unedited.

Total: 5 files, 2 packages (contracts, platform-apple).

Result vs the stop rule

measure old shape new shape avoided
files 16 5 11
unsupported-owner fact files 7 0 7
packages touched 8 2 6
test fixture files 4 (+1 builder test) 0 (+1 builder test) 4

Stop rule was "fewer than two owner edits avoided → stop". Seven are avoided. Continue.

platform-vega never had a touch cell of its own on either shape — it inherits
createUnavailablePlatformRuntimeFacts, which is file #1 above; on the new shape that file needs no
edit either, so Vega is covered without a Vega-side or a contracts-side edit.

Typed-denial proof (acceptance criterion 2)

The branch's own diff is the reverse of the replay: it deletes 37 hand-written cells across the seven
owner files and the shared builder, and every admission test that asserts the unavailable reason per
non-supporting platform stays green with no edit:

  • Untouched and green: packages/platform-android/src/runtime.test.ts (26),
    packages/platform-apple/src/runtime.test.ts (40), packages/platform-web/src/runtime.test.ts
    (20), packages/platform-linux/src/runtime.test.ts (7),
    packages/platform-harmonyos/src/runtime.test.ts (9),
    packages/provider-webdriver/src/platform-runtime.test.ts (18),
    packages/provider-limrun/src/interaction-operations.test.ts (10),
    packages/contracts/src/platform-runtime-unavailable.test.ts (2),
    packages/contracts/src/platform-runtime.test.ts (9).
  • Wide run on the branch: 609 files / 4022 tests passed (packages/contracts,
    packages/platform-{vega,android,apple,web,linux,harmonyos},
    packages/provider-{webdriver,limrun}, src/daemon/__tests__,
    src/daemon/handlers/__tests__, test/integration/provider-scenarios).
  • pnpm typecheck clean.

The five edited test files are the family builder's own test (which gains a case for the new
default) and four fixtures that previously spelled out cells; none of them is an admission assertion.

Caveat on the forecast

The umbrella's ≥2-package target is only reached when the next capability addition is in this
family. Two owners still name a touch operation they refuse — platform-android keeps
hover: hoverUnavailable (unsupported-platform-leaf, not its device-kind reason) and
provider-webdriver keeps fillRef: typeUnavailable — because their denial for that one operation
differs from their family default. That is the design working: the default carries the owner's
reason, so an owner overrides it only where the reason genuinely differs, and one cell of override
is cheaper than eight cells of boilerplate.

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

This now conflicts with main after #2418 landed. Please move the new hover evidence references into the shared coverage declarations when resolving it, then rerun the coverage contracts. The historical addition replay requested above is still needed.

The touch family's facts builder took one required cell per operation, so an
operation only one owner implements still cost a hand-written denial in every
other owner. The builder now takes the family's denial once, as `unsupported`,
and every operation cell is optional: an owner names what it implements and
omission reports that denial verbatim, with the reason and hint the owner would
otherwise have repeated per cell.

Omission stays a classified refusal, never an unclassified cell and never an
implied success: `unsupported` is required, so a call that leaves the family
blank does not compile. Facts are unchanged for every owner, so the per-platform
admission assertions hold untouched.

Refs #2412
Making every touch cell optional dropped the compile-time guard on the
three operations every owner implements, with nothing replacing it: an
owner could silently drop `tap` and nothing would fail. Only the truly
per-owner cells (tapRef, hover, hoverRef, fillRef, tapElementSelector)
need `unsupported` as their default.

Restoring the required fields caught a real regression this refactor
introduced: platform-web's touch facts had silently lost their explicit
`longPress: readinessUnavailable` cell, so longPress reported the
family's `unsupported` fallback instead of the owner's own readiness
reason. Restored it, and fixed the other now-required-field call sites
(the fully-unavailable owner fixtures, and the screenshot-runtime-fixture
test double, which had also silently lost its longPress/fill distinction).

Refs #2412
…ce line

The touch family's additive-facts refactor dropped every owner's explicit
hover: unavailable cell in favor of the family-level unsupported fallback,
so the coverage manifests' literal-substring checks against
packages/platform-apple/src/runtime.ts and packages/platform-linux/src/runtime.ts
broke: that source line no longer exists.

Point the macOS, iOS, tvOS, and Linux hover rows at new tests that assert the
typed unavailable denial directly against each owner's bound facts, instead of
re-adding a hand-written denial line the refactor was meant to retire.

Refs #2412
@thymikee
thymikee force-pushed the claude/2409-2412-additive-facts branch from 2371ba2 to e23d92e Compare September 9, 2026 14:59
@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Rebased this branch onto main at 9d7d60c5e0 (which merged #2418, deleting the six per-platform coverage-manifest.ts files in favor of test/integration/command-coverage/declarations.ts/entries.ts/evidence.ts).

The manifest edits from the last commit (2371ba2f72) conflicted with #2418's deletion; resolved by taking main's deletion and porting the hover evidence forward instead:

  • Added APPLE_HOVER_DENIAL_EVIDENCE and LINUX_HOVER_DENIAL_EVIDENCE to test/integration/command-coverage/evidence.ts, pointing at the two new typed-denial tests (packages/platform-apple/src/runtime.test.ts and packages/platform-linux/src/runtime.test.ts).
  • Updated the hover row's iosSimulator, macos, tvos, and linux entries in test/integration/command-coverage/declarations.ts to use those constants, keeping the same contract classification and explanation text each row had.

Verified: all four coverage smoke tests (macos/ios-simulator/tvos/linux), both platform runtime test files, typecheck, check:layering, and check:affected all pass. Force-pushed the rebased branch.

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

The e23d92e conflict resolution correctly moves hover evidence into the shared declarations, and the typed denial behavior remains intact. No new code finding. The remaining acceptance item is #2412’s historical single-platform addition replay: report which unsupported-owner edits disappear and the count against the two-edit stop rule. iOS smoke is still running.

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

The replay is now in the PR body under "Replay: historical single-platform addition": the seven unsupported-owner edits that disappear are listed by file (six owner runtimes plus the shared unavailable builder), 16 files / 8 packages → 5 files / 2 packages, seven avoided against the two-edit stop rule. The 14:37 comment carries the full method. iOS smoke passed on e23d92e94c.

@thymikee

thymikee commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

The documented replay resolves the remaining evidence gap: the forward replay of the historical Apple-only operation shape avoids six unsupported platform/provider edits plus the shared builder, exceeding the two-edit stop rule. The caveat about this being a touch-family pilot is clear. Code review is clean at e23d92e and all checks are green.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 9, 2026
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.

refactor(contracts): additive capability facts pilot on one operation family

1 participant