Skip to content

refactor(mcp): project interaction family output schemas from their owning module - #3025

Merged
thymikee merged 2 commits into
refactor/mcp-output-schemas-2819-system-index-schemasfrom
refactor/mcp-output-schemas-2819-interaction-index-schemas
Sep 29, 2026
Merged

thymikee merged 2 commits into
refactor/mcp-output-schemas-2819-system-index-schemasfrom
refactor/mcp-output-schemas-2819-interaction-index-schemas

Conversation

@thymikee

@thymikee thymikee commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Summary

Moves the 6 hand-authored MCP outputSchema entries for press, click, fill, longpress, hover, and find out of src/mcp/command-output-schemas.ts into a frozen INTERACTION_COMMAND_OUTPUT_SCHEMAS map exported from src/commands/interaction/index.ts, following the same #2810 projection seam the system family used. Registers the family in the PROJECTED_FAMILIES disjointness check and adds a colocated command-tools-interaction-schemas.test.ts proving reference equality with the module object (including the settle-observation copy for the 5 settle-capable entries; find carries no such trait).

postActionSurfaceChangeSchema is duplicated locally in the interaction module rather than imported back from command-output-schemas.ts (which still needs it for the generic --settle observation shared across families) — same duplication precedent the system family's move established for constSchema. responseCostSchema and the other interaction-only helpers had no other consumer and moved outright, no duplication.

Behavior is unchanged: every advertised MCP outputSchema for these 6 commands is byte-identical before and after (verified below).

Part of #2819. Stacked on refactor/mcp-output-schemas-2819-system-index-schemas (#3014).

Touched files: 4 (src/commands/interaction/index.ts, src/mcp/command-output-schemas.ts, src/mcp/__tests__/command-tools-replay-schemas.test.ts, new src/mcp/__tests__/command-tools-interaction-schemas.test.ts).

Validation

Tested commit: 00d88dd7f8.

pnpm check:affected --run: 1343/1344 passed. The one failure, test/integration/provider-scenarios/ios-lifecycle.test.ts (5s timeout, unrelated iOS provider-scenario test), passed cleanly in isolation on retry — a contention flake, not caused by this change.

No device is needed for this change (pure MCP schema projection, no runtime behavior touched). Instead, byte-identical proof was gathered by dumping COMMAND_OUTPUT_SCHEMAS.{press,click,fill,longpress,hover,find} (deep, key-sorted JSON) from a worktree checked out at the parent commit and from this branch's head, then diffing:

diff /tmp/schemas-before.json /tmp/schemas-after.json
# exit code 0 — no output, 3143 lines each

No unresolved risk: the change is a pure move with a compiler-enforced (satisfies) totality check and a reference-equality test proving the composed map still serves the family module's own schema objects.

Review in cubic

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.88 MB 4.88 MB +58 B
Package (unpacked) 4.88 MB 4.88 MB +58 B
Package (download) 1.46 MB 1.46 MB +162 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.9 ms 28.2 ms +1.3 ms
CLI --help 82.8 ms 83.7 ms +0.9 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/commands/interaction/index.ts">

<violation number="1" location="src/commands/interaction/index.ts:300">
P3: `INTERACTION_COMMAND_OUTPUT_SCHEMAS` is exported as a mutable object, despite the promised frozen projection map. Wrap the map in `Object.freeze` so consumers cannot replace its command entries or mutate the canonical projection through the exported map.</violation>
</file>

<file name="src/mcp/__tests__/command-tools-interaction-schemas.test.ts">

<violation number="1" location="src/mcp/__tests__/command-tools-interaction-schemas.test.ts:13">
P3: The comment claims find is read-only, but the module's own find schema documents mutating actions (`x`/`y`: "Resolved x/y coordinate for mutating find actions", `message`: "Diagnostic message for mutating find actions") and the CLI accepts mutating find actions. Recount only what the test depends on: find carries no post-action observation trait.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

