Skip to content

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) and 0010 (packaging + self-update) — both need a bearer/cookie-authenticated studio API and a controls-managed server lifecycle (graceful shutdown, health, re-exec hook). GTB already ships all of this — the controls-integrated pkg/http server and http.AuthMiddleware (cookie + bearer + api-key + mTLS, on pkg/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 (0010 Q1b).
  • Controls-integrated pkg/http serverNewServer + Start/Stop/Status returning controls.StartFunc/StopFunc/StatusFunc, health/readiness/liveness handlers, a middleware Chain, security-headers/otel/max-bytes middleware — so the studio becomes a registered controls service with a graceful Stop (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 + register http.Start/Stop/Status as a controls service (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's POST /api/shutdown (→ controls.Controller.Stop()) and POST /api/update (bearer-authed), and 0009's credential-entry endpoint (§3.1).

4. Implementation plan (layered)

  • Slice 1 — the bump + regenerate. ✅ Landed 2026-07-04. Bumped go.mod to go-tool-base v0.27.2; no compile breaks across the eight imported packages; just ci green (tests, race, lint, wasm). Ran gtb regenerate project (via the pinned go tool gtb): the generated code is already structurally on-pattern with v0.27.2 — the only diff was cosmetic (import aliases + collapsing the NewCmdRoot(...) call to one line, arguably worse), so we kept the tidy hand-formatted root/cmd.go. The hand-tuned command cmd.go files (ExcludeFromMCP, custom Args/Long) were correctly protected by regenerate's conflict detection. The Diátaxis docs migration is opt-in behind --update-docs (v0.27.2) — a plain regenerate project does 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. RunStudio now builds the handler as before, then registers it via http.Register with a controls.NewController (WithoutSignals — the root command owns signals and cancels ctx; we drive stop from ctx) and drains on ctx cancellation (Start<-ctx.Done()StopWait). Verified end-to-end: / and the GTB /healthz both 200, localhost-only bind confirmed (ss shows 127.0.0.1:PORT, not 0.0.0.0), SIGINT stops gracefully. R-API-1 caveat: GTB's server binds ":port" (all interfaces) with no host option, so we override srv.Addr to the loopback host after Register (start() reads srv.Addr at 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/studio mints a high-entropy per-launch session token and wraps the server in a chain: http.AuthMiddleware (cookie + bearer over the token) guards /api/* (via WithAuthSkipper), and a cookie-bootstrap middleware sets that token as the krites_session cookie 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 substrate 0009/0010 consume.

5. Non-goals (this spec)

  • Implementing 0009 / 0010 features. 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 plain gtb regenerate project only 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 run gtb regenerate project on 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 the NNNN-*.md specs), 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-check green after the bump; the new imports are all CLI/studio-side, the engine stays cgo-free/WASM-safe.