Skip to content

Onboarding v2: setup --json surface, grant-aware apply, device-grant pair cleanup - #349

Merged
cvince merged 8 commits into
feat/portabilityfrom
feat/portability-refactor
Aug 30, 2026
Merged

Onboarding v2: setup --json surface, grant-aware apply, device-grant pair cleanup#349
cvince merged 8 commits into
feat/portabilityfrom
feat/portability-refactor

Conversation

@cvince

@cvince cvince commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Lands the onboarding-v2 CLI line onto feat/portability. Contents: the docs/cli-setup-json.md contract implementation (capy setup --json plan / --confirm apply, --json sync, coded refusals, EXIT_NEEDS_INPUT), grant-aware project-key resolution on the apply path (a machine paired via the temporary device-key grant rides the same rail as capy run), pure-JSON-stdout fixes, and removal of dead pre-device-grant pairing machinery with a corrected pairCommand header.

Validation: full suite 1804 pass / 0 fail (exit 0); the end-to-end onboarding-v2 journey (Keep signup → WorkOS device approval → grant ceremony → MCP plan/confirm relay → encrypt → decrypt round-trip byte-match) passed against the local rig with this exact line.

cvince added 8 commits August 29, 2026 18:41
Completes what #328's harness repoint missed: pairE2E.int.test.ts and
fakePairingService.ts still spoke the old anonymous-bootstrap pairing
protocol, so both E2E tests failed at head (no /auth/device/authorize on
the fake service — no user code ever printed). Masked until now by the
CAP-601 exit-code leak that kept CI red-blind.

The fake service now serves the RFC 8628 device-grant surface
(/auth/device/authorize + /auth/device/token with a per-device queue)
alongside the authenticated owned-connection broker surface the CAP-384
grant ceremony drives, and the tests run the real two-phase flow: device
approval completes the session, the grant ceremony answers the key
material. Both load-bearing properties unchanged: the zero-durable-files
walk on the happy path, and coded PAIR_CODE_EXPIRED with exit 3 and
nothing written on expiry.

Validated: pairE2E 2/0, tsc clean, FULL suite 1854/0 with exit 0.
Onboarding v2 (docs/onboarding-v2.md) replaces the CLI-driven onboard flow
entirely: signup/org/custody now happen in Keep, and the hosted MCP onboard
tool does install + pair + plan/apply. The flow-driven onboard machinery this
CLI carried (driver, sandbox-session ceremony + worker, plan/apply/edits,
agentDocs/clis/copy/deployTargets/providers, and the hidden `capy onboard`
command with its --broker-ceremony/--ceremony-worker flags) is dead code.

