# Developer takeover

Developer takeover pauses an unfamiliar-page failure without discarding the
browser, profile, Runner slot, or saved workflow progress. It is opt-in per job.
It is not permission to retry input or record a state automatically.

```ts
const job = await tapi.workflows.run({
  sessionId,
  versionId,
  inputs: { stock: "TSLA" },
  allowDeveloperTakeover: true,
  takeoverContext: {
    appLabel: "RSA",
    returnUrl: "http://127.0.0.1:5177",
  },
});
```

`sessions.prepareForWorkflow()` accepts the same takeover options. Supply
`takeoverContext.inputs` and `takeoverContext.checkpoint` when preparation itself
can fail before a job exists. Return URLs must be loopback HTTP(S) URLs.

## Workflow-owned continuation

Every workflow receives `resume` and `checkpoint(value)`. A checkpoint is an
explicit safe restart boundary, not a JavaScript stack snapshot. Store JSON
progress only, up to 64 KiB. Never store a password, verification code, or token.

```ts
async run({ session, input, resume, checkpoint }) {
  const completed = Array.isArray(resume?.completed) ? resume.completed : [];
  await checkpoint({
    stock: input.stock,
    completed,
    resumeStateIds: ["s4"],
  });
  // Perform one unit of work, then save its completion before starting another.
  // On continuation, skip completed units and inspect the current page again.
}
```

Checkpoints are written by the active workflow grant and stored with its job.
They are copied into the takeover request when recognition reaches its deadline
on an unknown, ambiguous, or unexpected state. A missing expected text on an
otherwise expected state does not automatically offer developer takeover.

## App and Studio flow

1. Read `tapi.takeovers.list({ mapId })` to restore outstanding handoffs after an
   app restart. `get(takeoverId)` returns current capabilities and status.
2. Show **Take over in Studio**. Generate a fresh `studio-window:<uuid>` for the
   destination tab. `present(takeoverId, { windowId })` targets that exact tab;
   other Studio windows cannot claim its invitation. Include `tapiWindowId` and
   `tapiTakeoverId` in its Studio URL. A local app may launch
   Studio with `tapi studio dev --workspace ... --tapp ... --no-open` first.
3. Studio registers its persistent window and controller IDs, calls `accept()`
   with `windowId`, `presentationId`, `controllerId`, and `expectedVersion`, and
   waits for the returned control operation. The old
   job must have relinquished control first. No new browser is allocated.
   A newly targeted window may explicitly transfer an active takeover; the
   control epoch advances and the old window can no longer send browser input.
   Stable and browser input remain disabled until `controlReady` is true for the
   same controller. Refreshing the target tab preserves its window identity.
4. The developer demonstrates actions and explicitly commits or discards their
   recordings. Creating a state does not automatically teach workflow code how
   to handle it.
5. Studio observes the current page and calls `finish()` with that observation,
   its control epoch, and the expected map revision. The state must be in the
   checkpoint's `resumeStateIds`. Studio then waits for the return operation.
6. The app prepares a workflow version pinned to `takeover.resumeMap`, then calls
   `resume(takeoverId, { expectedVersion, versionId, requestId })`. Run a normal
   workflow host for the returned job. The new job receives the saved checkpoint
   as `resume`; the old job is not restarted.

`cancel(takeoverId, { expectedVersion })` ends the handoff reservation without
closing Chromium or deleting profile data. All mutating handoff methods require
the current request version. Control epochs fence stale browser writers.

## RSA behavior

RSA offers takeover by default, with a per-lookup checkbox. Before account work,
the supported return states are S0 through S4, including the code-entry state.
During the account loop, the only return boundary is S4. Completed accounts are
saved and skipped. An unfinished account restarts at S4, never in the middle of
quantity or review input. RSA continues to review and cancel; this feature does
not enable order placement. Its embedded live view remains read-only.

The takeover banner survives refreshing or restarting RSA because Railway owns
the request and checkpoint. The desktop workflow host still has to be running
to execute a job; this mechanism is not a general host-crash recovery service.
