Part VIII · SynthesisSix complete traces
Chapter 39source checked

Six complete traces: ownership, convergence, and failure boundaries

Six source-grounded paths connect the book's modules end to end: a local turn, a T3 Connect mobile turn, an approval, an offline outbox drain, a checkpoint diff and revert, and an exact-version service update. Each trace names the owner of every handoff and the point where the observed implementation stops promising convergence.

What this chapter resolves
  • Trace six representative operations across client, relay, domain, side-effect, and projection boundaries.
  • Distinguish a durable fact from a best-effort handoff, a cached intention, and a completed external effect.
  • Reuse the owning chapters without flattening their separate authorization, recovery, and update protocols into one story.
  • Identify the exact failure boundary for each trace before treating a local design choice as a general guarantee.

This chapter is a synthesis, not a new seventh protocol. It puts already-audited pieces on one time axis so that the ownership changes are visible: a device can record intent, a relay can authorize connection setup, an environment can commit a fact, and a provider, Git driver, or stable launcher can perform a later effect. Those verbs are deliberately not interchangeable.

The six traces are chosen because they exercise different seams. They do not combine into a global exactly-once guarantee. In particular, a server command may commit before a hot reactor observes it; a phone file and a server database have no shared transaction; a checkpoint revert can mutate files before provider rollback; and a prepared service runtime is not committed until its independent launcher says so.

The common reading key: trigger, owner, durable fact, convergence boundary

Every trace below has four questions:

  1. What triggered work? A user action, provider callback, queued file, or release workflow begins the path.
  2. Which owner may act next? An owner can pass an intent or fact onward, but does not automatically own the recipient’s state machine.
  3. What became durable, where? A committed orchestration event, mobile outbox file, hidden Git ref, or launcher state has a scope. It is not universal proof.
  4. What converges—or stops? Snapshots and subscriptions converge client views; some other paths terminate at an honest failure activity, retry policy, or manual recovery boundary.
Figure 39.1 · Six traces share handoffs, not one universal delivery contractRead each horizontal band left to right. The repeated lanes are synchronized by the selected trace, while the owner and guarantee change at each handoff.
Six synchronized end-to-end T3 Code trace swimlanesDiagram loading

Zoom with the controls, +/, or Ctrl/ + trackpad scroll. Enable Pan to drag, use two-finger scrolling, or use the arrow keys. 0 fits the diagram; Esc leaves Pan or expanded view.

Six synchronized end-to-end T3 Code trace swimlanes
Text equivalent

Across six horizontal trace bands, the lanes are release automation when relevant, device or client, relay if present, environment command and projection state, external worker such as provider, Git, or launcher, and durable stores. A local turn commits before provider work. Relay setup returns endpoint and bootstrap material to the phone, then ends before direct client-to-environment traffic. An approval persists request and response intents around a provider callback that can later reject a stale request. An offline outbox file waits for synchronized shell evidence before remote dispatch; the command result triggers local removal and snapshots reconcile afterward. A checkpoint creates a hidden Git ref before durable checkpoint metadata; revert first commits a request, can stop after file restore, and commits completion only through the engine. Release automation publishes an exact package and exposes a client before that client requests a server update; the stable launcher commits only a prepared trial.

Figure 39.1. The first band shows a local turn through the environment. The second uses the relay only to authorize, mint, and return a bootstrap for a direct mobile-to-environment session. The third records an approval response before a provider callback can succeed or fail. The fourth drains a durable mobile intent after a live-shell check, then removes it on the command result while projections reconcile separately. The fifth captures and reverts a hidden Git checkpoint through a durable request and a later engine completion. The sixth has release automation publish and expose an exact runtime before a separate connected client requests an update. Dashed paths are deliberately weaker boundaries, not omitted implementation detail.

Trace 1 — local first turn: commit intent before provider work

An existing-thread turn begins in a local, web, desktop, or mobile renderer. The client sends the typed command through an authenticated RPC method; the server normalizes the external input, serializes a decision through its command queue, and commits the emitted event, read-model work, and accepted receipt in one SQLite transaction. That transaction is the durable acceptance boundary.

