mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 16:02:43 +00:00
* test(mobile): record main's github.* PR and diff-review loaders before migrating them
Scenarios and goldens for the step-4 `src/session/` first half, recorded
against main's unmigrated product code so the migration that follows has a
frozen parity oracle instead of an assertion.
- 20 scenarios over seven new families: the seven `github.*` PR reads, the
twelve PR mutations split by their three reply contracts (`{ok}` envelope,
bare boolean, slug-addressed comment edit), the triage createTerminal+send
launch, the PR branch-context chain and the review screen's three loaders.
- Two new sender-style mount adapters. Both mount exported async functions
taking a client, so no React host is needed and the recorded state is each
wrapper's own outcome.
- 50 new goldens: 20 pilot, 30 reply-matrix sites. `recorderSha256` moved on
all 153 existing goldens because the adapters are in the whole-recorder
digest; no other line in any of them changed.
Text diffs are deliberately unscripted: highlighting one reaches `lowlight`,
which the module loader refuses as an unspecified native dependency.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): send the github.* PR surface and the review loaders through RpcOperation
The step-4 first half for `src/session/`: eight files, 38 references to the raw
request port, all replaced with declared operations. No behaviour change — the
50 goldens recorded in the previous commit do not move, which is the claim.
- 21 operations over 21 methods. The seven PR reads keep their defensive
parsers as readers; the ten status-envelope mutations share one reader
because the `{ok, error}` convention is one host convention, not ten; the two
bare-boolean mutations read the payload unchecked because `=== true` is the
caller's confirmation rule.
- Four second readers, each justified in place: git.status and git.branchCompare
for the PR branch context (a refusal costs a fallback, not the screen),
git.branchCompare and git.branchDiff for review (the projection is not a
superset of the verbatim payload), and worktree.show for the review notes the
summary reader drops.
- Every failure text is preserved, including the two main kept apart: a refusal
with no message falls back to the screen's copy, a transport drop with no
message surfaces its empty message verbatim. `sendRaw`'s callers replaced
theirs a second time, so those fall back on both paths.
- No retry, and no operation reads a dropped reply as a failed mutation: the
rejection reaches each wrapper's catch as the original object.
- `github-pr-mutations.ts` split along the action/comment seam it already had
in its consumers, so no file needs a max-lines bump.
Inventory: src/session/ 47 files / 114 references -> 39 / 76.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): record the review snapshot answering its notes leg first
The barrier mutation census found one survivor: moving
`reviewWorktreeMetadataRead.interpret` inside the `Promise.all` in
`loadMobileDiffReviewSnapshot` changed nothing any golden observed. The base
scenario answers the branch-base legs before the notes leg, so by the time the
notes reply lands the compare leg has already sent `git.branchCompare` and the
two orders record the same sender list.
This scenario answers the notes leg first, while the compare leg is still
resolving its base ref, and checkpoints before the rest. At that checkpoint the
barrier is the whole difference: the correct order has nothing settled, the
early interpretation has already rejected the action. The mutation now fails it.
Recorded from a detached checkout of the previous commit, which carries main's
unmigrated product code with this branch's recorder over it, so the parity claim
stays non-circular. One new golden; no existing golden moved, because the family
base is unchanged and `scenarioSha256` is per golden.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): bind one git.status projection reader, not a copy per domain
The branch-context read declared its own `statusProjectionReader` with the same
parser, the same 'normalized-status' variant and the same empty salvage as
source-control's `gitStatusProjectionReader`, while its doc block claimed "one
reader serves both". Export the source-control reader and bind it here so the
claim is true; the doc now names the reader and keeps the part that is actually
different, which is what a refusal means on each policy.
No wire change and no golden moves: the reader is the same function value the
copy computed.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): undo the github-pr-mutations split, which max-lines no longer forces
The split was made when the migrated file measured 319 lines. It does not any
more: `sendRaw`, `sendGithubPrMutation` and `extractMutationError` moved to
github-pr-mutation-outcome.ts and the prRepo/headSha allow-lists to
github-pr-repo-slug.ts, so the merged file is 293 lines against the 300 limit
and oxlint is clean.
Nothing imported github-pr-comment-mutations directly — every consumer went
through the re-export hub in github-pr-mutations — so the seam bought a reader
one extra file to open and nothing else. Merge it back and drop the hub.
Product-only: same wrappers, same params, same settle shapes, no golden moves.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): one settleable-operation type for the PR reads and mutations
`GitHubPrMutationOperation` and the private `GitHubPrReadOperation` declared the
same two members for the same reason: a settle shape needs a bound operation's
method and its interpret, nothing else. Keep one, `GitHubPrSettleableOperation`,
and import it into the read settle. `extractMutationError` goes back to private,
as it was on main; it never had an importer outside its own file.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* docs(mobile): drop the key-order claim from the PR param builder
The oracle does not observe param key order: `captureValue` in recording-values.ts
sorts keys, and no golden carries a raw frame string, so "the sender recordings
pin the bytes" was not a fact the evidence supports. The assertion stays for the
reason already in the doc — the builder is method-generic and returns a record.
`GitHubPrParamOptions` goes back to private; nothing outside the module names it.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): read the bare-boolean mutations with the shared unchecked reader
`mutationConfirmationReader` spelled out what `rpcUncheckedPayloadReader` already
returns, under the same 'pr-mutation-confirmation' variant that eleven other
operations in this tree get from the helper. Same function value, same variant,
so no golden moves. The comment explaining why the payload is left unread stays.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): one RpcOperationSender for both domains, not one alias each
`MobileSessionRpcSender` and `MobileSourceControlRpcSender` were the same type
with the same doc, each derived from whichever operation its domain happened to
own. Replace both with `RpcOperationSender` in transport, derived from
`settingsRead` there, and name it for what it is: what a bound operation needs
to send with.
Still derived rather than restated, so no module names the raw request port to
accept a client; the port inventory and its ratchet are untouched.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): point the moved PR and diff-review adapters at the seam and register them
The merge commit carried the two adapter files into adapters/ with their old
specifiers and left the register untouched, so this completes the move: the
relative imports climb one more level, and both modules are registered in
adapters/mounted-operation-modules.ts as identifiers imported from their own
source, which is what adapter-seam.test.ts checks.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): re-record the session goldens against #20662's adapter seam
The merge brought #20568's per-golden scenario digest and #20662's per-golden
adapter digest, so the 51 goldens this PR owns move on four header fields and
nothing else: baseline, goldenFormatVersion, recorderSha256, and the newly
added adapterSha256. No recorded byte outside those headers changed.
baseline stays at main's own pin c6a7216984
rather than moving to 6a11a0b8e6. Repinning rewrites the baseline line in all
208 goldens main owns, which this branch must leave byte-identical. Recording
at either commit produces identical bytes everywhere except that one line, so
the pin costs no coverage.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
129 lines
4.7 KiB
TypeScript
129 lines
4.7 KiB
TypeScript
import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc-operation'
|
|
import type { RpcMethodName } from '../transport/rpc-params-contract'
|
|
import type { RpcCompatibleReader } from '../transport/rpc-operation-contract'
|
|
import {
|
|
rpcPayloadMember,
|
|
rpcReadUnchecked,
|
|
rpcUncheckedPayloadReader
|
|
} from '../transport/rpc-reader-payload'
|
|
|
|
// Host-state changes on the `github.*` PR surface. A lost reply here is *unknown*, never failed:
|
|
// none of these operations interprets a transport rejection, so the rejection object — and the
|
|
// delivery-unknown mark the WeakSet holds on it — reaches the wrapper's own catch intact. The
|
|
// wrappers still collapse it into their `{ ok: false }` outcome, exactly as main did; nothing here
|
|
// retries, and no operation below treats a dropped reply as evidence the mutation did not happen.
|
|
|
|
/**
|
|
* What a PR mutation reported in-band. `structured: false` is the host returning void or a bare
|
|
* value with no `ok` member, which every caller has always read as success.
|
|
*/
|
|
export type GitHubPrMutationStatus =
|
|
| { readonly structured: false }
|
|
| { readonly structured: true; readonly ok: unknown; readonly error: unknown }
|
|
|
|
/**
|
|
* One reader for ten methods, not ten readers.
|
|
*
|
|
* The `ok in result` test and the `error` read are a single host convention — GitHubProjectMutation
|
|
* -Result and GitHubCommentResult share it — so there is no input on which two of these methods
|
|
* would want different answers. Which failure text a caller shows is the caller's, not the
|
|
* reader's: `extractMutationError` still names the method in its fallback.
|
|
*/
|
|
const mutationStatusReader: RpcCompatibleReader<
|
|
unknown,
|
|
'pr-mutation-status',
|
|
GitHubPrMutationStatus
|
|
> = (raw) =>
|
|
raw && typeof raw === 'object' && 'ok' in raw
|
|
? rpcReadUnchecked('pr-mutation-status', {
|
|
structured: true,
|
|
ok: raw.ok,
|
|
error: rpcPayloadMember(raw, 'error')
|
|
})
|
|
: rpcReadUnchecked('pr-mutation-status', { structured: false })
|
|
|
|
// Ten operations, one definition site: they share a method-independent acceptance, barrier and
|
|
// reader, and writing the same five lines ten times would hide that rather than show it. Name and
|
|
// method stay per operation, which is what a call site picks.
|
|
function mutationStatusOperation<Method extends RpcMethodName>(name: string, method: Method) {
|
|
return bindDeferredRpcOperation(
|
|
defineRpcOperation({
|
|
name,
|
|
method,
|
|
acceptance: 'require-result-or-throw-message',
|
|
barrier: 'after-caller-barrier',
|
|
read: mutationStatusReader
|
|
})
|
|
)
|
|
}
|
|
|
|
export const githubPrMergeRun = mutationStatusOperation('github.merge-pr', 'github.mergePR')
|
|
|
|
export const githubPrAutoMergeSet = mutationStatusOperation(
|
|
'github.set-pr-auto-merge',
|
|
'github.setPRAutoMerge'
|
|
)
|
|
|
|
export const githubPrStateSet = mutationStatusOperation(
|
|
'github.update-pr-state',
|
|
'github.updatePRState'
|
|
)
|
|
|
|
export const githubPrReviewersRequest = mutationStatusOperation(
|
|
'github.request-pr-reviewers',
|
|
'github.requestPRReviewers'
|
|
)
|
|
|
|
export const githubPrReviewersRemove = mutationStatusOperation(
|
|
'github.remove-pr-reviewers',
|
|
'github.removePRReviewers'
|
|
)
|
|
|
|
export const githubPrChecksRerun = mutationStatusOperation(
|
|
'github.rerun-pr-checks',
|
|
'github.rerunPRChecks'
|
|
)
|
|
|
|
export const githubPrReviewCommentReplyAdd = mutationStatusOperation(
|
|
'github.add-pr-review-comment-reply',
|
|
'github.addPRReviewCommentReply'
|
|
)
|
|
|
|
export const githubPrIssueCommentAdd = mutationStatusOperation(
|
|
'github.add-issue-comment',
|
|
'github.addIssueComment'
|
|
)
|
|
|
|
export const githubPrIssueCommentEdit = mutationStatusOperation(
|
|
'github.update-issue-comment-by-slug',
|
|
'github.project.updateIssueCommentBySlug'
|
|
)
|
|
|
|
export const githubPrIssueCommentDelete = mutationStatusOperation(
|
|
'github.delete-issue-comment-by-slug',
|
|
'github.project.deleteIssueCommentBySlug'
|
|
)
|
|
|
|
// The two mutations whose host result is a bare boolean rather than a status envelope. Their
|
|
// payload is unread here on purpose: `=== true` is the caller's confirmation rule, and reading it
|
|
// as a status would turn a `false` into the "no structured status" success the envelope methods get.
|
|
export const githubPrTitleSet = bindDeferredRpcOperation(
|
|
defineRpcOperation({
|
|
name: 'github.update-pr-title',
|
|
method: 'github.updatePRTitle',
|
|
acceptance: 'require-result-or-throw-message',
|
|
barrier: 'after-caller-barrier',
|
|
read: rpcUncheckedPayloadReader('pr-mutation-confirmation')
|
|
})
|
|
)
|
|
|
|
export const githubPrReviewThreadResolve = bindDeferredRpcOperation(
|
|
defineRpcOperation({
|
|
name: 'github.resolve-review-thread',
|
|
method: 'github.resolveReviewThread',
|
|
acceptance: 'require-result-or-throw-message',
|
|
barrier: 'after-caller-barrier',
|
|
read: rpcUncheckedPayloadReader('pr-mutation-confirmation')
|
|
})
|
|
)
|