Deletes the entire src/flows/onboard/** directory, src/commands/onboardCommand.ts,
the `onboard` command registration and CAPY_FLOW_ONBOARD gate in
src/index.ts/src/index-dev.ts, and every test that existed solely to cover
that code.

`codeForSilentAuthFailure` — the one thing `capy flow run` (checkout) genuinely
borrowed from the onboard executors — is relocated to a new src/flows/shared.ts
so checkout no longer imports the onboard layer at all. src/flows/client.ts's
`NextRequest.observations` field loses its dependency on the deleted
onboard/observe.ts in favor of a generic `FlowObservations` type; the class
itself (and its `FlowHttpError` export, used by the generic error screen)
is otherwise untouched.
…capyCommand

capyCommand.ts carried a parallel set of fields and branches
(assumeEncryptConsent, onProjectResolved, pinnedOrgId, flowProjectName,
noWizardStops, refuseWizardStop, CliOptions.brokerCeremony) that only the
now-deleted onboard executors ever set — initializeProjectForFlow,
bootstrapProjectForFlow, and syncForFlow were public entry points that
existed solely for src/flows/onboard/executors to call. All three, their
backing private fields, and every `if (this.noWizardStops)` /
`if (this.options.brokerCeremony)` branch they gated (the org/project/branch
pickers, the encrypt-consent prompt, the sync conflict menu, the silent-auth
fallback) are removed. Every removed branch sat ahead of the plain TTY/--web
`else` path in an if/else chain, so that path is unchanged — same prompts,
same wizard stops, same order.

webMode.ts's brokerCeremonyMode and onboardJsonMode were process-globals set
exclusively by onboardCommand.ts (now deleted) and read by errorScreen.ts's
displayErrorAndExit. Since nothing can ever set them again, the
isBrokerCeremonyMode()/isOnboardJsonMode() branches in displayErrorAndExit
collapse to their TTY/--web case unconditionally, and the two mode flags plus
their setters/getters are deleted. `human()` survives — capyCommand.ts's own
runInitialization/reconcileBranchConflict still call it — simplified to a
plain console.log now that the stderr-under-json-mode routing it existed for
is gone.

Test fallout: the describe blocks that existed only to test
initializeProjectForFlow/noWizardStops/brokerCeremony are deleted from
capyCommand.test.ts (the plain-TTY/--web sibling test in each block already
covers the surviving behavior); errorScreenBrokerCeremony.test.ts tested
setBrokerCeremonyMode exclusively and is deleted outright. ERROR_CODES loses
FLOW_STOP_UNREACHABLE (its only three throw sites are all in this diff) and
FLOW_ENVELOPE_INVALID (onboard/sandboxCeremony.ts's own code, deleted in the
prior commit).
…omEnvelope

pairCeremony.ts and pairingBrokerClient.ts were the CAP-409 pairing
ceremony that capy-cli#328/CAP-566 superseded with the RFC 8628 device-grant
rail (deviceAuth.ts/pairDeviceGrant.ts). pairCommand.ts already imports only
the device-grant modules — verified with grep before deleting — so nothing
in this build reaches runPairCeremony anymore. Their dedicated tests
(pairCeremony.test.ts, and pairingBrokerClient.test.ts which only that
ceremony imported) go with them. pairCommand.test.ts is untouched: it already
tests the current device-grant behavior and only mentions runPairCeremony in
a historical doc comment.

createOrganizationFromEnvelope (orgCreation.ts) was org creation from a
broker-ceremony sealed answer — its only caller was the now-deleted
sandboxCeremony.ts. Deleted along with its private helpers
(installOrglessThenScopedTokenProvider, codeForAuthFailure) and
runCannedCaseAEnrollment, which had no caller left once
createOrganizationFromEnvelope was gone (sandboxCeremony.ts was its other
caller). createNewOrganization — the TTY/--web org-creation path capyCommand.ts
actually uses — is untouched, including its own use of keyServiceOpsFromClient.
orgCreationFromEnvelope.test.ts tested nothing else and is deleted outright.
INVALID_RECOVERY_PHRASE (types/index.ts, previous commit) was this function's
own error code and had no other reference.
Implements the docs/cli-setup-json.md contract: setup plan as pure JSON on
stdout with a deterministic plan_hash, apply gated on the hash recomputed at
confirm time (PLAN_CHANGED on mismatch), root --json sync when keep.lock
exists, coded refusals (EXIT_NEEDS_INPUT) for anything that would prompt.
New narrow code path beside the untouched TTY/--web wizard; installGitHooks
extracted byte-for-byte from CapyCommand for reuse by the apply path.
Tests registered at the isolated-files append marker.
A machine paired via the temporary device-key grant holds no local
key.enc/K_local — its key material lives behind the grant daemon — so the
apply's resolveProjectKey refused PERMISSION_DENIED on the fresh-paired
machine's first confirm. Fall back to the same grant rail capy run rides
(fetchGrantedKLocal + resolveProjectKeyFromGrant) when the socket is
configured; grant failures keep their own coded errors. Found live by
journey run 14.
backupPlaintextEnv printed its notice unconditionally, leaking one prose
line ahead of the confirm result and tripping the pure-JSON-stdout law
(journey run 15 — the apply itself succeeded, secrets written). Additive
quiet flag; the TTY init path prints byte-identically.
…d header

pairCommand.ts's header described the demolished anonymous-bootstrap
ceremony; rewritten to match the live device-grant design. pairContract.ts
loses PAIR_PURPOSE/PAIR_FLOW/PAIR_CEREMONY, parsePairPayload and its
validators (zero live importers — verified); PairMachineAnswer* types stay
(deviceAuth/installPairedSession/pairKeyMaterial still import them). The
test file covering only the removed parser goes with it. Scoped cleanup
from CAP-608 recon; drafted by the recon agent, applied post-journey-green.
@cvince
cvince merged commit 128ae0c into feat/portability Aug 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant