Files
orca/mobile/rpc-foundation/goldens/matrix-git.commit-message-ai-git.generatecommitmessage-1.json
T
Jinwoo Hong fc525c355d refactor(mobile): send the task workspace-creation domain through typed RpcOperations (#20568)
* test(mobile): record main's task workspace-creation RPC behaviour before migrating it

28 scenarios over nine task senders, recorded from main so the step-4 migration of
the workspace-creation half of src/tasks/ has a frozen answer to compare against.
Four senders mount as plain exported functions; three are model-chained hooks
mounted the way the settings adapters mount theirs.

The 153 existing goldens change header-only (`baseline`, `recorderSha256`): any new
scenario re-digests the recorder, and the pinned baseline had drifted from main
because the source-control migration landed. Content is byte-identical on all 153 —
verified field-by-field against HEAD.

`operation-module-loader.ts` now shares src/transport/rpc-delivery-ambiguity.ts with
mounted modules instead of evaluating a second copy. The mark is a WeakSet keyed on
the rejection object, so the copy the loader built had an empty registry and every
delivery-unknown rejection read as a definite failure inside the operation under
test — worktree.create's whole replay path was unreachable. With one registry,
`tw-create-retry-ambiguous-after-drop` records the create still pending at the
reconnect wait and abandoning at exactly 20000 ms, while the unstamped-create
scenario records the same rejection surfacing at 0 ms. No existing golden moves:
no other mounted module consumes the mark.

`task-preferences-optimistic` is re-anchored above the send rather than across it,
so migrating this file does not have to move the anchor. It still kills, and for
the same reason: the preset the screen shows no longer follows the tap.

Scenarios deliberately pin the empty-message refusals (`*-refused-empty-message`,
`*-empty-message`), because a refusal with no message falls back to the screen's
copy while a transport error with no message does not, and the two paths are easy
to collapse when a call site moves behind an acceptance policy.

Goldens: 153 -> 201, 2.9M -> 3.7M.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): format the recording manifest and re-digest the goldens

`oxfmt --check` from mobile/ collapses a one-element `sites` array in each new
scenario. The JSON value is unchanged — verified by comparing both files parsed
and key-sorted — but the manifest is inside `recorderSha256`, so all 201 goldens
carry a new digest. Every other field, header and observation alike, is
byte-identical.

Re-recorded in a separate worktree at the previous commit so the goldens stay
attributable to main's product source rather than to the migration that follows.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): separate the goldens from the migration, and re-digest

The previous commit accidentally carried the product migration alongside the
manifest format, which both broke the commit that is supposed to prove parity and
left the suite red: the digest was recorded without a comment move that a lint fix
had made inside the adapter, so all 201 goldens failed their `recorderSha256`
header.

This backs the product half straight out again — the next commit re-applies it
byte-for-byte — and re-records from the pinned baseline in a separate worktree
carrying this branch's recorder, per the procedure in the recording README. Every
field except `recorderSha256` is byte-identical to the previous commit's goldens on
all 201 files, so no observation moved in either direction. The suite is green here
with main's product source, which is what makes the next commit's "no golden
changed" claim mean something.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): send the task workspace-creation domain through typed RpcOperations

12 of src/tasks/'s 37 raw-port files now send through a declared operation instead of
the raw request port: 36 references to 0, leaving 25 files and 73 references for the
provider item/detail/mutation half. No golden moved — `git show --stat` on this commit
touches nothing under mobile/rpc-foundation/, which is the parity claim.

Twenty-two operations over twenty methods, in four modules named for what they send:
workspace create (create, PR/MR base resolution, create-time capabilities), workspace
source (SSH connect/state, agent detection, repo hooks, sparse presets, ref search),
task runtime (status, ui.get/ui.set, preflight, Linear status, settings.update) and the
Smart picker's provider reads.

Two methods carry two policies each, and both pairs are named. `status.get`: the Tasks
screen cannot hydrate without it and surfaces the host's message, while create-time
capability probing degrades to "no capabilities" and creates anyway — so one throws on
refusal and one skips. `ui.set`: two sites await it, one is fire-and-forget and never
interprets the reply at all. Both pairs share one reader, so no method has two readers.
No new acceptance policy.

