flows: onboard write phase reaches done — project pin, adopt auth, first-push auto-resolve, json purity - #348
Merged
Merged
Conversation
…reate A resumed onboard run (--flow-id/--flow-secret --broker-ceremony) never calls create, so its own minted keypair had no way to reach the service. Send client_pubkey on every /next report while a broker-ceremony keypair is held; the create-path resend is the documented idempotent no-op there. Surfaces the service's coded 409 (client pubkey conflict) as a fatal, coded failure on the --broker-ceremony --json path instead of collapsing it to a generic service error.
… leaked across files in one bun process runOnboardCommand sets brokerCeremonyMode/onboardJsonMode as process globals and never unsets them (a real CLI is one process per run). Under bun test's shared process, tests exercising the broker-ceremony path left brokerCeremonyMode=true, and errorScreen's isWebMode() && !isBrokerCeremonyMode() gate then silently refused to serve loopback pages for every later --web test in the same run. Reset both flags unconditionally in this file's shared restore(), which every test already calls. No test expectations changed. Full suite: 1851 pass / 0 fail.
A --flow-id/--flow-secret resume skips create entirely, so a flow minted remotely (no local directory at mint time) reached its confirm/onboard_plan step with target_dir/variable_count/plan_hash all empty: the service only ever gets plan facts from a client-reported body.plan, and the resume path never sent one. The driver now sends its own already-computed plan (the same value the self-mint path already puts in the create body) once, on a resumed run's first next report, falling back to buildPlan() when no plan was pre-computed. The service's existing plan-replacement path treats this the same way it treats any plan report: a no-op when the hash already matches, so later reports on the same run send nothing more.
…emony create_org now mints personal orgs through the mint-ceremony rail (ServiceClient.mintPersonalOrgCeremony) instead of /auth/create-org: the endpoint creates the org and claims this device's key-mint lease in one round trip, name-collision suffixing moves server-side (the CLI-side retry loop is gone), auth is re-scoped to the new org via authenticateSilent, and the lease is finalized (finalizeKeyMintOrThrow) only after the local key is saved — a finalize conflict is now a coded step failure, never swallowed into a false success. 409 ALREADY_PROVISIONED arrives as a typed CapyError. New first_run.kind:'mint_key' sealed-answer kind covers an org that exists but never had its first master key minted: pin org, claim the lease, derive+wrap M, run the same canned Case A device-key enrollment create_org uses, then finalize with the same never-swallow semantics. sandbox_session steps gain an optional mint_org_id param, threaded through prepareCeremonyScreen -> the detached ceremony worker's stdin payload -> its own buildCeremonyUrl call, so the fragment jumps straight to the mint rail for an already-known org. Absent params stay byte-identical to the prior fragment shape. Additive to the vendored flow contract (sandbox_session.params_schema gains mint_org_id, optional) — sync-flow-contract.ts --check will flag this vendored copy as hand-edited until shared/flows/steps.json in the monorepo (outside this submodule's scope) gets the same property and the sync script is re-run for real.
…tderr capture parse-failure debug() logs SHAPE only (type/keys/kind, never content); CAPY_CEREMONY_LOG routes the detached worker's stderr to a file so the breadcrumbs survive the parent's exit. Unset env = byte-identical.
… moves forward WorkOS refresh tokens are single-use. save() persisted the whole in-memory session, so an instance that never rotated could write its stale loaded token over a newer rotation another instance persisted — next invocation died on invalid_grant with no recovery. Observed live twice in the mint ceremony worker chain. Guard: track the loaded baseline; a non-rotating save re-reads the store and preserves its token (adopting it as the new baseline), while a genuine rotation still advances. Three tests pin clobber-prevention, adopt-then-rotate, and rotate-wins.
…-push auto-resolve, json-mode purity Four fixes proven live on a real broker-ceremony flow (validated to kind:done with an encrypted .env and a decrypt round-trip): 1. create_org ceremonies now report result.project_id (threaded from mint-ceremony through the worker marker and driver report), so the service pins the project the mint provisioned and write_keep_lock adopts it instead of dead-ending on the project picker. 2. bootstrapProjectForFlow settles the command's own auth service before bootstrapExistingProject — the adopt path's co-decrypt previously went out with no Authorization header at all. 3. The sync menu's sole-action fast path now also fires under brokerCeremony (State 6 first push: one real action is not a decision); TTY and --web keep their prompts byte-identical. 4. The adopt stub-project lines route through human() so --json stdout stays exactly one JSON envelope. Also refactors applyFirstRun/runCeremonyWorker to let-free early-return form per house immutability rules; behavior preserved, suites green.
…marker path is organically unreachable
Headless journey runs 6 and 7 both hit the same wall: pinned_org_id set,
pinned_project_id empty, write_keep_lock dead-ended on FLOW_STOP_UNREACHABLE.
Root cause (proven from the run-7 service log): the marker→driver-report leg
requires a later driver run to RE-ENCOUNTER the sandbox_session step, and
organically it never does — the worker's own session-store write flips the
next derivation's sessionLive observation, the service (correctly) skips
authenticate and derives past the satisfied ceremony screen, and the settled
marker is never consumed. The service pins the org off the bearer's claim
but never learns the project the mint provisioned.
The worker now POSTs the settled step to /flows/:id/next itself, right after
writing the marker: real flow/step ids ride the spawn payload, the report
carries result.{org_id, project_id}, the flow secret, the client pubkey, and
the org-scoped bearer the ceremony just settled (exactly what
projectPinnableBy requires). Best-effort — on any failure the marker remains
and the driver's consume-and-report path (unchanged) still covers every
sequencing where the step is re-encountered. Payload ids are optional for
back-compat; absent, behavior is byte-identical to before.
ceremonyWorker suite asserts the report wire shape (ids, result, secret,
bearer); 16/0, adjacent suites green, tsc clean.
…un test process (#347)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the gap between a completed sandbox ceremony and a finished onboard flow. All four fixes were proven live on a real broker-ceremony flow driven to
kind:done, with.envciphertext at rest and a decrypt round-trip throughcapy run.create_orgceremonies now carryresult.project_id(mint-ceremony response → worker marker → driver step report), so the service pins the project the mint provisioned andwrite_keep_lockadopts it instead of dead-ending on the adopt-vs-create picker under--broker-ceremony.bootstrapProjectForFlowsettles the command's own auth service first — the adopt path previously issued co-decrypt with no Authorization header.brokerCeremony(a menu with one real action is not a decision). TTY and--webprompts are byte-identical to before.--jsonpurity: the adopt stub-project lines route throughhuman()so stdout stays exactly one JSON envelope.Includes the let-free refactor of
applyFirstRun/runCeremonyWorkerper house immutability rules. Suites: sandboxCeremony 42/0, ceremonyWorker 16/0, driver 42/0, executors 33/0, orgCreationFromEnvelope 11/0, capyCommand 69/0; tsc clean.