Only after that commit does ProviderCommandReactor observe the committed intent and ask ProviderService to establish or continue the provider session and send the turn. The provider adapter maps native notifications to the canonical runtime union. Runtime ingestion turns those facts back into internal commands, whose resulting projections flow to a client through an HTTP snapshot and resumable subscription. The detailed command-to-checkpoint path belongs to Chapter 4, with the lifecycle guards expanded in Chapter 23.

apps/server/src/orchestration/Layers/OrchestrationEngine.ts:197–259 ↗verbatim · typescript · 24e00da0
        const committedCommand = yield* sql
          .withTransaction(
            Effect.gen(function* () {
              const committedEvents: OrchestrationEvent[] = [];
              let nextCommandReadModel = commandReadModel;
 
              for (const nextEvent of eventBases) {
                const savedEvent = yield* eventStore.append(nextEvent);
                nextCommandReadModel = yield* projectEvent(nextCommandReadModel, savedEvent);
                yield* projectionPipeline.projectEvent(savedEvent);
                committedEvents.push(savedEvent);
              }
 
              const lastSavedEvent = committedEvents.at(-1) ?? null;
              if (lastSavedEvent === null) {
                return yield* new OrchestrationCommandInvariantError({
                  commandType: envelope.command.type,
                  detail: "Command produced no events.",
                });
              }
 
              yield* commandReceiptRepository.upsert({
                commandId: envelope.command.commandId,
                aggregateKind: lastSavedEvent.aggregateKind,
                aggregateId: lastSavedEvent.aggregateId,
                acceptedAt: lastSavedEvent.occurredAt,
                resultSequence: lastSavedEvent.sequence,
                status: "accepted",
                error: null,
              });
 
              return {
                committedEvents,
                lastSequence: lastSavedEvent.sequence,
                nextCommandReadModel,
              } as const;
            }),
          )
          .pipe(
            Effect.catchTag("SqlError", (sqlError) =>
              Effect.fail(
                toPersistenceSqlError("OrchestrationEngine.processEnvelope:transaction")(sqlError),
              ),
            ),
          );
 
        commandReadModel = committedCommand.nextCommandReadModel;
        for (const [index, event] of committedCommand.committedEvents.entries()) {
          yield* PubSub.publish(eventPubSub, event);
          if (index === 0) {
            yield* Metric.update(
              Metric.withAttributes(
                orchestrationCommandAckDuration,
                metricAttributes({
                  ...baseMetricAttributes,
                  ackEventType: event.type,
                }),
              ),
              Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs)),
            );
          }
        }
        return { sequence: committedCommand.lastSequence };
Read this as: One command transaction persists accepted event data, projections, and idempotency receipt before publishing the committed result.

The decisive limitation is after the commit: the reactor consumes a hot stream, not a durable outbox. A crash in the narrow window after the transaction but before the provider reactor handles the event does not automatically replay a missing send on restart. Retrying the same command finds the accepted receipt and does not create a new event. This is intentionally safer than duplicating the durable fact, but it is not a proof that every accepted turn reached a harness.

Trace 2 — relay-connected mobile turn: launch through the relay, work directly with the environment

A relay-connected phone first holds account authority and a device DPoP key. The relay authorizes discovery or connection setup and asks the selected environment to mint a short-lived bootstrap credential bound to that proof key, then returns the endpoint and bootstrap material to the phone. The phone exchanges that bootstrap directly with the environment, persists the environment-bound access material, gets a direct WebSocket ticket, and then runs the same shared connection, snapshot, and command path as another client.

The tunnel can expose the local environment, but the hosted relay does not carry normal T3 API or WebSocket traffic after launch. Thus the mobile turn begins only after the environment session is established; neither a successful Clerk session nor a successful relay call is a thread update. Chapter 35 owns the credential ladder and tunnel setup; Chapter 29 and Chapter 33 own the client session and remote-native continuity edges.