worktree.create keeps its delivery-unknown contract. `request` returns the transport
promise itself, so the retry loop catches the object the transport marked; two new tests
assert `toBe(marked)` in one direction and that an unmarked rejection stays unmarked in
the other, because a mark added on the way out would replay a create the host never
received. `tw-create-retry-ambiguous-after-drop` records the create still pending at the
reconnect wait and abandoning at exactly 20000 ms.

Three sites still read the raw refusal envelope before interpreting, because the code or
the message decides the route and no acceptance policy carries either through: the create
retry needs the message for `isRetryableWorktreeCreateConflict`, and the paste lookup
needs `method_not_found` to retire the slug probe host-wide. Both are documented at the
site.

The hydration barrier keeps raw requests inside its `Promise.all`. main's group rejects as
soon as one leg rejects; `startRpcOperation` + `interpretAtRpcBarrier` would wait for the
slowest peer and let a later policy surface a different error. Interpretation stays after
the `stale` guard, where it was.

`WorkspaceCreateParams` is now `RpcSendParams<'worktree.create'>` rather than
`Record<string, unknown>`, which types the builder and the operation together; every field
the three builders already sent typechecks against the host schema unchanged.
`RpcSendArguments` now also makes params optional for a method whose params type has no
required field, because `preflight.check` is such a method and main sent it none —
requiring `{}` would have put a new object on the wire.

The Mobile Tasks source-parity hashes move for the same reason bound settings requests
moved them: the method string and the envelope read leave the screen. The signature diff
is evidence rather than a re-pin — `semantics` is a pure deletion of 22 `rpc:` call
signatures and 22 method literals with nothing added, statement/declaration/render/style
counts are unchanged, and render tokens, styles and declarations are byte-identical.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): split the task workspace adapters at the sender/hook seam

The single adapter file reached 344 lines against mobile's 300-line limit. CI lints
every file, so this is red there even though the changed-code gate does not report it.
Split along the seam the recording README already draws: exported async senders that
take a client and need no React host, and the drawer's three model-chained hooks.
No adapter body changed.

Both files are inside `recorderSha256`, so all 201 goldens carry a new digest. Every
other field is byte-identical, verified file by file. Re-recorded from the pinned
baseline in a separate worktree carrying this branch's recorder, so the goldens stay
attributable to main's product source.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* refactor(mobile): give the one-key unchecked reader a name

Four readers were the same three lines: read one property off the reply, wrap
it unchecked. `rpcUncheckedMemberReader` is the one-key sibling of the existing
`rpcUncheckedPayloadReader`, so the annotation and the closure go away at each
site. The pilot's `commitCompareEntriesReader` is converted too, so the helper
has no longhand twin left to copy from.

No behaviour change: the helper composes the same `rpcReadUnchecked` over
`rpcPayloadMember`, including the property-read exception on a null result.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): record the local arm of workspace agent detection

`preflight.detectAgents` was the one migrated operation with no recorded
coverage: the ssh adapter hardcoded `connectionId: 'ssh-1'`, so the detection
effect's ternary only ever took the remote arm and the local call site could be
repointed at another method without a golden noticing.

The adapter now takes the connectionId as a parameter and registers twice;
`tasks.workspace-ssh-local` mounts the same hook with no connection, which is
the only difference the effect branches on. Recorded at the pinned baseline
with this branch's recorder laid over it, so the new golden is main's
behaviour and the migrated code has to reproduce it — it does.

Goldens: two added (`tw-workspace-ssh-local-agents` and its reply matrix). The
other 201 changed on `recorderSha256` only, because the adapter edit moves the
recorder digest every golden pins.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): drive workspace create and the Linear list to a recorded wire

Two operations passed a policy swap unnoticed, both because no golden reached
their acceptance branch.

