0011 — GTB uplift & studio re-platform (v0.21 → v0.27.2)¶
Status: IMPLEMENTED (2026-07-04) — a foundational precursor that bumps go-tool-base
and re-platforms the studio onto GTB's service stack. Companion to
0003-studio.md (the studio this re-platforms),
0009-ai-review.md §3.1 and
0010-packaging-update.md §2.1/§6 (the two features
that depend on the auth + controls this delivers), and the GTB scaffold
(.gtb/manifest.yaml, the generated root cmd.go).
This is a DRAFT for review. No implementation begins until §6 is resolved or deferred.
Why now. Two queued features —
0009(AI review) and0010(packaging + self-update) — both need a bearer/cookie-authenticated studio API and acontrols-managed server lifecycle (graceful shutdown, health, re-exec hook). GTB already ships all of this — the controls-integratedpkg/httpserver andhttp.AuthMiddleware(cookie + bearer + api-key + mTLS, onpkg/authn) — but only from ≥v0.27.2; krites is pinned to v0.21.0. Rather than hand-roll auth/lifecycle (and diverge from the toolchain), we bump GTB and adopt its stack. It is also plain hygiene: krites is six minor versions behind.
1. What GTB ≥v0.27.2 gives us (and where it landed)¶
http.AuthMiddleware(v0.23) +pkg/authn(v0.23) — credential verification core: bearer, API-key, mTLS, authorize predicate, JWT/OIDC.- Cookie credential source (v0.24) — the cookie sits below the bearer header
and is the ambient credential the browser sends on
<img>sub-resource loads that can't set an Authorization header (the studio's preview grid); a token-in-URL bootstrap sets the cookie on first load (Jupyter-style), which is exactly the launcher→browser token handoff (0010Q1b). - Controls-integrated
pkg/httpserver —NewServer+Start/Stop/Statusreturningcontrols.StartFunc/StopFunc/StatusFunc, health/readiness/liveness handlers, a middlewareChain, security-headers/otel/max-bytes middleware — so the studio becomes a registeredcontrolsservice with a gracefulStop(0010's shutdown + the update re-exec hook). WithMTLSVerifier— the seam for the future multi-tenant/TLS path (0009§3.1).
2. The bump surface (v0.21 → v0.27.2)¶
krites imports eight GTB packages: config, props, setup, cmd/root,
version, logger, errorhandling, credentials/keychain. From the GTB
changelog, the surface touching those is modest:
| Version | Change | Impact on krites |
|---|---|---|
| v0.23 | + http.AuthMiddleware, authn, rate-limit/circuit-breaker |
additive — the capability we want |
| v0.24 | + cookie credential source; generator regenerate project --force migrates flat docs → Diátaxis |
additive auth; docs-migration is the risk (§6-Q1) |
| v0.25 | props: remove .hcl/.tf asset support |
krites uses YAML assets → likely N/A (verify) |
| v0.26 | remove deprecated update ExportNew* seams; grpc ConfigKey* |
krites uses neither → N/A |
| v0.27 | config: project-local .<tool>.yaml layer (repo-root, overrides global) |
behavioural — a new config layer; verify krites' resolution (§6-Q2) |
No hard breakage is expected in the eight imported packages; the real work is
(a) a careful scaffolding regen to v0.27.2 and (b) the studio re-platform
(new code, not a migration — the studio hand-rolls http.Server today).
3. The studio re-platform¶
Today pkg/cmd/studio/main.go builds a plain http.Server and calls
Shutdown on a signal. This spec replaces that with GTB's stack, preserving
every existing endpoint and behaviour:
- Server: build the studio handler as now, then serve it via GTB
http.NewServer+ registerhttp.Start/Stop/Statusas acontrolsservice (WithStop= the drain). The studio gains health/readiness endpoints and a graceful, controller-driven lifecycle for free. - Auth: wrap the API routes in
http.AuthMiddleware(cookie + bearer), loopback-bound and single-user. A per-launch session token is minted by the launcher/server and delivered via the token-in-URL bootstrap; thereafter the cookie authenticates preview<img>loads and the bearer authenticates explicit API calls. Health endpoints stay outside the auth chain. - What this unlocks downstream (built in their own specs, not here):
0010'sPOST /api/shutdown(→controls.Controller.Stop()) andPOST /api/update(bearer-authed), and0009's credential-entry endpoint (§3.1).
4. Implementation plan (layered)¶
- Slice 1 — the bump + regenerate. ✅ Landed 2026-07-04. Bumped
go.modtogo-tool-base v0.27.2; no compile breaks across the eight imported packages;just cigreen (tests, race, lint, wasm). Rangtb regenerate project(via the pinnedgo tool gtb): the generated code is already structurally on-pattern with v0.27.2 — the only diff was cosmetic (import aliases + collapsing theNewCmdRoot(...)call to one line, arguably worse), so we kept the tidy hand-formattedroot/cmd.go. The hand-tuned commandcmd.gofiles (ExcludeFromMCP, customArgs/Long) were correctly protected by regenerate's conflict detection. The Diátaxis docs migration is opt-in behind--update-docs(v0.27.2) — a plainregenerate projectdoes not touch docs — so §6-Q1's concern is moot; docs stay flat unless we deliberately opt in later. - Slice 2 — studio onto
controls+http. ✅ Landed 2026-07-04.RunStudionow builds the handler as before, then registers it viahttp.Registerwith acontrols.NewController(WithoutSignals — the root command owns signals and cancels ctx; we drive stop from ctx) and drains on ctx cancellation (Start→<-ctx.Done()→Stop→Wait). Verified end-to-end:/and the GTB/healthzboth 200, localhost-only bind confirmed (ssshows127.0.0.1:PORT, not0.0.0.0), SIGINT stops gracefully.R-API-1caveat: GTB's server binds":port"(all interfaces) with no host option, so we overridesrv.Addrto the loopback host afterRegister(start() readssrv.Addrat invocation). The health endpoints ride outside any middleware — the launcher's liveness probe (0010) and exempt from slice-3 auth. - Slice 3 — the auth layer. ✅ Landed 2026-07-04.
cmd/studiomints a high-entropy per-launch session token and wraps the server in a chain:http.AuthMiddleware(cookie + bearer over the token) guards/api/*(viaWithAuthSkipper), and a cookie-bootstrap middleware sets that token as thekrites_sessioncookie whenever the SPA shell/assets are served. So a browser that loads the studio is authenticated for its subsequent API calls and<img>preview loads (which can't set an Authorization header) — with no launcher and no token-in-URL (Matt's simplification: the server hands the token to the UI directly). Cookie is HttpOnly + SameSite=Strict + Secure-on-TLS; GTB health endpoints stay open for the launcher probe. Verified end-to-end (401 without cookie → shell sets cookie → 200 with cookie) + unit-tested. This is the substrate0009/0010consume.
5. Non-goals (this spec)¶
- Implementing
0009/0010features. This delivers the substrate (bumped GTB, studio-on-controls, studio auth); the review, shutdown, update and packaging behaviours ship in their own specs. - Multi-tenant / TLS / remote access. The auth is loopback single-user; mTLS is a wired-but-unused seam.
- A studio feature rewrite. Endpoints and UX are preserved; only the server platform + auth wrapper change.
- Adopting unrelated GTB additions (grpc, chat failover, resilience) — pull in only what the studio needs; keep the binary lean (§6-Q4).
6. Open questions¶
- Q1 —
regenerate project& the Diátaxis docs migration. ✅ RESOLVED (2026-07-04): no docs impact. In v0.27.2 the docs migration is gated behind--update-docs; a plaingtb regenerate projectonly rewrites command registration (cmd.go), not docs. On krites it produced only cosmetic churn (§4 slice 1), so nothing was adopted and docs stay flat. Original text kept below for context. — We want to rungtb regenerate projecton the bump so generated code tracks the latest patterns (§4 slice 1). The one side-effect to decide: v0.24's regenerate also migrates flat docs → Diátaxis (tutorials / how-to / reference / explanation), and krites' docs are flat and hand-written (docs/commands/*,docs/development/specs/*). Decision needed: (a) adopt the Diátaxis layout (it is the latest GTB pattern; a one-time restructure that keeps us aligned, but relocates our hand- written pages and theNNNN-*.mdspecs), or (b) run regenerate but decline/ revert the docs move, keeping the flat layout while taking all code changes. Either way, guard the hand-written content: run regenerate on a clean tree, diff, and verify no spec or command doc is lost or mangled before committing. Proposed: (a) adopt Diátaxis if the migration cleanly carries our existing pages; fall back to (b) if it would damage the specs. - Q2 — config project-local layer (v0.27). ✅ RESOLVED: no impact. The bump built and all tests/CI passed unchanged; the new layer is additive and did not perturb krites' config resolution.
- Q3 — session-token issuance. ✅ RESOLVED (simpler than framed): the server mints it, in-memory, per launch, and sets it as the SPA cookie (§4 slice 3) — no launcher, no URL bootstrap, no lock-file storage. The token lives only in the running process and the browser's HttpOnly cookie.
- Q4 — dependency weight. ⏳ The studio imports only
authn(api-key verifier) http/controls; grpc/JWT/JWKS are not imported by krites. A binary-size spot-check before release would confirm no heavy transitive creep, but nothing forces it in. (Track, low priority.)- Q5 — WASM boundary. ✅ RESOLVED:
just wasm-checkgreen after the bump; the new imports are all CLI/studio-side, the engine stays cgo-free/WASM-safe.