18 KiB
18 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-giteascaled 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.lolWorkers 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/timeon 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 → migratetimewith 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/dockermust live on the volume). Labelsubuntu-latest,ubuntu-22.04→node:22-bookworm. Registered instance-wide asfly-iad-1. Smoke:teahubxyz/teahub.gitea/workflows/smoke.ymlgreen. Why: Actions on prod are a precondition for Time on prod. - 2026-10-14 · Time landed on prod (
teahubxyz/timenative, 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_TRUSTEDvariable, or collaborator, because the Actions token can't read permissions.venture.pyis 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_KEYonteahubxyz/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>/siteis served at the org's domain bygreen-tea-pages(Fly, stdlib static host, one shallow clone per site, pull on a Gitea push webhook or every 5 min; code inpages/). 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(+ vendoredhuman_vault.py),persimmons/extension,phast/app(+acp/);persimmons/siteandphast/sitelive 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, andmarketing-loopsis 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.jsonon time'sgh-pages, with asourcefield);teahubxyz/notplannedis 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 asventures/<slug>/ongh-pagesand nothing else is provisioned. The nine Workers (time-<slug>) and their custom domains were deleted with the Workers token on hb-5;backendis gone fromventures.json;time.jsdegrades without/api/.cloudflare.py/worker.jsleave with the venture.py port (time#48). Kept for now:time-aicharts(the lab, no static twin — rebuilds on prod per #5) andjev-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 onteahubxyz(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 withwrite:userand 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=5andTIME_MONTHLY_USD=100as user-level variables; a daily breach opens anandonissue and waits for midnight, a monthly breach also flipsTIME_MODEtodry-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 overgit push, and the two PR calls (open, merge) go out astime: a bot user, collaborator (write) onteahubxyz/time, token scopedwrite:repository,write:issue, stored as the repo secretTIME_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 usesTIME_TOKEN. Also today: the daily plannerastra.py/astra.yml(one Opus call a day, queues ≤2 cards, writes to the "Astra log" issue; off untilTIME_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. - 2026-10-14 · Correction: the temporary admin tokens were not revoked when this log said. The
ANTHROPIC_API_KEYentry says the token used to set the secret was "revoked within minutes"; it wasn't.gitea admin userhas no command that deletes a token, and the call meant to do it failed without anyone seeing an error. Four short-livedteahubxyztokens (for the user-level secret and variables, Time's repo variables, and the push that made PRs go out astime) stayed valid for 11 to 65 minutes, were never printed, and were deleted from the database by 20:23 UTC (access_tokenids 22, 23, 24, 26). The only token on prod now istime's own (TIME_TOKEN). From now on a temporary token is deleted withsqlite3on the Fly box, and the deletion is checked by listing the table before anyone writes "revoked". Why: this log is how the next agent knows which credentials exist. - 2026-10-14 · Every write Time makes, except comments and commit statuses, goes out as
time. Widens the entry above, which covered only opening and merging PRs. Gitea 1.24 also refuses the Actions identity label edits (removing a label is a 500); that crashedpromote_nextafter time#50 merged and stalled the queue.gh()now sends every non-GET call withTIME_TOKENexcept comments and statuses, which stay withgitea-actionsbecause Time's beats are that author's comments. Thetimeuser never counts as trusted, mention replies ignore it, andastra.ymlgets the token too (timec2055e8). Three more temporary admin tokens were used this evening, to read job logs and push the fix; each was deleted by id and the table checked (ids 27, 28, 29). Also found: Gitea's label events carry no label name, so labelling a cardreadydoesn't wake the heartbeat; promoted cards wait for the hourly tick. Why: one identity that can actually do the factory's writes, and the Actions identity kept for the two things it reliably does. - 2026-10-14 · The factory runs without the operator: Astra is on, and a steward merges engine PRs. The operator delegated merging and admin to Claude and approved both explicitly. Astra (
astra.yml, daily at 13:13 UTC, one Opus call) now labels its picksqueuedinstead ofready, so they line up behind the existing queue andpromote_nextruns one card at a time (time97b7667). Engine PRs (anything under.github/) still never merge themselves: Time marks them needs-human, and the Teahub steward, a scheduled Claude session (cloud, every 6 hours at :47 past 01, 07, 13 and 19 UTC, automatic approval), reads each one, answers the reviewer, and merges it only if CI is green, it does what its card's DONE line says, it touches no workflows, secrets or deploy, and it doesn't weaken the spend governor or the protected-path rule. The steward also works open andons (never by raising a cap or changingTIME_MODE) and repairs a failed heartbeat. Its admin tokens follow the rule above: minted on the Fly box, deleted by id, table checked. Tonight time#54 and time#56 were merged that way andTIME_ASTRAwas set (tokens 30, 31, 34, all deleted); tokens 32 and 33 belonged to another session and were already gone when checked. Why: the operator doesn't want to be the merge gate, and the engine still shouldn't change without someone reading the diff. - 2026-10-14 · SKILLS.md is the skills contract, the third root file beside
AGENTS.md(how to move here) andDESIGN.md(what good looks like): routes (task → one to three skills, named by path), a ledger (upstream ref and tree, version, what each skill writes) and belts (⬜ admitted, 🟩 katas beat no-skill on a named seat, ⬛ grown from our traces, 💤 retired once a seat no longer needs it). Skills are never left to auto-trigger, and no model decides which skill to load..agents/skills/README.mdnow points to it and AGENTS.md has the move. (The first skills README said Claude Code reads.agents/skills/; it reads.claude/skills/only.) Why: the Oct 14 skills research — unaided triggering fires about half the time, generic skills often add nothing on strong models, and the trust that holds comes from gates (evals, pinned refs, sandboxes), not prose. Time's seats are plain API calls, so routing has to live in a file every seat can read (#7). - 2026-10-14 · phast and persimmons re-ported from their canonical repos; phast runs. The port above copied
tea-mcp/srcsnapshots from Oct 9, older than the canonicalmatcha/phastandmatcha/persims. So phast.app came up in the old light design and persimmons.app on a holding page. Both canonical repos are now merged, with history, intophast/appandpersimmons/app, and their trees are the canonical ones. phast: Fly appphast-app(1 machine, volumephast_data) serves phast.app, my.phast.app and*.phast.appfrom that one repo: the pages are itsindex.htmlandmy/index.html, and the MCP atmy.phast.app/mcpsigns in with Teahub OAuth. The OAuth app is "Phast app", owned byteahubxyz, redirecthttps://my.phast.app/auth/callback; the client secret and JWT key exist only as Fly secrets and were never printed.ALLOWED_GITEA_USERS=matcha,chai. matcha's Phast app moved from hb-5. Closes phast/app#1. persimmons: pages serves persimmons.app frompersimmons/app's root (itspages/page.confsayssource=.). The API isn't on prod, so the vault reads "Service unavailable". persimmons/app#1 covers the port, including whether a product backend may hold an admin-scoped token: it creates vault repos and stores PATs as Actions secrets.phast/site,persimmons/siteandpersimmons/extensionare archived with pointers. Pages now serves extensionless HTML (/signin) as text/html. Why: the regression was a stale copy, so each product ships from the one repo its owner works in. - 2026-10-14 · The portfolio lists the operator's own products too, and phly.cloud is back. Teahub, Persimmons, Phly, Phast app, MacMCP and Time join the ledger at yolopreneur.lol (25 entries). They are listed like well-known.online:
urlis the product's own address,ventures/<slug>/index.htmlforwards<slug>.yolopreneur.lolthere, and a first-screen shot (960×600) sits beside it. phly.cloud had been dark since the edge went. Its site,matcha/phlywith history, is now orgphly, repophly/site, served by pages (cert issued, DNS moved, push webhook). The favicon pointed at an expired Boring blob link and is now the mascot itself. Still dark and not listed: supertestnet.lol, unicorn.ceo, and the*.teahub.xyzsites (maths, notplanned, dreams, oracle, greentea); their bytes are on hb-5 (~/sites, the old Gitea). Why: the operator asked for the products in the list; one list, same card for everything. - 2026-10-14 · Receipt: temporary admin tokens for the phast/persimmons/phly work.
teahubxyztoken 35 (fix-phast-20261014, minted on the Fly box for the pushes, the OAuth app and the repo archives) was deleted by id at 22:27 UTC, and the table listing shows only 25 (time). Token 36 (receipt-20261014) was minted only to push this line, then deleted the same way. Neither was printed. Why: the rule above.