`worktree.create`: the create hook's fixture resolved setup to a prompt, so all
three settings.task-workspace scenarios stopped before the request and the only
consumer that hands a refusal to interpret was never recorded. The adapter now
takes the setup resolution as a parameter and registers a second family that
resolves it, so createWorkspace runs to the wire. Two scenarios: a Linear item
that creates directly, and a GitHub pull request that resolves its base first,
which also puts this hook's built params — start point, generated display name,
agent launch fields — in a golden for the first time. The existing prompt
family is untouched, so its recordings still pin that branch.

`linear.listIssues`: it appeared only in a non-base scenario, and the matrix
reads the family base, so the family had no partition for it. The base now
lists assigned issues after searching.

Goldens: five added. Five moved beyond the digest, all derived from the
smart-search base that gained the list leg. The other 198 changed on
`recorderSha256` only. Recorded at the pinned baseline with this branch's
recorder laid over it, so every new golden is main's behaviour.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): drop the unreachable unmount branches from the task adapters

Nothing dispatches `unmount` to these three adapters: the only producer is
`lifecycleSchedules`, driven from a hardcoded five-id list that names no
task-workspace family, and it pushes a `remount` right after, which these
adapters would throw on. The branch read as lifecycle coverage that was never
wired up. `dispose: hook.unmount` already tears the mount down.

Goldens re-recorded at the pinned baseline because the recorder digest moved;
`recorderSha256` is the only line that changed in all 208.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record the goldens at main's post-squash baseline

Recorded from a detached checkout of e53f1557e1 (main's unmigrated
product code) with this branch's recorder laid over it, so the parity
claim stays non-circular.

- `baseline` repinned to e53f1557e1 on all 208 goldens; main pinned
  5ec0b2698f, a pre-squash branch commit not reachable from main.
- `recorderSha256` moved on all 208 because this branch's adapters are
  in the whole-manifest digest.
- 55 task-workspace goldens re-recorded at the new baseline.
- No other line in any of main's 153 goldens changed.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record the goldens under #20562's per-scenario digest

Baseline repinned to 50e752fc66 and all 208 goldens recorded from that
commit's unmigrated product tree with this branch's recorder laid over it.
recorderSha256 moves on every golden because the task-workspace adapters
live in the recorder directory. scenarioSha256 does not move on any of
main's 153: the manifest only adds 31 scenarios and edits none, which is
the property #20562 was built to give.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-14 13:36:16 -04:00

661 lines
15 KiB
JSON

