Skip to content

feat(lazurio): the Environment pairs its own browser - #84

Merged
immakermatty merged 2 commits into
mainfrom
lazurio/self-pairing
Oct 18, 2026
Merged

immakermatty merged 2 commits into
mainfrom
lazurio/self-pairing

Conversation

@immakermatty

@immakermatty immakermatty commented Oct 17, 2026 •

Copy link
Copy Markdown

Problem

On a Lazurio Environment only the Launchpad's Chat click pairs a browser with T3: it mints a 60 s token and sends the browser to /pair#token=…. Every other way into Chat ends on T3's "Pair with this environment" card, outside the app layout, with no way back:

  • a bookmark;
  • the shell's switch in Automate;
  • the Dashboard;
  • another Environment's Chat in the picker.

That token also carries only the standard scopes, so Settings → Connections hides "Authorized clients". A person can't create a pairing link for T3 Desktop or mobile there. Browsers paired on 0.0.45 keep legacy scopes on 0.1.0 (no terminal, files, Git, settings or Environment browser tab holds) until they come through the Launchpad again.

Change

The Organization Admin decided on 2026-10-17 that the Environment pairs its own browser. This replaces the 2026-09-21 rule that a direct visit shows T3's pairing page and the fork stays untouched.

  • The auth gate's third credential source (apps/web/src/environments/primary/auth.ts, two calls; the code is in apps/web/src/lazurio/environmentPairing.ts).
    • Without a link's or the desktop app's credential, the page asks its own origin: GET /.lazurio/chat/pairing.
    • The Lazurio gateway forwards that to the Environment's Launchpad after admission (the Lazurio account sign-in).
    • The credential is exchanged like any bootstrap credential, before any route runs. A deep link keeps its place and no token appears in a URL.
    • A session with legacy scopes asks too; when that fails, it keeps its session.
    • Anything but the exact JSON answer leaves upstream's pairing page: T3's own index.html outside Lazurio or with an older Platform, a non-200, a redirect, an error, or a 5 s timeout.
    • It never asks in the desktop app or over plain HTTP.
  • t3 lazurio pairing (overlay CLI).
    • It mints upstream's one-time pairing grant, the way t3 auth pairing create does, with the operator's scopes: AuthStandardClientScopes + access:read + access:write.
    • It never mints relay:write: T3 Connect would be a public way in around the gateway and the tailnet.
    • It prints the same JSON as t3 auth pairing create --json.
    • t3 lazurio capabilities gains operatorPairing: true. That is additive: Machines and the Platform check only their own keys.
  • CI, tests, docs. The allowlist entry for auth.ts, contract-test pins, overlay row 19 with its runbook section, the overlay spec and the public README.

Scope and approval

Decided by the Organization Admin in the thread of #70 on 2026-10-17:

  • the Environment pairs the browser;
  • operator scopes, Team Environments included;
  • public shared Environments keep the standard scopes, a choice the Launchpad makes;
  • desktop and mobile pair through device tokens from Connections, only on tailnet Environments.

The Launchpad route is Lazurio/LazurioPlatform#486. Either side can ship first: without the route, this fork behaves as today. The Platform calls t3 lazurio pairing without --json, because this command always prints JSON and the CLI refuses unknown flags.

