fix(web): connect refusals reach the caller - #344
Conversation
The last file in the refusal class, and I was wrong that it needed a decision.
I had reported connect as not-contained because `execute` returns
Promise<{ linked: boolean }> and recurses into itself, so wrapping it in a
handler that returns `never` would change the contract. That was the wrong
shape to reach for. rotateCommand already documents the right one, and it does
not wrap anything:
`await refuse(...); return;` at every call site, and the `return` is not
decoration: displayErrorAndExit is Promise<never> but TypeScript does not
narrow across an awaited never
Called directly and followed by `return { linked: false }`, the signature is
untouched and the recursion is untouched. Three refusals converted:
dev-mode live firewall (x2) PERMISSION_DENIED
unknown provider INVALID_FORMAT
The unknown-provider one is the reachable-under-web case: the browser picker is
offered first, and this is where the run ends when the picker is declined or
cannot be shown. It ended on a stream with nobody on it.
The firewall sites are dev-binary only, but `capy-dev --web --live` reaches
them, so they get the same treatment. Nothing about the refusal itself is
weakened — same condition, same exit code, one more surface it reaches.
capy-cli now has zero bare `console.error` + `process.exit(1)` refusals in the
command layer that are reachable under --web.
Suite: 1851 pass, 0 fail.
Status, since chat replies are not arrivingYou read #343's diff in detail, so PR content reaches you. Chat does not. Posting here instead.
|
| file | bare exits reachable under --web |
|---|---|
| kick, org, transport, edit, connect | 0 |
| recover | 0 reachable — 5 remain after if (options.web) return, unreachable by construction |
| deployTokenCommand | 2 remain, both double-print cases (below) |
Merged: #334, #335, #338, #339, #340, #341, #342, #343. This PR (#344) is connect, the last one.
Skip paths
checkoutCommand:377-398— done in fix(web): two skip paths that completed without ever reaching a browser branch #343capyCommand:262-309— done in fix(web): two skip paths that completed without ever reaching a browser branch #343 (needed no signature change;this.options.webwas already on the instance)orgCommand:253-256— done in fix(web): route org and transport refusals through the error screen #335endRecoverCommand:83-86— not done, and I will not do it unilaterally.WebEndRecoverParams.sessionis REQUIRED by design; the type comment says a page served without one "would let a click unlink files the terminal form would never have touched — and every row on it arrives ticked, which makes that the DEFAULT answer rather than an opt-in." Serving it satisfies the URL rule by reintroducing a plaintext-deletion bug. It needs an informational screen (no rows, nothing destructive) through workbench with approved copy.
Genuinely remaining
Four double-print cases — 2 in inviteCommand (role-grant refusals), 2 in deployTokenCommand (mintOrExit after spinner.fail, ambiguous-prefix concatenation). Each already has its message on screen before the exit, so converting prints it twice. Folding the existing output into the throw is a copy decision, not a mechanical edit.
Two things, one of them a correction to my own code in #344. PROVIDER_NOT_FOUND. I used INVALID_FORMAT for an unknown provider, which is wrong: that is a not-found condition, not a malformed one, and the remedies differ — "pick from the list" versus "correct your syntax". A caller branching on the code cannot tell those apart, and the code is the contract; that is the whole reason we do not parse the prose. Minted at the point the condition is first known, per the rule KEY_NOT_ON_DEVICE's own comment states, and alongside the existing not-found family. push. Found by RUNNING commands under --web with no TTY rather than reading them: `capy push --web` refused with exit 1 and no URL anywhere. It was on nobody's list. Both its guards now throw into the catch that execute() already routes to displayErrorAndExit. push takes no `web` option and needs none — that function reads web mode itself. Verified by running, not inferred: $ capy push --web # clean HOME exit code: 1 url line : http://127.0.0.1:60064/s/G5BjAmn9Owi7-uZY9PR3pPqnL85M8tIprJ96XX3qne0 Before this it printed the refusal and nothing else. Suite: 1851 pass, 0 fail. A first run exited 1 on tests/auth/authServiceKeepScreens.test.ts; that file passes in isolation on this tree and a second full run was clean, so it is order-dependent flake, not this change — the diff touches only push, connect and the code table.
The last file in the refusal class — and I was wrong that it needed a decision.
I reported
connectas not-contained becauseexecutereturnsPromise<{ linked: boolean }>and recurses into itself, so wrapping it in a handler returningneverwould change the contract. That was the wrong shape to reach for.rotateCommandalready documents the right one, and it wraps nothing:Called directly and followed by
return { linked: false }, the signature is untouched and the recursion is untouched.PERMISSION_DENIEDINVALID_FORMATThe unknown-provider case is the one reachable under
--web: the browser picker is offered first, and this is where the run ends when the picker is declined or cannot be shown — previously on a stream with nobody on it.The firewall sites are dev-binary only, but
capy-dev --web --livereaches them, so they get the same treatment. Nothing about the refusal is weakened: same condition, same exit code, one more surface it reaches.capy-cli now has zero bare
console.error+process.exit(1)refusals in the command layer that are reachable under--web.Suite: 1851 pass, 0 fail.