* validating. #1652: the opt-in `settle` observation is NOT listed here — the trait derivation
* pass in that file grafts it onto settle-capable entries.
*/
export const INTERACTION_COMMAND_OUTPUT_SCHEMAS = {

@cubic-dev-ai cubic-dev-ai Bot Sep 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: INTERACTION_COMMAND_OUTPUT_SCHEMAS is exported as a mutable object, despite the promised frozen projection map. Wrap the map in Object.freeze so consumers cannot replace its command entries or mutate the canonical projection through the exported map.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/interaction/index.ts, line 300:

<comment>`INTERACTION_COMMAND_OUTPUT_SCHEMAS` is exported as a mutable object, despite the promised frozen projection map. Wrap the map in `Object.freeze` so consumers cannot replace its command entries or mutate the canonical projection through the exported map.</comment>

<file context>
@@ -54,6 +63,288 @@ import {
+ * validating. #1652: the opt-in `settle` observation is NOT listed here — the trait derivation
+ * pass in that file grafts it onto settle-capable entries.
+ */
+export const INTERACTION_COMMAND_OUTPUT_SCHEMAS = {
+  press: tapInteractionResponseDataSchema,
+  click: tapInteractionResponseDataSchema,
</file context>
Fix with cubic


// press, click, fill, longpress, and hover carry the post-action observation trait (#1652): the
// composed map grafts a `settle` property onto a COPY, so they are not reference-equal to the
// module's own object. find is read-only and carries no such trait.

@cubic-dev-ai cubic-dev-ai Bot Sep 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The comment claims find is read-only, but the module's own find schema documents mutating actions (x/y: "Resolved x/y coordinate for mutating find actions", message: "Diagnostic message for mutating find actions") and the CLI accepts mutating find actions. Recount only what the test depends on: find carries no post-action observation trait.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp/__tests__/command-tools-interaction-schemas.test.ts, line 13:

<comment>The comment claims find is read-only, but the module's own find schema documents mutating actions (`x`/`y`: "Resolved x/y coordinate for mutating find actions", `message`: "Diagnostic message for mutating find actions") and the CLI accepts mutating find actions. Recount only what the test depends on: find carries no post-action observation trait.</comment>

<file context>
@@ -0,0 +1,58 @@
+
+// press, click, fill, longpress, and hover carry the post-action observation trait (#1652): the
+// composed map grafts a `settle` property onto a COPY, so they are not reference-equal to the
+// module's own object. find is read-only and carries no such trait.
+const SETTLE_DERIVED_COMMANDS = new Set(['press', 'click', 'fill', 'longpress', 'hover']);
+
</file context>
Suggested change
// module's own object. find is read-only and carries no such trait.
// module's own object. find, which carries no such trait, survives the spread untouched.
Fix with cubic

@thymikee

Copy link
Copy Markdown
Member Author

Findings at 00d88dd.

postActionSurfaceChangeSchema at https://github.com/callstack/agent-device/blob/00d88dd/src/commands/interaction/index.ts#L78 is a verbatim copy of the one at https://github.com/callstack/agent-device/blob/00d88dd/src/mcp/command-output-schemas.ts#L71, and each copy points a comment at the other as if that were a reason. It isn't: command-output-schemas.ts already imports interaction/index.ts (line 13), so interaction can export the schema and the MCP file can import it along that same edge, no back-import needed. Two sources of truth for one contract type means an edit to one copy lets --verify evidence and --settle observation report different surfaceChange shapes, and nothing tests that they still match. Each contract schema should have one declaration, with every other reader importing it. Can postActionSurfaceChangeSchema move to src/commands/interaction/index.ts (or src/commands/command-input.ts), get imported into command-output-schemas.ts, and have both mirror comments deleted?

constSchema at https://github.com/callstack/agent-device/blob/00d88dd/src/commands/interaction/index.ts#L66 is now a third verbatim copy, alongside https://github.com/callstack/agent-device/blob/00d88dd/src/mcp/command-output-schemas.ts#L51 and https://github.com/callstack/agent-device/blob/00d88dd/src/commands/system/index.ts#L77. src/commands/command-input.ts already owns and exports the sibling primitives (objectSchema, enumSchema, stringSchema, booleanSchema...), and this module already imports from there, so the seam exists. The system family already copied constSchema once; repeating the pattern here means every remaining family migration under #2819 adds one more copy to keep in sync by hand. JSON-schema primitives should live only in src/commands/command-input.ts. Can constSchema (and nullableStringSchema) move there and be removed from all three local copies, including this one?

The Coverage failure (eager-closure-budgets: src/cli.ts evaluates 296 modules vs 295) looks unrelated to this PR: it traces to the system/index.ts -> packages/contracts/src/fold-runtime.ts edge, which the stacked base commit 110cb57 (#3014) added, not this PR's diff. This PR's new imports in interaction/index.ts are type-only or already pulled from ../command-input.ts. Smoke Tests is still queued, and a schema move alone shouldn't touch the device route it exercises, but that run hasn't finished so it isn't confirmed either way. I didn't run the author's deep key-sorted JSON dump; the byte-identity claim rests on a textual diff of the moved hunks, and I didn't re-run the budget test on the head commit myself.

Before this can merge, #3014 needs to fix the inherited eager-closure edge (system/index.ts -> fold-runtime.ts) so Coverage passes again, and this PR still needs the postActionSurfaceChangeSchema and constSchema duplicates resolved as described above.

@thymikee
thymikee added this pull request to stack #3043 September 28, 2026 13:44
@thymikee
thymikee force-pushed the refactor/mcp-output-schemas-2819-interaction-index-schemas branch from 00d88dd to 9fa2dd5 Compare September 28, 2026 15:31
@thymikee

Copy link
Copy Markdown
Member Author

At 9fa2dd5, both duplicate declarations from the earlier review (00d88dd, #3025 (comment)) are still there, so the findings from that round are still open.

postActionSurfaceChangeSchema at src/commands/interaction/index.ts#L78 is still a verbatim copy of the one at src/mcp/command-output-schemas.ts#L68, and each copy still carries a comment pointing at the other. command-output-schemas.ts already imports interaction/index.ts at line 14, so the interaction module can export the one declaration and command-output-schemas.ts can import it along that same edge. Right now the PostActionSurfaceChange contract has two sources of truth: if only one copy gets edited, the outputSchema for InteractionEvidence.surfaceChange and the one for the generic --settle observation will drift apart, and nothing catches it. Every contract schema should have exactly one declaration, owned by its family module, with every other reader importing it; here that means exporting the schema from interaction/index.ts and deleting the copy and both mirror comments in command-output-schemas.ts.

The local function constSchema(value) at src/commands/interaction/index.ts#L66 is also a verbatim copy of the exported constSchema that the rebased base (a595a82) added at src/commands/command-input.ts#L110, which interaction/index.ts already imports other schema builders from at lines 30-42. This adds a second copy of a helper the base branch just deduplicated, so the commit message's precedent for a local copy no longer holds. Can the local constSchema be deleted and constSchema added to the existing ../command-input.ts import instead? Worth asking the same question about nullableStringSchema at line 70: should it move into command-input.ts next to the other builders too?

The failing Coverage check looks unrelated to this diff. git diff a595a82 9fa2dd5 only touches src/commands/interaction/index.ts, src/mcp/command-output-schemas.ts, and two MCP tests, with no edge into packages/contracts. The reported eager-closure module chain (fold-runtime.ts -> device-rotation.ts -> kernel/errors -> kernel/redaction) traces back to stacked base commit bf0da83 (#3014), which moved FOLD_SCREEN_COORDINATE_SPACE into device-rotation.ts and made fold-runtime.ts re-export it; that edge should be fixed on the #3014 branch, for example by keeping the constant in fold-runtime.ts or another leaf module with no kernel import and re-exporting it from the facade or system/index.ts instead. Smoke Tests is still queued, and this PR only changes MCP schema data, not device routes, so that queue isn't informative either way.

I didn't run the eager-closure test locally; the module-chain attribution above comes from the CI log and the commit topology. I also didn't check whether the interaction ownership test carries the two-way ownership proof that a595a82 added for the system family, since the prior review already covered that test and this delta doesn't touch it.

There are no conflicts. The two duplicate declarations in src/commands/interaction/index.ts need to go before this can merge, and the fold-runtime eager-closure edge needs to be fixed separately on the #3014 base branch.

@thymikee
thymikee force-pushed the refactor/mcp-output-schemas-2819-interaction-index-schemas branch from 9fa2dd5 to 311d4be Compare September 28, 2026 18:05
@thymikee
thymikee force-pushed the refactor/mcp-output-schemas-2819-interaction-index-schemas branch from 311d4be to 9b674a0 Compare September 28, 2026 18:23
…wning module

Moves the 6 hand-authored MCP outputSchema entries for press, click, fill,
longpress, hover, and find out of command-output-schemas.ts into a frozen
INTERACTION_COMMAND_OUTPUT_SCHEMAS map exported from
src/commands/interaction/index.ts, following the #2810 projection seam.
Registers the family in the PROJECTED_FAMILIES disjointness check and adds
a colocated command-tools-interaction-schemas.test.ts proving reference
equality with the module object, including the settle-observation copy for
the five settle-capable entries (find has no such trait).

postActionSurfaceChangeSchema is duplicated locally rather than imported
back from command-output-schemas.ts (which still needs it for the generic
--settle observation), matching the constSchema duplication precedent from
the system family's move; responseCostSchema and the other interaction-only
helpers had no other consumer and moved outright.

Part of #2819.
@thymikee
thymikee force-pushed the refactor/mcp-output-schemas-2819-interaction-index-schemas branch from 9b674a0 to dd27903 Compare September 28, 2026 20:50
@thymikee

thymikee commented Sep 28, 2026 •

Copy link
Copy Markdown
Member Author

Rebased onto the updated base (system-index-schemas) and pushed; head is now dd27903.

  • postActionSurfaceChangeSchema: now declared once in src/commands/interaction/index.ts and exported. The copy in src/mcp/command-output-schemas.ts and both mirror comments are gone; the MCP file imports it.
  • constSchema: the local copy is deleted; it is imported from src/commands/command-input.ts, which the base now owns. No other declaration remains in src/ or packages/ (checked with grep).
  • nullableStringSchema: moved into src/commands/command-input.ts next to the other schema builders; it had only one declaration, now shared.
  • Coverage eager-closure failure: it came from the base branch edge, and the base now carries the fix; this branch has no new edge of its own.
  • Test comment about find: reworded to say only that find carries no post-action observation trait.
  • Not changed: Object.freeze on INTERACTION_COMMAND_OUTPUT_SCHEMAS. The system and other family maps are plain objects too, and the composed map already copies entries, so I kept them consistent.

pnpm check:affected passes locally. CI results are pending on the new head.

@thymikee

Copy link
Copy Markdown
Member Author

The fixes since 9b674a0 look good, and I found no remaining problems in dd27903. The interaction family output schemas now come from their owning module. The removed declarations match the shared ones, and the builder bodies are identical.

I compared the removed and shared declarations as text. I did not dump COMMAND_OUTPUT_SCHEMAS at base and head, and I did not run the unit tests or pnpm check:affected on this commit.

Two checks fail, and neither looks related to this diff. Smoke fails with "prepare ios-runner timed out" while it prepares the iOS runner. This diff only touches MCP output-schema data, so it does not reach that route. Coverage fails in daemon-client-transport.test.ts ("a delayed restart health probe stops at the RPC deadline without retrying"). This diff does not touch daemon-client, and the same test fails on #3029. #3015 recently changed daemon-client health caching, so it may be the cause. I did not bisect it.

Before merge, Smoke and Coverage need a green rerun or a fix on main, and the stacked base #3014 must merge first. I found no conflicts.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 29, 2026
@thymikee
thymikee merged commit fd3173d into main Sep 29, 2026
24 of 26 checks passed
@thymikee
thymikee deleted the refactor/mcp-output-schemas-2819-interaction-index-schemas branch September 29, 2026 09:52
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.

1 participant