{
"operation": "source-control.commit-message",
"family": "git.commit-message-ai",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "50e752fc66a84638b0a8c1f4bdbbd5fdc708c3e9",
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
"recorderSha256": "5ec2faffa944097d2916e2e9e2259b9d0c68e364c5f865bafd8a9600faedbdc7",
"scenarioSha256": "22ea5279155ecf749aaab521ffd570221ac3169b177fc1daf85ef93a49d38260",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 4,
"values": {
"125fbea5f50a": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"1290c04bc26c": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"message": "feat: recorded",
"success": true
}
},
"31141af16c2d": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
}
},
"3186ccdbc53f": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": "Unknown method",
"success": false
}
},
"3ef8a5f65bc8": {
"generated": {
"message": "feat: recorded",
"success": true
}
},
"3f131697d120": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
}
},
"46920d3cb0c1": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "refused"
}
}
}
},
"5213fea85cf0": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "inner refused",
"ok": false
}
}
}
},
"5246b9fd2d12": {
"generated": {
"error": "outer refused",
"success": false
}
},
"5ad7ea556320": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": "outer refused",
"success": false
}
},
"63410fd1b187": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": ""
},
"id": "frame-1",
"ok": false
}
}
},
"6453ff669a2f": {
"generated": {
"error": "Failed to generate commit message",
"success": false
}
},
"68e6f784ba09": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"$rpc": "null"
}
}
}
},
"915059a8a000": {
"generated": {
"error": "Unknown method",
"success": false
}
},
"9270aeb7d9c6": {
"status": "pending",
"startedAt": 0
},
"a0551476eb3b": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": "No commit message generated",
"success": false
}
},
"a09d0ada6684": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"message": "feat: recorded",
"success": true
}
}
}
},
"a64074c2ba96": {
"name": "git.generateCommitMessage#1",
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"git.generateCommitMessage\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
},
"a947768bc0ed": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
},
"adb40821f3e2": {
"generated": "ungenerated"
},
"b95c8d57adc9": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": "Failed to generate commit message",
"success": false
}
},
"bfe04c9c1653": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true
}
}
},
"c39c20fa07f2": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
}
},
"c7584e82c72f": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
},
"e96b430d7d35": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "method_not_found",
"message": "Unknown method"
},
"id": "frame-1",
"ok": false
}
}
},
"fa7a334b373d": {
"name": "git.generateCommitMessage#1",
"args": [
{
"name": "method",
"value": "git.generateCommitMessage"
},
{
"name": "params",
"value": {
"worktree": "id:repo42::/p"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "outer refused"
},
"id": "frame-1",
"ok": false
}
}
},
"faad14b9f95d": {
"generated": {
"error": "No commit message generated",
"success": false
}
}
},
"recording": {
"scenario": "matrix-git.commit-message-ai-git.generatecommitmessage-1",
"checkpoints": [
{
"id": "sc-commit-message-generated.prelude:pending",
"observation": {
"sender": ["125fbea5f50a"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "9270aeb7d9c6"
},
"state": "adb40821f3e2",
"effects": []
}
},
{
"id": "sc-commit-message-generated.normal:settled",
"observation": {
"sender": ["a09d0ada6684"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "1290c04bc26c"
},
"state": "3ef8a5f65bc8",
"effects": []
}
},
{
"id": "sc-commit-message-generated.result-absent:settled",
"observation": {
"sender": ["bfe04c9c1653"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "b95c8d57adc9"
},
"state": "6453ff669a2f",
"effects": []
}
},
{
"id": "sc-commit-message-generated.result-null:settled",
"observation": {
"sender": ["68e6f784ba09"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "b95c8d57adc9"
},
"state": "6453ff669a2f",
"effects": []
}
},
{
"id": "sc-commit-message-generated.inner-ok-missing:settled",
"observation": {
"sender": ["46920d3cb0c1"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "a0551476eb3b"
},
"state": "faad14b9f95d",
"effects": []
}
},
{
"id": "sc-commit-message-generated.inner-false-string-error:settled",
"observation": {
"sender": ["5213fea85cf0"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "a0551476eb3b"
},
"state": "faad14b9f95d",
"effects": []
}
},
{
"id": "sc-commit-message-generated.inner-false-object-error:settled",
"observation": {
"sender": ["3f131697d120"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "a0551476eb3b"
},
"state": "faad14b9f95d",
"effects": []
}
},
{
"id": "sc-commit-message-generated.outer-refused:settled",
"observation": {
"sender": ["fa7a334b373d"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "5ad7ea556320"
},
"state": "5246b9fd2d12",
"effects": []
}
},
{
"id": "sc-commit-message-generated.outer-refused-no-message:settled",
"observation": {
"sender": ["63410fd1b187"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "b95c8d57adc9"
},
"state": "6453ff669a2f",
"effects": []
}
},
{
"id": "sc-commit-message-generated.method-not-found:settled",
"observation": {
"sender": ["e96b430d7d35"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "3186ccdbc53f"
},
"state": "915059a8a000",
"effects": []
}
},
{
"id": "sc-commit-message-generated.transport-rejection:settled",
"observation": {
"sender": ["31141af16c2d"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "a947768bc0ed"
},
"state": "adb40821f3e2",
"effects": []
}
},
{
"id": "sc-commit-message-generated.transport-rejection-no-message:settled",
"observation": {
"sender": ["c39c20fa07f2"],
"payloads": ["a64074c2ba96"],
"settlements": {
"generate": "c7584e82c72f"
},
"state": "adb40821f3e2",
"effects": []
}
}
]
}
}