Skip to content

Factory route layer behind T3's authentication - #65

Merged
tomkaczocha merged 7 commits into
mainfrom
t3/factory-issue-57
Oct 16, 2026
Merged

tomkaczocha merged 7 commits into
mainfrom
t3/factory-issue-57

Conversation

@rawphp

@rawphp rawphp commented Oct 16, 2026 •

Copy link
Copy Markdown

Closes #57

Contract

  • done: apps/server/src/factory/t3/ exports FactoryRoutes (an HttpRouter layer) and an auth adapter (withScope, route) that authenticates through EnvironmentAuth.authenticateHttpRequest and checks scope with AuthHttp.requireEnvironmentScope; server.ts merges FactoryRoutes into layerMakeRoutes in one line; GET /api/factory/status returns {state:"running"} or {state:"idle",reason} from FactoryLoop; design.md open question 1 answered under "Decided".
  • out: other factory routes (Factory HTTP API for status, runs and control #27, Factory settings page: edit the machine config at /settings/factory #55), the web page, RPC methods, contract changes, the DPoP WWW-Authenticate challenge header on 401 (upstream's own plain routes omit it too).
  • risk: auth. The adapter adds no credential parsing; it calls the same authenticateHttpRequest T3's HTTP API middleware calls and maps failures with the helpers auth/http.ts already exports. Reviewed by correctness, security, api-contract and a mutation pass.
  • test: vp test run apps/server/src/factory/t3/http.test.ts apps/server/src/factory/boundary.test.ts apps/server/src/factory/index.test.ts → pass (41 tests)

Acceptance criteria

  • factory/t3/ exposes the route layer; server.ts adds it to layerMakeRoutes in one line — apps/server/src/factory/t3/http.ts (FactoryRoutes), apps/server/src/server.ts (FactoryRoutes, in the inner mergeAll, plus its import). server.ts was already listed in factory/SEAMS; no SEAMS change.
  • Uses T3's existing HTTP auth through the adapter; no token or cookie parsing in the factory — apps/server/src/factory/t3/auth.ts (withScope); the correctness and security reviewers compared it line by line with layerAuthenticatedAuth and authenticateRawRouteWithScope.
  • GET /api/factory/status → { state, reason? }; idle reasons cover no config and config failed to load — http.test.ts "reports an idle factory and why", "reports a running factory with no reason"; index.test.ts "reports itself idle, and why, when there is no config", "reports itself idle, with the config errors, when the machine file is invalid".
  • Scope check routes use; 401 without a session, 200 with one, 403 without the scope on a test-only AuthSettingsWriteScope route — http.test.ts "answers 401 without credentials", "answers 401 for a token that is not a session", "answers 403 to a session without it", "lets a session with it through", "needs orchestration:read ...".
  • Paired, relay and tunnel sessions authenticate as for T3's own HTTP API; no factory token — same authenticateHttpRequest call; verified by reading auth/EnvironmentAuth.ts (reviewers) and verified apps/server/src/factory/t3/auth.ts:28-40.
  • Open question 1 moved under "Decided" with what was found — factory/docs/design.md § Decided.

Gate

  • vp test run apps/server/src/factory → exit 0 (1139 tests)
  • vp run --filter t3 typecheck → exit 0

Models

  • maker: claude-sonnet-5-5 (via host; .dw/config.yml has no models block, so the global roster applied)
  • checker: claude-opus-5-5

Review

  • cycles: 1 (correctness, security, api-contract approved; testing requested changes)
  • fixed: testing-1 (status 200 cases now hold only orchestration:read, and a settings:write-only session gets 403, so the route's scope is pinned); testing-2 (one request names /api/factory/status literally and STATUS_PATH is asserted); correctness-1 (design.md and the auth.ts comment no longer name the asset route as a precedent; only the OTLP proxy calls authenticateHttpRequest).
  • mutation pass: 8 mutants, 6 killed at the reviewed tip, the 2 survivors are the fixed testing-1 and testing-2.
  • leftovers: testing-3 (nit) — nothing tests that server.ts mounts FactoryRoutes; there is no server-level route harness in the factory suite, and the one-line seam is read on every sync. Accepted.

Notes

  • At Isolate the worktree was behind origin/main; fast-forwarded with git merge --ff-only origin/main before provisioning.
  • The status route requires orchestration:read, the lowest read scope every standard client holds; the issue named no scope for it.
  • Labels created this run: none (all seven exist).

Lessons

  • Read the branch name from git, not from the host's snapshot, before the first push. Symptom: the first git push -u origin <name> failed with "src refspec does not match any". Cause: the harness context named the worktree's thread id as the branch, while the factory had checked out t3/factory-issue-57. Default: run git branch --show-current at Isolate and use that name for every push.

  • In a fork, pass --repo <origin> to gh pr create. Symptom: the draft PR opened on the upstream parent repository (pingdotgg/t3code#20176, closed at once with an explanation) although every push went to origin. Cause: gh resolves a fork's default repository to its parent unless told otherwise. Default: at Ship, gh pr create --repo "$(gh repo view --json nameWithOwner --jq .nameWithOwner)", or read git remote get-url origin and pass that owner/name, and check the URL gh prints before reporting it.

Commits

  • cc7ae04fc test(factory): route layer behind T3's auth and the loop's status — Make slice (red: http.ts missing, three index.test.ts status assertions undefined)
  • 641bc7714 feat(factory): serve /api/factory/status behind T3's authentication — Make slice
  • 54fa1b3dd test(factory): pin the status route's scope and its path — Check cycle 1
  • dfe3aa5f8 docs(factory): name only the OTLP proxy as the plain-route auth precedent — Check cycle 1

Files

  • apps/server/src/factory/t3/auth.ts — the adapter: withScope authenticates through T3 and checks scope; route mounts a scoped route
  • apps/server/src/factory/t3/http.ts — FactoryRoutes and the status route
  • apps/server/src/factory/t3/http.test.ts — 401/200/403 against the real EnvironmentAuth with issued sessions
  • apps/server/src/factory/index.ts — FactoryLoop carries status; idle reasons for no config and a config that failed to load
  • apps/server/src/factory/index.test.ts — status assertions on the real runtime layer
  • apps/server/src/server.ts — one import and FactoryRoutes, in layerMakeRoutes (listed seam)
  • factory/docs/design.md — open question 1 moved under "Decided"

Red: apps/server/src/factory/t3/auth.ts and http.ts do not exist yet, so
http.test.ts fails to resolve "./auth.ts" and "./http.ts". The three new
index.test.ts cases fail because FactoryLoop has no `status` (it reads
undefined instead of idle/running).

Issue: #57
Adds the factory route layer under factory/t3. withScope authenticates
through EnvironmentAuth.authenticateHttpRequest, the call
AuthHttp.layerAuthenticatedAuth makes, then checks the route's scope.
FactoryLoop now carries a status (running, or idle with a reason) which
GET /api/factory/status reports. server.ts merges the routes in one line.
Question 1 in the design doc is answered under "Decided".

Issue: #57
The 200 cases hold only orchestration:read, a settings:write-only session
gets 403, and one request names /api/factory/status literally.

Issue: #57
@rawphp
rawphp marked this pull request as ready for review October 16, 2026 06:46
@rawphp

rawphp commented Oct 16, 2026

Copy link
Copy Markdown
Author

Factory gate: passed (attempt 1, run by the factory in a fresh checkout of the pull request's head dfe3aa5 merged with the base branch tip ff23fa2)

Command Exit Time
factory/scripts/check.sh 0 43.1 s

…tches build.sh

- http.test.ts: a route whose auth lookup fails inside T3 answers 500 internal_error and never
  runs its handler (review f2).
- Session tokens are issued in the test's Effect on the live clock instead of through
  Effect.runPromise, which the t3code lint rule forbids in tests.
- design.md step 6 describes the smoke test build.sh runs (environment 200, "factory started"
  in the log); it never called /api/factory/status, which needs a paired session (review f4).
…unning means

GET /api/factory/status goes to every client holding orchestration:read, so the idle reasons are
now the two the issue names, "no factory config" and "factory config failed to load"; the config
path and each config error stay in the server log, where they already went (review f1).

FactoryStatus says `running` means the pass loop started, not that passes succeed; pass health
is in the log until the status route grows (#27) (review f3).
@rawphp

rawphp commented Oct 16, 2026

Copy link
Copy Markdown
Author

Factory gate: passed again (attempt 2, run by the factory in a fresh checkout of the pull request's head d33aaab merged with the base branch tip ff23fa2)

Command Exit Time
factory/scripts/check.sh 0 85.1 s

@rawphp
rawphp marked this pull request as draft October 16, 2026 08:19
# Conflicts:
#	apps/server/src/factory/index.test.ts
#	apps/server/src/factory/index.ts
@tomkaczocha
tomkaczocha marked this pull request as ready for review October 16, 2026 08:22
@tomkaczocha
tomkaczocha merged commit 2a979c1 into main Oct 16, 2026
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.

Factory route layer behind T3's authentication

2 participants