Files
teahub/docs/decisions.md
T

11 KiB

Decisions (append-only)

Format: date · decision · why. Add at the bottom. Never edit an old entry; supersede it with a new one.

Standing rules

  • Prod is the cloud instance at teahub.xyz. The Mac (hb-5) is the operator's playground and the archive of the old factory; prod never depends on it.
  • No secrets on prod beyond: Gitea's own keys, the GitHub OAuth client, the MCP's OAuth client, and the seat keys listed in the ledger. Adding one is a decision.
  • Every agent acts as itself (its own Gitea login). No service tokens.
  • Port pieces from the old instance only when a card needs them, after reading them, one piece per commit, logged in docs/rebuild.md.

Ledger

  • 2026-10-14 · Green Tea v1 is a fresh prod, not a migration. Nothing copied from hb-5 (no issues, labels, secrets, tokens). Why: the old factory was scaffolding; the rebuild is the portability test, and chai gets a box with no dev cruft.
  • 2026-10-14 · GitHub-backed Gitea on Fly. Gitea stays (ownership, a place for chai to be admin); GitHub is upstream for Time and the first login. Why: Time is GitHub-native and already works; Gitea is cheap to run and keeps the sovereignty option.
  • 2026-10-14 · Logins: GitHub now, nostr next. Auto-registration from GitHub; no local sign-up. Why: one click for collaborators; nostr gives agents and anyone else a key-based identity (see well-known.online).
  • 2026-10-14 · Tea MCP is off the Mac. New Green Tea MCP on Fly, OAuth against prod Gitea, every tool as the caller. Why: the MCP is the product surface; it should be reachable without the operator's machine and must never outrun the caller's permissions.
  • 2026-10-14 · Boring stays on the Mac. It is the operator's admin lane, not a prod component. Why: the thing that can repair prod and the thing that runs prod are different boxes with different owners.
  • 2026-10-14 · Codex is not installed on prod. Seats are API-driven (Claude API, Venice); Codex on demand as a Fly Sprite. Why: no resident credentials on the runner.
  • 2026-10-14 · Fly cost accepted for prod (two small apps). Same category as Venice credit.
  • 2026-10-14 · Mac runners reversed. Last week's move of runners onto hb-5 is undone on purpose (portability exercise).
  • 2026-10-14 · Venture loop on teahubxyz/time paused until the audit's remaining items are judged safe (3 criticals + 3 highs fixed in time#45, #46).
  • 2026-10-14 · well-known.online is a separate product, not a Teahub component; may later be prod's signup front door. No dependency either way.
  • 2026-10-14 · The Mac's Gitea stays, as a separate unit. The operator keeps a Gitea on hb-5 to play with customisation. It is not connected to prod: no mirrors, no shared users, no shared tokens, no DNS under teahub.xyz pointing at it beyond the archive hostname matcha.teahub.xyz (which may go). Why: Teahub and the Mac are distinct units; the Mac's job is maths and experiments, prod's job is being prod.
  • 2026-10-14 · Graduated ventures are orgs on prod (persimmons, phast, phly …), treated as independent clients of Teahub with their own repos and a pages convention. Not wholesale imports; each starts from what it actually is. Why: a venture with its own domain is a client, not a folder of the factory.
  • 2026-10-14 · notplanned + venture pages merge into Time's venture loop on GitHub, not into prod Gitea. Why: Time already is the venture loop; two listings would drift.
  • 2026-10-14 · Scouting is plural. Time's trending-repos scout stays the first source; notplanned's curated backlog is a second source into the same pitch round; more sources can be added the same way. notplanned is not "the" scout. Why: popularity and wanted-but-unbuilt are different signals, and the loop should be able to take several.
  • 2026-10-14 · The old Fly edge is down; its sites are dark on purpose. teahub-gitea scaled to zero (app kept, no machines). Dark until ported: persimmons.app, phast.app, phly.cloud, yolopreneur.lol apex/www, supertestnet.lol, notplanned., maths., 🫖, matcha.teahub.xyz. Per-venture <slug>.yolopreneur.lol Workers are unaffected. The Mac is now reachable only over the tailnet; the last public wire into it is cut. Port order: persimmons, phast, yolopreneur apex. Why: the edge was the only thing coupling public DNS to the Mac, and nothing behind it had meaningful traffic.
  • 2026-10-14 · Time moves to prod. GitHub was the proof of concept. Supersedes the "GitHub is upstream for Time" clause above and the "merge into Time on GitHub" entry: the factory runs on prod Gitea, in one place, with Gitea Actions. teahubxyz/time on GitHub becomes read-only once the port lands; no two-way issue sync, ever (Time's state is its issue comments, and a mirrored state machine is a conflict generator). Steps: Fly runner → Gitea backend behind Time's forge module → migrate time with issues once → flip workflows to .gitea/workflows → archive GitHub copy. Why: Time in one repo host and Teahub in another made no sense; everything on GitHub would defeat standing up Gitea at all.
  • 2026-10-14 · Runner on Fly: green-tea-runner (act_runner 5.0.0-dind, iad, shared-cpu-2x/4GB, 10GB volume holding Docker's data root — Fly's rootfs is overlay so /var/lib/docker must live on the volume). Labels ubuntu-latest, ubuntu-22.04 → node:22-bookworm. Registered instance-wide as fly-iad-1. Smoke: teahubxyz/teahub .gitea/workflows/smoke.yml green. Why: Actions on prod are a precondition for Time on prod.
  • 2026-10-14 · Time landed on prod (teahubxyz/time native, migrated once with issues; port merged as time#47; GitHub copy archived with a pointer). Workflows stay in .github/ (Gitea reads it). Trust = repo owner, TIME_TRUSTED variable, or collaborator, because the Actions token can't read permissions. venture.py is not ported yet (time#48); venture loop stays off. Why: see the entry above; one host, no sync.
  • 2026-10-14 · First product secret on prod: VENICE_API_KEY on teahubxyz/time (repo Actions secret; the same key the old factory used, taken from its delivery on hb-5, never printed). With it: TIME_WORKER=venice:qwen coder, TIME_REVIEW=venice:grok, TIME_MODE=live, TIME_AUTOMERGE=on (merges only on a trusted Approve + green CI, never on protected paths). Why: seats are the product; the operator said the key lives with Boring's deliveries, and the factory is meant to run without hand-offs.
  • 2026-10-14 · Pages convention: <org>/site is served at the org's domain by green-tea-pages (Fly, stdlib static host, one shallow clone per site, pull on a Gitea push webhook or every 5 min; code in pages/). Supersedes "Cloudflare Pages by default" in #2. No tokens anywhere: site repos are public and cloned over https. Why: the simplest thing that makes "a commit to site ships" true on prod itself, with nothing on the Mac and no new secret.
  • 2026-10-14 · persimmons and phast are ported as orgs, as they were. persimmons/app (+ vendored human_vault.py), persimmons/extension, phast/app (+ acp/); persimmons/site and phast/site live at persimmons.app and phast.app (certs issued, DNS moved off the dead edge). Neither app runs yet: persimmons signs in with Gitea passwords, which prod does not have (needs Gitea OAuth, like the MCP); phast's production blockers (volume, owner auth) stand. persimmons.app says so on a holding page; phast.app serves its landing. Why: the domains were dark; the code is where its owner is; the runtime seams are cards, not blockers for the port.
  • 2026-10-14 · Skills are vendored into the spine at .agents/skills/, not installed per seat. First set: brag-slim (latent-spaces/brag) and six of coreyhaines31/marketingskills (product-marketing, launch, social, content-strategy, copywriting, marketing-loops); evals/ left out. Both MIT. Why: seats on prod are API-driven with no home directory to install into, so the repo checkout is the only place a skill reliably exists; the factory just got dev.to and Bluesky, and marketing-loops is the shape Time can schedule. More skills go in the same way, by copying the folder.
  • 2026-10-14 · One venture ledger, at yolopreneur.lol. Time's ventures and notplanned's are one list (ventures.json + backlog.json on time's gh-pages, with a source field); teahubxyz/notplanned is on prod with its 4 issues; the index is hand-authored (neo-brutalist) and renders the JSON, so the generator writes data only. Supersedes the "notplanned merges into Time on GitHub" entry. Why: two listings drift, and the operator wanted one.
  • 2026-10-14 · Every venture is static, on Teahub pages, at <slug>.yolopreneur.lol. No Cloudflare. *.yolopreneur.lol → green-tea-pages (wildcard cert); the apex /<slug>/ redirects to the subdomain; a venture ships as ventures/<slug>/ on gh-pages and nothing else is provisioned. The nine Workers (time-<slug>) and their custom domains were deleted with the Workers token on hb-5; backend is gone from ventures.json; time.js degrades without /api/. cloudflare.py/worker.js leave with the venture.py port (time#48). Kept for now: time-aicharts (the lab, no static twin — rebuilds on prod per #5) and jev-prefab-decider (not Time's). D1 databases could not be deleted with that token (no D1 scope); the Workers that used them are gone, so they cost nothing but still exist. Why: one format for all sixteen, and no Cloudflare spend.
  • 2026-10-14 · Second product secret on prod: ANTHROPIC_API_KEY, user-level on teahubxyz (Actions secret shared by every repo teahubxyz owns; taken from Boring's delivery on hb-5, never printed; the Gitea token used to set it was minted on the Fly box with write:user and revoked within minutes). With it: Time gets Claude seats (haiku, sonnet, opus, fable) through the Claude API's OpenAI-compatible layer, TIME_WORKER=claude:sonnet, and a spend governor: a ledger in the repo (time/ledger/<month>.jsonl, one line per model call with tokens and USD), TIME_DAILY_USD=5 and TIME_MONTHLY_USD=100 as user-level variables; a daily breach opens an andon issue and waits for midnight, a monthly breach also flips TIME_MODE to dry-run. Venice stays wired but is no longer the worker. Why: the operator's Max plan outruns manual use and the factory should run on API credits unattended; Venice burned $20 of $75 before anyone noticed, so the cap is counted in dollars by code, not in launches, and no model decides whether to spend.
  • 2026-10-14 · Time is a Gitea user: time, with its own token. Gitea 1.24 lets the Actions identity (gitea-actions) comment and label, and git-push with the task token, but never open or merge a pull request ("user must be a collaborator") or write through the contents/branches API (pre-receive denies it). So Time's writes go over git push, and the two PR calls (open, merge) go out as time: a bot user, collaborator (write) on teahubxyz/time, token scoped write:repository,write:issue, stored as the repo secret TIME_TOKEN, minted on the Fly box, never printed. Operator approved. Reading of the standing rule: Time is an agent and this is Time acting as itself, not a shared service token; nothing else uses TIME_TOKEN. Also today: the daily planner astra.py/astra.yml (one Opus call a day, queues ≤2 cards, writes to the "Astra log" issue; off until TIME_ASTRA=on), and the first Claude-seat card, time#49, is the one that found all three Gitea walls. Why: without an identity that can hold a PR the council has nothing to review and nothing ever lands.