mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 08:02:38 +00:00
test(mobile): record the real worktree catalog snapshot result in the RPC recorder (#20873)
* test(rpc-recording): record the real worktree catalog snapshot result `worktree.catalog-snapshot`'s action returned `WorktreeCatalogSnapshotClient.fetch`'s raw result. That value nests the live `RpcClient` under `pending.client`, so `captureValue` threw `Unsupported observation: function`: both goldens baked an `unhandled-rejection` effect and left the action's settlement `pending`, proving nothing about what the fetch returns. Project the result through `projectObservable` on the settlement path, the same way `state()` already shows it and the same way #20667 fixed `transport.pairing-race`. Rejections still propagate unchanged, so the two transport-rejection matrix partitions keep their recorded errors. The matrix golden now discriminates all eleven reply partitions: a full admission, six invalid ones, three `request_failed` codes and two rejections. `recorderSha256` does not move; `adapterSha256` moves on the six goldens mounted through this adapter module, four of which have no other change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(rpc-recording): fail the suite when a golden records a recorder failure A projection refusal settles as data, not as a failed run: `captureValue` throws inside the action, the recorder captures it as an `unhandled-rejection` effect, and `--record` writes a green golden whose action never settled. The class has landed twice — `transport.pairing-race` in #20667 and `worktree.catalog-snapshot` in the previous commit — and reverting either one plus a re-record would go green with the broken golden back. Read every golden and fail on an `unhandled-rejection` effect or on either of `captureValue`'s refusal texts in any observation field. A positive control in the same case asserts both detectors fire, so the absence claim is load-bearing rather than vacuous. Not a recording driver, so `recorderSha256` excludes it and no golden moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(rpc-recording): ban only the recorder's own refusals, not product bugs The gate also failed on any effect named `unhandled-rejection`, which bans a real observation: detached-rejection capture exists to pin a main bug in a golden, and `unhandled-recording.test.ts` pins the capture precisely because no golden carries one today. Banning the name would make recording a genuine product failure a test failure. Drop that detector. `refusalText` alone catches all seven checkpoints of the worktree-catalog regression, because the refusal is the message of the captured error rather than the effect's name. The positive control now seeds that shape — the refusal inside a recorded error under `effects` — so the surviving detector is still proven to fire. Vacuity guards on golden and checkpoint counts are unchanged. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(rpc-recording): re-record the worktree catalog goldens over main's engine digest #20874 re-digested `recorderSha256` on all 509 goldens, so the six goldens mounted through the worktree catalog adapter had to be recorded again from the pin rather than merged. Bodies and `adapterSha256` are unchanged from the pre-merge recording. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"baseline": "e7206f62a827f4fe0a2badf3eecd2167b8ac4285",
|
||||
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
|
||||
"recorderSha256": "e044443e01f282566a5e0eff110d0861d2a1047ca8d8696a2e7ff914f51957f3",
|
||||
"adapterSha256": "6119d409e1958877e4a04b3901f94a3609ac0420f5ca02af20eb8dae5cbf3408",
|
||||
"adapterSha256": "4e942ddfbaa0ba6bfc2993969276987f6528ac53765d125e99a830e261f93a8e",
|
||||
"scenarioSha256": "95ca47f382997c412da974e564a46b1ae0c20d6e0f3ca14258d33c8d8b51a160",
|
||||
"platform": "darwin",
|
||||
"scenarioVersion": 1,
|
||||
@@ -112,12 +112,19 @@
|
||||
"admitted": "unadmitted",
|
||||
"fetched": "unfetched"
|
||||
},
|
||||
"2d40d1d38104": {
|
||||
"name": "unhandled-rejection",
|
||||
"3ef9b57ea9ad": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"category": "Error",
|
||||
"isRpcDeliveryUnknown": false,
|
||||
"message": "Unsupported observation: function"
|
||||
"kind": "response",
|
||||
"pending": {
|
||||
"admission": {
|
||||
"kind": "invalid"
|
||||
},
|
||||
"client": "logical-client",
|
||||
"hostId": "host-1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4262ba495b1b": {
|
||||
@@ -258,6 +265,29 @@
|
||||
"status": "pending",
|
||||
"startedAt": 0
|
||||
},
|
||||
"9948855e8b8d": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"kind": "response",
|
||||
"pending": {
|
||||
"admission": {
|
||||
"kind": "full",
|
||||
"snapshotId": "snapshot-1",
|
||||
"worktrees": [
|
||||
{
|
||||
"displayName": "One",
|
||||
"repo": "Repo",
|
||||
"worktreeId": "w-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"client": "logical-client",
|
||||
"hostId": "host-1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"a0fab6bf1fb0": {
|
||||
"name": "worktree.ps#1",
|
||||
"args": [
|
||||
@@ -584,10 +614,10 @@
|
||||
"sender": ["227f9e3de4fa"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "9948855e8b8d"
|
||||
},
|
||||
"state": "ab1a9ba6301c",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -596,10 +626,10 @@
|
||||
"sender": ["ad584cc963bb"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "3ef9b57ea9ad"
|
||||
},
|
||||
"state": "50c4271e912d",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -608,10 +638,10 @@
|
||||
"sender": ["b670d230caf2"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "3ef9b57ea9ad"
|
||||
},
|
||||
"state": "50c4271e912d",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -620,10 +650,10 @@
|
||||
"sender": ["4262ba495b1b"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "3ef9b57ea9ad"
|
||||
},
|
||||
"state": "50c4271e912d",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -632,10 +662,10 @@
|
||||
"sender": ["5a288976750e"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "3ef9b57ea9ad"
|
||||
},
|
||||
"state": "50c4271e912d",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -644,10 +674,10 @@
|
||||
"sender": ["f5d207eddd1d"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "3ef9b57ea9ad"
|
||||
},
|
||||
"state": "50c4271e912d",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"baseline": "e7206f62a827f4fe0a2badf3eecd2167b8ac4285",
|
||||
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
|
||||
"recorderSha256": "e044443e01f282566a5e0eff110d0861d2a1047ca8d8696a2e7ff914f51957f3",
|
||||
"adapterSha256": "6119d409e1958877e4a04b3901f94a3609ac0420f5ca02af20eb8dae5cbf3408",
|
||||
"adapterSha256": "4e942ddfbaa0ba6bfc2993969276987f6528ac53765d125e99a830e261f93a8e",
|
||||
"scenarioSha256": "fa0e28a167a5fba6fe7ffebb9f4ad28dd413d601c07116a24a7781d156f54beb",
|
||||
"platform": "darwin",
|
||||
"scenarioVersion": 1,
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"baseline": "e7206f62a827f4fe0a2badf3eecd2167b8ac4285",
|
||||
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
|
||||
"recorderSha256": "e044443e01f282566a5e0eff110d0861d2a1047ca8d8696a2e7ff914f51957f3",
|
||||
"adapterSha256": "6119d409e1958877e4a04b3901f94a3609ac0420f5ca02af20eb8dae5cbf3408",
|
||||
"adapterSha256": "4e942ddfbaa0ba6bfc2993969276987f6528ac53765d125e99a830e261f93a8e",
|
||||
"scenarioSha256": "d321d6c17e67ae90f6ceefb775495ff765a86a35423ed33a212e71ec5e9e94aa",
|
||||
"platform": "darwin",
|
||||
"scenarioVersion": 1,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"baseline": "e7206f62a827f4fe0a2badf3eecd2167b8ac4285",
|
||||
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
|
||||
"recorderSha256": "e044443e01f282566a5e0eff110d0861d2a1047ca8d8696a2e7ff914f51957f3",
|
||||
"adapterSha256": "6119d409e1958877e4a04b3901f94a3609ac0420f5ca02af20eb8dae5cbf3408",
|
||||
"adapterSha256": "4e942ddfbaa0ba6bfc2993969276987f6528ac53765d125e99a830e261f93a8e",
|
||||
"scenarioSha256": "d2947158840576cbd0f0604ed3d37b0f446c63c6d439b4d1b7def7fe8524523d",
|
||||
"platform": "darwin",
|
||||
"scenarioVersion": 1,
|
||||
@@ -60,18 +60,33 @@
|
||||
"admitted": "unadmitted",
|
||||
"fetched": "unfetched"
|
||||
},
|
||||
"2d40d1d38104": {
|
||||
"name": "unhandled-rejection",
|
||||
"value": {
|
||||
"category": "Error",
|
||||
"isRpcDeliveryUnknown": false,
|
||||
"message": "Unsupported observation: function"
|
||||
}
|
||||
},
|
||||
"9270aeb7d9c6": {
|
||||
"status": "pending",
|
||||
"startedAt": 0
|
||||
},
|
||||
"9948855e8b8d": {
|
||||
"status": "fulfilled",
|
||||
"startedAt": 0,
|
||||
"settledAt": 0,
|
||||
"value": {
|
||||
"kind": "response",
|
||||
"pending": {
|
||||
"admission": {
|
||||
"kind": "full",
|
||||
"snapshotId": "snapshot-1",
|
||||
"worktrees": [
|
||||
{
|
||||
"displayName": "One",
|
||||
"repo": "Repo",
|
||||
"worktreeId": "w-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"client": "logical-client",
|
||||
"hostId": "host-1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"a87f1f91dc98": {
|
||||
"name": "worktree.ps#1",
|
||||
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.ps\",\"params\":{\"limit\":10000,\"afterSnapshotId\":null,\"supportsWorktreeVisibilitySourceDefaults\":true}}"
|
||||
@@ -153,10 +168,10 @@
|
||||
"sender": ["227f9e3de4fa"],
|
||||
"payloads": ["a87f1f91dc98"],
|
||||
"settlements": {
|
||||
"fetch": "9270aeb7d9c6"
|
||||
"fetch": "9948855e8b8d"
|
||||
},
|
||||
"state": "ab1a9ba6301c",
|
||||
"effects": ["2d40d1d38104"]
|
||||
"effects": []
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"baseline": "e7206f62a827f4fe0a2badf3eecd2167b8ac4285",
|
||||
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
|
||||
"recorderSha256": "e044443e01f282566a5e0eff110d0861d2a1047ca8d8696a2e7ff914f51957f3",
|
||||
"adapterSha256": "6119d409e1958877e4a04b3901f94a3609ac0420f5ca02af20eb8dae5cbf3408",
|
||||
"adapterSha256": "4e942ddfbaa0ba6bfc2993969276987f6528ac53765d125e99a830e261f93a8e",
|
||||
"scenarioSha256": "4749bb3b871275ba08f026f9b6bcfd383605f443e7bba70a7f89175b91db6fa5",
|
||||
"platform": "darwin",
|
||||
"scenarioVersion": 1,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"baseline": "e7206f62a827f4fe0a2badf3eecd2167b8ac4285",
|
||||
"lockfileSha256": "788c1234b38a61fc882fca292341fce6690deee9cc6695673b0575cf2c4cf571",
|
||||
"recorderSha256": "e044443e01f282566a5e0eff110d0861d2a1047ca8d8696a2e7ff914f51957f3",
|
||||
"adapterSha256": "6119d409e1958877e4a04b3901f94a3609ac0420f5ca02af20eb8dae5cbf3408",
|
||||
"adapterSha256": "4e942ddfbaa0ba6bfc2993969276987f6528ac53765d125e99a830e261f93a8e",
|
||||
"scenarioSha256": "2faa07ee5f12b3ed584117359d3b7aeb9c78a04e8fc49e753372f8c3927a3740",
|
||||
"platform": "darwin",
|
||||
"scenarioVersion": 1,
|
||||
|
||||
@@ -55,7 +55,8 @@ export function worktreeCatalogMountAdapters(
|
||||
fetched = result
|
||||
// Admitting is what advances the snapshot token a later poll sends back.
|
||||
admitted = snapshots.admit(result.kind === 'response' ? result.pending : null)
|
||||
return result
|
||||
// The pending catalog carries the live client, which the recorder cannot observe.
|
||||
return projectObservable(result)
|
||||
}),
|
||||
state: () => projectObservable({ fetched, admitted }),
|
||||
dispose: () => {}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { readdirSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readGolden } from './golden-recording'
|
||||
import { OBSERVATION_FIELDS } from './golden-value-pool'
|
||||
import type { Observation } from './recording-scenario'
|
||||
import type { RecordedValue } from './recording-values'
|
||||
|
||||
const root = resolve(import.meta.dirname, '../../../..')
|
||||
const directory =
|
||||
process.env.RPC_FOUNDATION_GOLDENS ?? resolve(root, 'mobile/rpc-foundation/goldens')
|
||||
|
||||
/** `captureValue`'s two refusals. Neither is ever product behaviour. */
|
||||
const PROJECTION_REFUSALS = [
|
||||
'Unsupported observation',
|
||||
'Observation requires an explicit projection'
|
||||
]
|
||||
|
||||
function refusalText(value: RecordedValue): boolean {
|
||||
if (typeof value === 'string') {
|
||||
return PROJECTION_REFUSALS.some((refusal) => value.includes(refusal))
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.some(refusalText)
|
||||
}
|
||||
return typeof value === 'object' && value !== null && Object.values(value).some(refusalText)
|
||||
}
|
||||
|
||||
function failures(at: string, observation: Observation): string[] {
|
||||
const found: string[] = []
|
||||
for (const field of OBSERVATION_FIELDS) {
|
||||
if (refusalText(observation[field])) {
|
||||
found.push(`${at}.${field}: recorder refused to project a value`)
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/**
|
||||
* A refused projection settles as data — the throw is captured as an effect and the action stays
|
||||
* `pending` — so `--record` writes it and the suite goes green over it. Two adapters shipped that
|
||||
* way (#20667, and the worktree catalog), and a revert plus a re-record would restore either one
|
||||
* silently. A detached rejection is not banned here: recording one is how a real main bug gets
|
||||
* pinned, and `unhandled-recording.test.ts` pins the capture itself.
|
||||
*/
|
||||
describe('recorder failures never reach a golden', () => {
|
||||
it('records no refused projection in any observation field', () => {
|
||||
const ids = readdirSync(directory)
|
||||
.filter((file) => file.endsWith('.json'))
|
||||
.map((file) => file.replace(/\.json$/, ''))
|
||||
|
||||
// Positive control: absence proves nothing unless the detector fires. `effects` carries the
|
||||
// shape both real defects took — the refusal captured as the message of a recorded error.
|
||||
const seeded: Observation = {
|
||||
sender: [],
|
||||
payloads: [],
|
||||
settlements: { fetch: { status: 'pending' } },
|
||||
state: { fetched: 'Observation requires an explicit projection for non-plain objects' },
|
||||
effects: [
|
||||
{ name: 'unhandled-rejection', value: { message: 'Unsupported observation: function' } }
|
||||
]
|
||||
}
|
||||
expect(failures('seeded', seeded)).toEqual([
|
||||
'seeded.state: recorder refused to project a value',
|
||||
'seeded.effects: recorder refused to project a value'
|
||||
])
|
||||
|
||||
let checkpoints = 0
|
||||
const found = ids.flatMap((id) =>
|
||||
readGolden(directory, id).recording.checkpoints.flatMap((checkpoint) => {
|
||||
checkpoints++
|
||||
return failures(`${id}/${checkpoint.id}`, checkpoint.observation)
|
||||
})
|
||||
)
|
||||
expect(found).toEqual([])
|
||||
expect(ids.length).toBeGreaterThan(0)
|
||||
expect(checkpoints).toBeGreaterThan(ids.length)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user