Verification

  • node --test scripts/lazurio-release-contract.test.mjs: 34/34.
  • Web: environmentPairing.test.ts 11/11 together with upstream's auth bootstrap tests, 28 in all. It covers answer parsing and refusals, request options, the timeout, the origin, the plain-HTTP and desktop guards, and the decision table through upstream's real auth gate against a fake server.
  • Server: cli.test.ts 10/10.
    • Pairing output equals upstream's command with the same scopes.
    • relay:write is absent.
    • The token works once and is not legacy.
    • Capabilities output is byte for byte.
  • Web and server typecheck pass, and fmt and lint are clean on the changed files.
  • Not yet in a browser. That needs the Platform route (Settings UI 'Codex binary path' has no effect pingdotgg/t3code#486) and a preview from main. It is checked on Ring 0 with both, before the stable.

Model and harness: Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

Note

Add Environment-owned browser pairing to Lazurio web authentication

  • Direct HTTPS visits with no session now fetch a one-time pairing credential from the page's own Environment origin and exchange it without navigation; URL and desktop credentials keep precedence in auth.ts
  • Sessions with legacy scopes automatically re-pair with the Environment; if re-pairing fails, the existing session is kept
  • Adds t3 lazurio pairing CLI command in cli.ts that issues operator-scoped credentials (client scopes plus access-read/write, no relay-write or admin) with upstream-compatible JSON output; capabilities now reports the operator-pairing capability
  • Pairing requests are strict: same-origin only, reject redirects, JSON content-type check, 5-second abort timeout, exact response-shape validation in environmentPairing.ts
  • Adds unit, contract, and release-contract tests covering pairing flow, CLI output, and static seams; documents the overlay spec and release runbook
  • Risk: behavioral change — the web auth gate in auth.ts is now an upstream-modified file (allowlisted in lazurio-fork-ci.yml); legacy sessions make a new pairing request on load, and failure to re-pair silently retains the old session

Macroscope summarized 1b6dd39.

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty Exact-head QA: APPROVED for 6cb80ae. I traced the web bootstrap and legacy-session re-pairing, strict same-origin response validation, one-time operator grant scopes (without relay:write), and the Platform pingdotgg#486 contract. In an isolated clone I ran the release contract (34/34), focused web/server tests (21/21), and both typechecks; the exact-head CI is green and review threads are clear. Joint browser smoke on Ring 0 remains a release qualification, not evidence claimed by this review.

immakermatty added a commit to Lazurio/LazurioPlatform that referenced this pull request Oct 18, 2026
…urio/chat/pairing, operator scopes) (#486)

* Launchpad: the Environment pairs its own browser into Chat (GET /.lazurio/chat/pairing)

T3 Code's web client (Lazurio/t3code#84) asks for a pairing on its own
origin whenever it has no session; the gateway already forwards GET and
HEAD of /.lazurio/* from that origin to the Launchpad behind the
unchanged admission. The new route answers only a page's own script
fetch (Sec-Fetch-Site same-origin, Sec-Fetch-Mode cors or same-origin, no
prefetch or prerender), only where Chat is T3 Code behind this Launchpad,
with 200 and exactly {"kind":"chat-pairing","credential","expiresAt"},
JSON and Cache-Control no-store: the one answer the fork's client
accepts. Everything else is refused (403 not-allowed, 400 for a query,
405 for HEAD and other methods, 404 on a workstation) and mints nothing.
A bookmark, the shell's switch on another app, the Dashboard and another
Environment's Chat no longer end on T3's pairing page.

One function, mintChatPairing, mints for this route, POST /api/chat/pair
and lazurio chat link. Where t3 lazurio capabilities says operatorPairing
(read by t3Offers, the reader the provider refresh already uses) it runs
t3 lazurio pairing, Team Environments included, without --json: the
fork's command always prints JSON and its CLI refuses a flag it does not
know. Otherwise, and always on a public Environment, which is not even
asked, it runs today's t3 auth pairing create --json. The token is
labelled with the admitted person, "Lazurio · <username>" ("Lazurio"
without one), and T3's answer is read for its credential and expiry
only; the Platform keeps no list of T3's scopes. A missing or failing t3
is a 409 with its reason. The Launchpad's Chat click keeps pairing on
the way: harmless, and older T3 Code builds need it until every
Environment's T3 Code pairs itself.

Tests: the route's refusals (unadmitted, T3 Code's own Host, cross-site,
same-site, typed address, navigation, subresource, prefetch, query, HEAD,
POST, workstation, Recovery mode), the exact JSON, no redirect and
no-store, the command chosen by capability and by audience with --json
refused by the fake T3 as by the real one, the label, and a missing or
failing t3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: the Environment pairs its own browser (decision of 2026-10-17)

The Chat entry records the Organization Admin's decision of 2026-10-17,
which replaces the rule of 2026-09-21 (root DEV-6616) that T3 Code opened
directly shows its own pairing page and that the T3 Code fork stays
untouched: an admitted browser is paired by the Environment itself, with
T3's operator pairing where T3 Code offers it, Team Environments
included, and the standard pairing for a public Environment's visitors.
T3 Code's desktop and mobile apps pair with device tokens from T3's
Settings -> Connections on Environments reached over the tailnet; a
public lane has no native path, which is intended for now.

The Chat entry's routes and the hosted entry describe GET
/.lazurio/chat/pairing: how the gateway forwards it from T3 Code's origin
(the Launchpad's Host, the session cookie alone, the browser's Fetch
Metadata), the one fetch it answers, the exact answer the fork's client
accepts within five seconds, its refusals and how the call is chosen and
labelled (t3 lazurio pairing takes no --json), with the verification and
what it did not exercise. The target shell's open question now says the
switch keeps pairing T3 Code until every Environment's T3 Code pairs
itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
immakermatty and others added 2 commits October 18, 2026 13:45
The Organization Admin decided on 2026-10-17 that a Lazurio Environment
pairs its own browser, which replaces the rule of 2026-09-21 that a
direct visit shows T3's pairing page. Until now only a browser that came
through the Launchpad's Chat click was paired; a bookmark, the shell's
switch, the Dashboard or another Environment's Chat ended on the pairing
page and lost its deep link.

A page without a valid T3 session now asks its own origin for a pairing
credential, GET /.lazurio/chat/pairing, which the Lazurio gateway
forwards to the Environment's Launchpad behind its admission. The page
accepts only the exact answer {kind: "chat-pairing", credential,
expiresAt?} and exchanges the credential like any bootstrap credential,
as the third source after a link's credential and the desktop app's, so
the person never sees a token and stays on the page they opened.
Anything else (T3's own index.html outside Lazurio or with an older
Platform, another status, a redirect, a network error, no answer within
5 s) leaves upstream's pairing page. The desktop app and pages served
over plain HTTP never ask. A session paired on 0.0.45, which v0.1.0
reads as legacy scopes, asks too and keeps its session when that fails.

`t3 lazurio pairing [--base-dir] [--ttl] [--label]` mints the credential
through upstream's one-time pairing grant, as `t3 auth pairing create`
does, and prints the same JSON. Its scopes are the operator's: the
standard client scopes plus access:read and access:write, built from the
contracts' constants, so Settings → Connections shows the authorized
clients and can pair the desktop and mobile apps. Never relay:write,
which would open T3 Connect around the gateway and the tailnet.
`t3 lazurio capabilities` adds operatorPairing: true at contract
version 1.

The seam is two calls in upstream's bootstrapServerAuth, on the CI
allowlist and pinned by the release contract test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runbook gets row 19 in the overlay table and the target stack, a
short section on pairing, and the gates that change with it: the
`t3 lazurio` commands now include `pairing`, whose output cli.test.ts
holds against `t3 auth pairing create --json`; the CLI and the server on
one version also carry the Launchpad's pairing; and a browser with
legacy scopes now pairs again by itself, while a scope split that
sessionHasLegacyPermissions does not recognise still needs a decision
or a link. The canary checks and the announcement follow. The rule of
2026-09-21 that a direct visit shows T3's pairing page is gone.

The overlay specification describes how the browser pairs, when it
asks, the operator's scopes without relay:write, the second pairing of
a session paired on 0.0.45, the desktop and mobile apps' links from
Settings → Connections on tailnet Environments, and the seam. The
distribution README lists pairing among the changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@immakermatty Exact-head re-review: APPROVED for 1b6dd39 on main cd211fa. Range-diff against the previously approved head shows both patches identical after the conflict-free rebase. The combined CI allowlist retains SidebarChrome.tsx, leaves AppSidebarLayout.tsx intentionally absent/unchanged as #82 specified, and adds environments/primary/auth.ts for #84. The release contract and runbook preserve the #82 column-header/wordmark behavior and #84 browser pairing and operator scopes. Four required fork CI jobs passed on this head; both Macroscope checks were skipped. In an isolated checkout, targeted contract checks passed (4/4) and git diff --check passed. A local full contract invocation lacked installed workspace dependencies (33/34, module not found in an unrelated test); CI installed dependencies and passed its full contract check. Zero review threads; live head/base and reviewer lane rechecked immediately before this review. Browser smoke on Ring 0 remains release qualification, not evidence claimed here.

@immakermatty
immakermatty merged commit b4f62c4 into main Oct 18, 2026
6 checks passed
@immakermatty
immakermatty deleted the lazurio/self-pairing branch October 18, 2026 11:57
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.

2 participants