infra/relay/src/environments/EnvironmentConnector.ts:541–670 ↗verbatim · typescript · 87e76875
    connect: Effect.fn("relay.environment_connector.connect")(function* (input) {
      yield* Effect.annotateCurrentSpan({
        "relay.environment_id": input.environmentId,
        "relay.operation": "connect",
        "relay.connect.has_device_id": input.deviceId !== undefined,
        ...(input.deviceId ? { "relay.mobile.device_id": input.deviceId } : {}),
      });
      if (input.clientProofKeyThumbprint.trim().length === 0) {
        return yield* new EnvironmentConnectNotAuthorized({
          environmentId: input.environmentId,
          operation: "connect",
          reason: "client_proof_key_thumbprint_missing",
        });
      }
      const { link, allocation } = yield* Effect.all(
        {
          link: links.getForUser(input),
          allocation: allocations.get(input),
        },
        { concurrency: 2 },
      );
      if (!link) {
        return yield* new EnvironmentConnectNotAuthorized({
          environmentId: input.environmentId,
          operation: "connect",
          reason: "environment_link_not_found",
        });
      }
      const endpoint = yield* resolveManagedEndpoint({
        operation: "connect",
        link,
        allocation,
      });
      const now = yield* DateTime.now;
      const expiresAt = DateTime.add(now, { minutes: 2 });
      const nonce = yield* crypto.randomUUIDv4.pipe(
        Effect.mapError(
          (cause) =>
            new EnvironmentMintRequestFailed({
              environmentId: input.environmentId,
              operation: "connect",
              cause,
            }),
        ),
      );
      const payload = {
        iss: relayIssuer,
        aud: `t3-env:${link.environmentId}`,
        sub: input.userId,
        jti: yield* crypto.randomUUIDv4.pipe(
          Effect.mapError(
            (cause) =>
              new EnvironmentMintRequestFailed({
                environmentId: input.environmentId,
                operation: "connect",
                cause,
              }),
          ),
        ),
        iat: Math.floor(now.epochMilliseconds / 1_000),
        exp: Math.floor(expiresAt.epochMilliseconds / 1_000),
        environmentId: link.environmentId,
        clientProofKeyThumbprint: input.clientProofKeyThumbprint,
        cnf: { jkt: input.clientProofKeyThumbprint },
        ...(input.deviceId ? { deviceId: input.deviceId } : {}),
        nonce,
        scope: ["environment:connect"],
      } satisfies RelayCloudMintCredentialProofPayload;
      const proof = yield* signRelayJwt({
        privateKey: Redacted.value(settings.cloudMintPrivateKey),
        typ: RELAY_MINT_REQUEST_TYP,
        payload,
      }).pipe(
        Effect.mapError(
          (cause) =>
            new EnvironmentMintRequestFailed({
              environmentId: input.environmentId,
              operation: "connect",
              cause,
            }),
        ),
      );
      const environmentClient = yield* makeEnvironmentClient(endpoint.httpBaseUrl);
      const decoded = yield* environmentClient.connect
        .t3MintCredential({ payload: { proof } })
        .pipe(
          withoutRedirects,
          Effect.mapError(
            (cause) =>
              new EnvironmentMintRequestFailed({
                environmentId: input.environmentId,
                operation: "connect",
                cause,
              }),
          ),
          Effect.timeoutOption(Duration.millis(ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS)),
          Effect.flatMap(
            Option.match({
              onNone: () =>
                Effect.fail(
                  new EnvironmentMintRequestTimedOut({
                    environmentId: input.environmentId,
                    timeoutMs: ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS,
                  }),
                ),
              onSome: Effect.succeed,
            }),
          ),
        );
      const verified = yield* verifyEnvironmentResponse({
        response: decoded,
        environmentId: input.environmentId,
        requestNonce: nonce,
        clientProofKeyThumbprint: input.clientProofKeyThumbprint,
        environmentPublicKeys: [link.environmentPublicKey],
        relayIssuer,
        nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000),
      });
      if (!verified) {
        return yield* new EnvironmentMintResponseInvalid({
          environmentId: input.environmentId,
          operation: "connect",
        });
      }
      return {
        environmentId: link.environmentId,
        endpoint,
        credential: decoded.credential,
        expiresAt: decoded.expiresAt,
      };
Read this as: The relay binds its mint operation to the proof-key thumbprint, asks the environment to mint, and returns a bootstrap credential.
packages/client-runtime/src/authorization/service.ts:180–299 ↗verbatim · typescript · 6815918a
  const authorizeDpop = Effect.fn("clientRuntime.connection.remote.authorizeDpop")(
    function* (input: {
      readonly expectedEnvironmentId: Parameters<
        RemoteEnvironmentAuthorization["Service"]["authorizeDpop"]
      >[0]["expectedEnvironmentId"];
      readonly obtainBootstrap: Parameters<
        RemoteEnvironmentAuthorization["Service"]["authorizeDpop"]
      >[0]["obtainBootstrap"];
    }) {
      const thumbprint = yield* signer.thumbprint.pipe(
        Effect.mapError(
          () =>
            new ConnectionBlockedError({
              reason: "configuration",
              detail: "Could not load the environment authorization key.",
            }),
        ),
        Effect.withSpan("environment.authorization.dpopKey.resolve"),
      );
      const now = yield* Clock.currentTimeMillis;
      const cached = yield* tokenStore
        .get(input.expectedEnvironmentId)
        .pipe(Effect.withSpan("environment.authorization.accessToken.cache"));
      if (
        Option.isSome(cached) &&
        cached.value.environmentId === input.expectedEnvironmentId &&
        cached.value.dpopThumbprint === thumbprint &&
        cached.value.expiresAtEpochMs > now + TOKEN_EXPIRY_SAFETY_MARGIN_MS
      ) {
        yield* Effect.annotateCurrentSpan({
          "connection.remote_token_cache": "hit",
        });
        const cachedSocket = yield* createDpopSocketUrl(
          cached.value,
          CACHED_ENDPOINT_SOCKET_TIMEOUT_MS,
        ).pipe(Effect.result);
        if (Result.isSuccess(cachedSocket)) {
          return {
            environmentId: cached.value.environmentId,
            label: cached.value.label,
            httpBaseUrl: cached.value.endpoint.httpBaseUrl,
            socketUrl: cachedSocket.success,
            httpAuthorization: {
              _tag: "Dpop" as const,
              accessToken: cached.value.accessToken,
            },
          };
        }
        if (cachedSocket.failure._tag === "ConnectionBlockedError") {
          return yield* mapDpopSocketError(cachedSocket.failure);
        }
        yield* tokenStore
          .remove(input.expectedEnvironmentId)
          .pipe(Effect.withSpan("environment.authorization.accessToken.remove"));
      }
 
      yield* Effect.annotateCurrentSpan({
        "connection.remote_token_cache": "miss",
      });
      const bootstrap = yield* input.obtainBootstrap;
      const descriptor = yield* fetchDescriptor(bootstrap.endpoint.httpBaseUrl).pipe(
        Effect.provideService(HttpClient.HttpClient, httpClient),
        Effect.withSpan("environment.authorization.descriptor"),
      );
      if (descriptor.environmentId !== input.expectedEnvironmentId) {
        return yield* environmentMismatchError({
          expected: input.expectedEnvironmentId,
          actual: descriptor.environmentId,
        });
      }
      const bootstrapProof = yield* signer
        .createProof({
          method: "POST",
          url: environmentEndpointUrl(bootstrap.endpoint.httpBaseUrl, "/oauth/token"),
        })
        .pipe(
          Effect.mapError(
            () =>
              new ConnectionBlockedError({
                reason: "configuration",
                detail: "Could not create the environment authorization proof.",
              }),
          ),
        );
      const access = yield* exchangeRemoteDpopAccessToken({
        httpBaseUrl: bootstrap.endpoint.httpBaseUrl,
        credential: bootstrap.credential,
        dpopProof: bootstrapProof,
        scopes: presentation.scopes,
        clientMetadata: presentation.metadata,
      }).pipe(
        Effect.mapError(mapRemoteEnvironmentError),
        Effect.provideService(HttpClient.HttpClient, httpClient),
        Effect.withSpan("environment.authorization.accessToken.exchange"),
      );
      const issuedAt = yield* Clock.currentTimeMillis;
      const token = new TokenStore.RemoteDpopAccessToken({
        environmentId: descriptor.environmentId,
        label: descriptor.label,
        endpoint: bootstrap.endpoint,
        accessToken: access.access_token,
        expiresAtEpochMs: issuedAt + access.expires_in * 1_000,
        dpopThumbprint: thumbprint,
      });
      const socketUrl = yield* createDpopSocketUrl(token).pipe(Effect.mapError(mapDpopSocketError));
      yield* tokenStore
        .put(token)
        .pipe(Effect.withSpan("environment.authorization.accessToken.persist"));
      return {
        environmentId: descriptor.environmentId,
        label: descriptor.label,
        httpBaseUrl: bootstrap.endpoint.httpBaseUrl,
        socketUrl,
        httpAuthorization: {
          _tag: "Dpop" as const,
          accessToken: token.accessToken,
        },
      };
    },
  );
Read this as: The client exchanges relay-provided bootstrap material at the environment using its DPoP signer, stores the key-bound token, and obtains a direct socket URL.

Trace 3 — approval round-trip: persist a request and a response around a native callback

A provider can request approval or structured user input while processing a turn. The adapter normalizes that native request; runtime ingestion flushes buffered assistant content before the interaction pause, records canonical pending activity, and projection storage exposes it to a selected web or mobile thread. The client derives a response UI from the synchronized pending request—not from an untrusted notification payload or a local approximation of provider state.

When the person answers, the client sends a typed response intent. The decider creates a durable response-requested event; the provider command reactor later routes it to the exact bound session, and the adapter uses the provider-native reply mechanism. A subsequent provider event and projection update are what settle the visible pending state. The canonical and provider-specific mapping is in Chapter 24.

Trace 4 — offline mobile task drain: durable phone intent waits for remote evidence

While offline, the phone first exposes a queued row optimistically, then serializes an atomic file write. Before delivery, it confirms that the durable record still exists behind pending mutations. That prevents an item whose write failed from escaping merely because it was briefly visible in the UI.

After reachability returns, the drain does not send every record at once. It handles one message globally and the first queued message per thread. For a creation, it waits for a live shell: a shell that already contains the stable thread id means the local cleanup is stale and the item is removed; only a live shell that lacks it may send. Existing-thread items likewise wait for enough shell evidence before a missing thread is discarded. Settings reconciliation can precede the final turn command; the environment then applies its normal receipt and invariant rules.

Once the start-turn command returns its selected success result, the drain removes its local file; it does not wait for a later shell/detail projection to make that cleanup decision. Snapshot and stream state reconcile afterward and remain useful evidence on later drain passes. That joins no transaction across phone storage and server SQLite. Chapter 33 is the full mobile state-machine account, while Chapter 10 explains the environment receipt boundary.

apps/mobile/src/state/thread-outbox-manager.ts:91–124 ↗verbatim · typescript · 616baa8c
  // The queued atom drives the composer's immediate "queued" feedback, so it
  // is published synchronously; the durable write happens behind it and rolls
  // the message back out if it fails (durability only matters for crash
  // recovery, not for the in-session queue).
  const enqueue = (message: QueuedThreadMessage): Promise<void> => {
    setMessages([
      ...currentMessages().filter((candidate) => candidate.messageId !== message.messageId),
      message,
    ]);
    return serialize(async () => {
      try {
        await options.storage.write(message);
      } catch (cause) {
        // Roll back by reference, not messageId: a retry enqueue with the same
        // id may have optimistically replaced this attempt while the write was
        // in flight, and its entry must survive this attempt's failure.
        setMessages(currentMessages().filter((candidate) => candidate !== message));
        throw new ThreadOutboxManagerError({
          operation: "enqueue",
          environmentId: message.environmentId,
          threadId: message.threadId,
          messageId: message.messageId,
          cause,
        });
      }
    });
  };
 
  // Resolves once all pending mutations (including any in-flight enqueue
  // write) have settled, reporting whether the message is still queued. The
  // drain awaits this before dispatching so a message whose durable write
  // later fails can never have been delivered first.
  const confirmQueued = (message: QueuedThreadMessage): Promise<boolean> =>
    serialize(async () => currentMessages().some((candidate) => candidate === message));
Read this as: The mobile queue is visible before its atomic write, while confirmation serializes delivery after that write and reference-based rollback protects a newer retry.
apps/mobile/src/state/thread-outbox-model.ts:145–226 ↗verbatim · typescript · d2969b0b
export function threadOutboxRetryDelayMs(attempt: number): number {
  return Math.min(1_000 * 2 ** Math.max(0, attempt - 1), THREAD_OUTBOX_MAX_RETRY_DELAY_MS);
}
 
export type ThreadOutboxDeliveryAction = "wait" | "remove" | "send";
 
export function resolveThreadOutboxDeliveryAction(input: {
  readonly isCreation: boolean;
  readonly threadExists: boolean;
  readonly shellStatus: EnvironmentShellStatus;
  readonly environmentConnected: boolean;
  readonly threadBusy: boolean;
}): ThreadOutboxDeliveryAction {
  if (input.isCreation) {
    // A pending task creates its thread on delivery. If the thread already
    // exists the creation command went through and only cleanup remains.
    if (input.threadExists) {
      return "remove";
    }
    // Wait for the shell to be live before sending: until the thread list has
    // synchronized, a previously delivered creation whose cleanup failed would
    // look missing and get re-issued, duplicating the thread.
    return input.environmentConnected && input.shellStatus === "live" ? "send" : "wait";
  }
  if (!input.threadExists) {
    return input.shellStatus === "live" ? "remove" : "wait";
  }
  return input.environmentConnected ? "send" : "wait";
}
 
/**
 * A queued creation can only be dispatched once its payload would pass server
 * validation; incomplete payloads stay pending until the user edits them.
 */
export function isQueuedThreadCreationSendable(message: QueuedThreadMessage): boolean {
  if (!message.creation) {
    return false;
  }
  if (message.text.trim().length === 0 || message.modelSelection === undefined) {
    return false;
  }
  return message.creation.workspaceMode !== "worktree" || Boolean(message.creation.branch);
}
 
function errorMessage(error: unknown): string | null {
  if (error instanceof Error) {
    return error.message;
  }
  if (typeof error === "object" && error !== null && "message" in error) {
    return typeof error.message === "string" ? error.message : null;
  }
  return typeof error === "string" ? error : null;
}
 
export function shouldRetryThreadOutboxDelivery(error: unknown): boolean {
  if (
    typeof error === "object" &&
    error !== null &&
    "_tag" in error &&
    error._tag === "ConnectionTransientError"
  ) {
    return true;
  }
  return isTransportConnectionErrorMessage(errorMessage(error));
}
 
export type ThreadOutboxCommandStage = "settings-sync" | "start-turn";
export type ThreadOutboxFailureAction = "retry" | "discard";
 
export function resolveThreadOutboxFailureAction(input: {
  readonly stage: ThreadOutboxCommandStage;
  readonly error: unknown;
  readonly interrupted: boolean;
}): ThreadOutboxFailureAction {
  if (
    input.stage === "settings-sync" ||
    input.interrupted ||
    shouldRetryThreadOutboxDelivery(input.error)
  ) {
    return "retry";
  }
  return "discard";
Read this as: The drain uses synchronized thread existence as a creation replay guard and classifies retryable versus discardable outcomes.

Trace 5 — checkpoint diff and revert: Git content first, then a conditional history rewrite

For an eligible completed turn, the checkpoint reactor can capture the workspace through a temporary Git index into a hidden, thread-scoped ref. It derives the patch against the preceding checkpoint and dispatches a thread.turn.diff.complete command only after that capture/diff work. The engine then makes the checkpoint summary visible in the durable thread projection. A review screen can compare hidden turn boundaries; live working-tree and branch modes are separate Git queries.

Revert begins with a durable request event, but it is an ordered saga rather than one transaction. It checks the thread, binding, Git workspace, turn count, and target; restores content and index, refreshes the workspace, asks the bound provider to roll back later turns, attempts ref pruning, returns those outcomes to the checkpoint reactor, and only then has the orchestration engine dispatch durable completion. If provider rollback or a later step fails after Git restore, files may already match the target while provider history and projected thread history do not. That partial state is an implementation-path inference, not a promise of automatic repair. See Chapter 27 for the full preconditions and provider matrix.

Trace 6 — stable release and exact-version update: publish, prepare, trial, commit or roll back

The release workflow first resolves a stable version and publishes the exact t3@V CLI/runtime package. Only then can the GitHub release make clients discoverable and a hosted deployment follow. A connected client that asks an eligible server to update targets that exact version, not whichever package currently owns an npm dist-tag.

The active server rejects non-exact or concurrent targets, stages the immutable runtime, preflights it, and hands prepared target information to the stable service launcher. The launcher owns the migration/trial boundary: it records pending state, captures the SQLite triplet, stops the old child, starts the new candidate, and commits only after the candidate reports prepared. A failed or timed-out trial can restore the snapshot and select the old runtime; after durable commit, later failures are governed by ordinary restart policy instead. Chapter 38 separates this boot-service updater from the desktop and mobile state machines.

docs/operations/release.md:174–187 ↗verbatim · markdown · 22eb4423
## Server self-update release invariant
 
Connected servers update to the client's exact version, not to an npm dist-tag. Every released
desktop or hosted client version must therefore have a matching `t3@<version>` package available on
npm before users can receive that client.
 
The workflow enforces this ordering:
 
1. `publish_cli` publishes the exact stable or nightly version to npm.
2. `release` depends on `publish_cli` before exposing desktop artifacts in GitHub Releases.
3. `deploy_web` depends on `release` before moving the hosted channel to the new client.
 
Preserve these dependencies when changing the release graph. Publishing a client first would leave
the **Update server** action targeting a package version that does not exist yet.
Read this as: Release operations require exact CLI publication before release and hosted-client exposure because clients can request that exact server runtime.
apps/server/src/cloud/selfUpdate.ts:64–169 ↗verbatim · typescript · 2024b45f
  const update: ServerSelfUpdate["Service"]["update"] = Effect.fn(
    "cloud.server_self_update.update",
  )(function* (input, reportProgress = () => Effect.void) {
    if (capability === "desktop-managed") {
      return yield* failWith(
        "This server is managed by the T3 Code desktop app on its machine; update the desktop app to update it.",
      );
    }
    if (capability === null) {
      return yield* failWith(
        "Remote updates require the T3 Code background service. Run `t3 service install` on the server machine.",
      );
    }
 
    const targetVersion = input.targetVersion.trim();
    if (!isExactServiceVersion(targetVersion)) {
      return yield* failWith(`'${targetVersion}' is not an exact t3 version.`);
    }
    if (yield* Ref.getAndSet(inFlight, true)) {
      return yield* failWith("A server update is already in progress.");
    }
 
    return yield* Effect.gen(function* () {
      yield* reportProgress("downloading");
      const paths = yield* ensurePinnedRuntimeInstalled({
        baseDir: serverConfig.baseDir,
        version: targetVersion,
        fs,
        path,
        runner,
        validate: (runtime) =>
          runner
            .run({
              command: execPath,
              args: [
                runtime.entryPath,
                "__service-preflight",
                "--database-path",
                serverConfig.dbPath,
                "--launcher-protocol",
                String(SERVICE_LAUNCHER_PROTOCOL),
              ],
              timeout: PREFLIGHT_TIMEOUT,
            })
            .pipe(
              Effect.mapError(
                (cause) =>
                  new PinnedRuntimeInstallError({
                    step: "running the staged service preflight",
                    cause,
                  }),
              ),
              Effect.flatMap(
                (
                  result,
                ): Effect.Effect<
                  void,
                  PinnedRuntimeInstallError | PinnedRuntimePreflightBlockedError
                > => {
                  if (result.code !== 0) {
                    return Effect.fail(
                      new PinnedRuntimeInstallError({
                        step: "running the staged service preflight",
                        exitCode: Number(result.code),
                        stdoutLength: result.stdout.length,
                        stderrLength: result.stderr.length,
                      }),
                    );
                  }
                  let parsed: unknown;
                  try {
                    parsed = JSON.parse(result.stdout.trim());
                  } catch (cause) {
                    return Effect.fail(
                      new PinnedRuntimeInstallError({
                        step: "decoding the staged service preflight",
                        cause,
                      }),
                    );
                  }
                  const preflight = decodeServicePreflightResult(parsed);
                  if (preflight === undefined || preflight.version !== targetVersion) {
                    return Effect.fail(
                      new PinnedRuntimeInstallError({
                        step: "verifying the staged service preflight",
                      }),
                    );
                  }
                  return preflight.status === "ready"
                    ? Effect.void
                    : Effect.fail(
                        new PinnedRuntimePreflightBlockedError({
                          version: targetVersion,
                          reason: preflight.reason,
                        }),
                      );
                },
              ),
            ),
      }).pipe(
        Effect.mapError((error) =>
          error._tag === "PinnedRuntimePreflightBlockedError"
            ? failWith(error.reason, error)
            : failWith(`Could not prepare t3@${targetVersion}.`, error),
        ),
      );
Read this as: The server updater rejects non-exact targets and concurrent work, stages the runtime, and requires a matching preflight result before launcher handoff.
Interactive synthesis lab · Chapter 39

Move one trace through the owner that acts next

Select a complete trace, then advance deliberately. The marker crosses the owner that acts next; its final boundary says what does—and does not—converge.

Step 1 of 6

Trace 1 · Local first turn

Client sends one existing-thread command

The user action enters the environment through an authorized RPC method; no provider work has happened yet.

Owner now
client and RPC entry policy
Handoff / evidence
typed turn command reaches the selected environment
Durable fact now
none yet
Boundary if it stops here
an RPC failure is not a provider failure and does not create an accepted turn

Local first turn. Step 1 of 6.

Static six-trace ledger
TraceOwner sequenceDurable evidenceConvergence or failure boundary
Local first turnclient → RPC/engine → SQLite → provider reactor/adapter → engine → client stateaccepted event, projections, and receipt commit togetherhot reactor delivery is not replayed after a crash; later projections only show committed facts
Relay-connected mobile turnphone → relay control plane → selected environment → relay bootstrap return → direct mobile session → environment statedevice-held DPoP-bound environment session material and environment projectionsrelay launch authorization is not thread synchronization; ordinary traffic bypasses the relay
Approval round-tripprovider → runtime ingestion → pending projection → client → response intent → provider reactorpending request and response-requested event are separate durable factsnative provider reply is a later hot handoff where a stale or unknown request can fail; runtime-mode labels do not equal provider semantics
Offline mobile drainmobile atom → atomic outbox file → live shell → environment receipt/result → phone cleanup → later projection reconciliationconfirmed phone file, then remote command receiptthere is no transaction shared by phone storage and server SQLite; cleanup follows the command result, while projections reconcile separately
Checkpoint diff / revertcompletion → Git hidden ref/diff → checkpoint projection → durable revert request → ordered saga → engine completionhidden ref precedes checkpoint metadata; the request and later completion are separate orchestration factsrestore can happen before provider rollback; later failure can leave a real partial state
Stable exact updaterelease automation → exact npm runtime → client exposure → connected-client request → server preflight → stable launcher trialexact package publication, then launcher pending/trial/commit statepublish does not replace a machine; launcher can roll back a failed trial before commit

What these traces establish—and what they refuse to claim

The repeatable pattern is not “make every operation a distributed transaction.” It is more disciplined: give each durable store a narrow contract, serialize the owner that must make a decision, pass effects through explicit handoff points, and show the reader or operator what evidence establishes convergence. Inference across modules is useful only when it keeps the seams visible.

For example, a relay-connected mobile turn legitimately combines credential launch, environment supervision, and an ordinary turn. It does not imply that the relay stores the thread. Likewise, an outbox retry and a command receipt together reduce duplicate work, but they cannot promise that every provider-side effect occurred once. These distinctions are the basis for the decision review that follows.

T3
Source-locked editionRead against fa219001d · 23 Aug 2026
Book search

Find a concept, module, or source path

Type two or more characters.