Commit Graph
30 Commits
Author SHA1 Message Date
Claude a06682f40a Taskmaster: a checkout per deal, a seat holder the app can find, a return that closes the deal
ci / test (pull_request) Successful in 46s
heartbeat / tick (pull_request_review) Successful in 9s
The reviewer objected that three paths the finish line depends on look broken. The review reached me cut off
partway through its first point ("Checkout re…"), so rather than guess at points two and three I ran each DONE
line against Whop's real semantics instead of the test doubles. Three paths failed, and they are the three fixed
here; the probes are now tests, so each one fails if the old behaviour comes back.

**A reused variant was sending the first deal's metadata.** `checkout()` returned the stored configuration whole
for any later deal that landed on the same price, so ann's configuration — with ann's deal, thread and member in
its metadata — was handed to bob. Bob's payment would have routed to a deal that was already paid: the webhook
answers `already`, bob's own deal never closes, his thread never leaves haggle mode. The card asks for variant
reuse, and reuse is what the *variant* is for, so the plan is still made once per price and named by `plan_id`
thereafter; the configuration on top of it is per deal, because its metadata is the whole routing mechanism.
`writing()` now runs before the store is read too, so a known variant is not a way around the live guard.

**A seat holder could never be found.** `whoIs` asked Whop what the member holds, and a seat holder holds nothing
— the pack is the lead's membership — so coach mode for a pack only ever worked for the buyer who bought it. The
flow test hid this behind a fake whose `membershipsFor` answered every user with every membership; it now answers
the way Whop does. `packIndex` (seats.js) remembers which pack a username was seated on and the server reads each
pack it names straight from Whop, so the index says where to look and Whop's metadata still says whether the seat
is there: a stale pointer costs one read and grants nothing, which a test asserts. The other half of the same
break: memberships answer to a Whop user id and seat lists are written in usernames, so a thread now carries both
names and `whoIs` uses the right one for each question.

**Back from checkout, the deal never closed.** The card allows a membership check on return as the other road a
confirmed payment arrives by, and the mode did switch — but nothing called `paid()`, so the deal stayed agreed,
was never logged, and showed no share card. Worse, `/api/walkaway` then logged a paid deal as a *walkaway*: a
closed sale recorded as a lost one, in the log the card calls the price discovery. `/api/return` now closes and
logs it, once, on a membership whose metadata either names this deal or names none; a membership from an earlier
deal does not close this one.

Two Whop writes that could take a member's turn down with them, while I was in there: a seat holder's usage write
refused by the live guard (which is every day of card #66) 500'd a coaching turn the member had paid for, and now
reports `usage_noted: false` instead; a refused checkout 500'd the accept turn and lost the thread's own record of
it, and now costs the quote card its button rather than the deal, with `/api/agree` minting it once Whop will
take it. And the seats routes were open to anyone holding a membership id — a paying org's staff list, readable
and writable by any caller — so they now take a thread, the same identity every other route runs on, and answer
403 to anyone but the buyer.

Nothing created on Whop, nothing bought: the live guard is intact and stronger, and the suite (83 tests, up from
75) still runs against an injected `fetch` with simulated signed webhooks. Since the reviewer's diff ran out
inside `dealdesk.test.js`, the README's layout now maps every test file to what it covers, so the half of the
suite that falls past a diff budget can at least be read about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M6pq5GUaS75LDV7WDaddA

Closes #66
2026-10-20 16:08:54 +00:00
Claude a1ea50f816 Taskmaster reroll: haggle to a real Whop checkout, then coach
ci / test (pull_request) Successful in 47s
heartbeat / tick (pull_request_review) Successful in 12s
`taskmaster/`: the factory's Whop coaching app rebuilt so the money is real. The old
version switched to coaching when the member typed "Paid $X via ..." and its webhook
handler only logged invoices.

The character haggles; the deal desk decides. The character never sets a price: it calls
one tool, quote(tier, price, reason), and dealdesk.js answers accept, counter or refuse.
Four rules — the opening is the list price and the desk never quotes above it or walks a
price back up; a quote under the floor is countered at the floor whatever reason came with
it; a step down needs one of four reasons the member gave (scope, urgency, commitment,
pay_now), each worth a slice of the anchor and each spendable once; three rounds, the
opening included. Two refusals are about honesty and spend no round: a reason that reaches
for the member's profile, following, employer, means or country, and invented scarcity or
a fake deadline.

The app decides the mode, never the model. One sheet, two prompts, and the server picks
from whether Whop says the member has paid — a valid membership on prod_EZ7UkE4pkW04U or a
place on a valid pack's seat list. Typing "Paid" changes nothing and a test proves it.

Also: the sealed quote card the model can't edit, the share card, the deal log (openings,
counters, final price, rounds, reasons, outcome, fee and net; never a word the member
wrote, validated so prose can't get in), seat packs in membership metadata with
rate-limited swaps and a buyer view of seats and usage but no chats, the signed payment
webhook, the three experiments bucketed by user, and the Venice voice with one tool, a
per-member limit and no uncensored default.

Nothing created on Whop, as the card requires: the Whop client refuses every write unless
constructed with live: true, and the 75 tests run against an injected fetch with simulated
signed webhooks.

What is missing, and why:

- teahub.xyz is blocked by this container's network policy, so teahubxyz/dealdesk and its
  SKILL.md could not be read. The rules are implemented in taskmaster/src/dealdesk.js
  behind the same seam (openDeal / quote / agree / walkaway), so swapping the import in is
  a one-file change. Someone with network access to Teahub should reconcile the two.
- docs.whop.com is blocked too, so four things stay unconfirmed and each is one line to
  correct: daily billing for the daily and checkins tiers (checkout() refuses to create a
  renewal tier until it is passed dailyBilling: "confirmed", so nothing is built on the
  assumption), the field that keeps an inline plan off the product's public page (HIDDEN in
  whop.js), the webhook's header names and whether the signed string is the body alone or a
  timestamp joined to it (both shapes verify; the names are constants), and how Whop's iOS
  rules treat a one-off checkout, which would change what these tiers may be priced at on a
  phone.
- Where the code lives was the operator's call. It is a top-level taskmaster/ here, beside
  havre/, since GitHub access for this session is scoped to teahubxyz/time alone. Moving it
  to teahubxyz/taskmaster or a shared Whop-apps repo is a directory move; nothing outside
  taskmaster/ depends on it but the one test hook.
- No workflow change is needed: CI's `python3 .github/time/test_time.py` step now runs the
  suite, as it already does for havre/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KUi5SZq4GBkdHQ2TdhZ3r

Closes #66
2026-10-20 14:15:26 +00:00
Claude c54cbf2596 venture backends: accounts with passkeys
Each venture's Worker can now sign people up and in with passkeys (WebAuthn):
no passwords, no email, no OAuth app, nothing to configure. The relying party
is the venture's own host and the session a __Host- cookie (HttpOnly, Secure,
SameSite=Lax, 30 days), stored hashed in the venture's D1 database. ES256 and
RS256 keys; challenges are single-use and expire in five minutes; origin,
relying party, user presence and the signature counter are checked; auth
endpoints refuse cross-origin calls. Items saved while signed in are recorded
as that person's, and /api/me/items lists them.

time.js gains Time.accounts, user, signUp, signIn, signOut and mine; builds
are told to use them for what the paid tier promises and never to put sign-in
in front of the free product. test_auth.mjs runs the whole ceremony against a
real SQLite and a software authenticator, including replay, phishing origin,
another site's passkey, a cloned counter, a tampered signature and a duplicate
registration; CI pins node 22 so it runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wyb3QQDESFkosKRXzrXqwr
2026-10-12 18:46:56 +00:00
Claude cb687873d3 venture backends: Gemma 4 by default; read either answer shape from Workers AI
The first default model had been retired on Workers AI (5028 "deprecated"),
found by calling the live backend. Default to @cf/google/gemma-4-26b-a4b-it,
and accept both the older { response } and the OpenAI-style choices answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wyb3QQDESFkosKRXzrXqwr
2026-10-12 11:05:52 -07:00
Claude 134a03ce41 venture backends on Cloudflare: a domain, a Worker, a database and AI per venture
With CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID, each venture gets its own
Worker (time-<slug>) with its own D1 database and Workers AI. The Worker serves
the venture's GitHub Pages folder under <slug>.<TIME_VENTURE_DOMAIN> (or
workers.dev) and adds a small API: share links stored in D1, and a
rate-limited small model. The page reaches it through time.js, which Time
writes next to index.html and the build may not edit; every call degrades to
null, so products still work on Pages alone and in the headless checks.

The backend is deployed when a venture is picked, so the plan, the signal
issue and the page all carry the venture's own domain. A Cloudflare failure
never holds a venture: it launches on Pages and the chores retry. The chores
also backfill older ventures and redeploy every venture when worker.js or its
settings change (a revision hash), a couple per tick; update.yml carries the
new files. The doctor checks the token. Offline tests cover the provisioner,
the loop with and without Cloudflare, the backfill and redeploys, and the
worker itself (node, against a fake D1, AI and Pages).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wyb3QQDESFkosKRXzrXqwr
2026-10-12 10:50:13 -07:00
Claude 23d6383060 Worker: runs can keep the files they generate
grok's Docscope round generated a real sample .docx and the finished page
with scripts inside run, then tried to cp them into place: each run starts
from a fresh checkout, so none of it reached the branch and the round
burned its steps. The run tool now takes keep: [paths] and commits exactly
those files afterwards (inside the checkout, not workflows, up to 2 MB,
at most ten), and the rules say plainly that everything else a run does is
thrown away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:59:41 -07:00
Claude f64a48300d Ventures: the product is the page
Taste test on the relaunched MDecks: the notplanned launch-page template
and its rotation of print styles turned the working tool into a section
under a big hero, and every page looked like the same magazine. Now:
- the brief puts the tool in the first screen under a slim header (name,
  a few words, the one signup button); how it works, why, pricing and
  limits sit quietly below;
- the look follows the product (a slide tool should feel like a slide
  tool), dark or light; the design rotation is gone;
- headless Chrome fails a page whose product starts below the top third
  of the first screen, at phone and laptop size;
- an earlier version can be kept on purpose (prior_note), so MDecks can
  be rebuilt from the first design people liked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:55:28 -07:00
Claude 35860f4bf8 Worker: a command that never exits is stopped and reported, not a crash
Docscope's fix round ran 'python -m http.server' to test its page; the
600s timeout raised and took the whole round down. The run tool now stops
a command after TIME_RUN_TIMEOUT (default 180s) and tells the model to use
one-shot commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:46:13 -07:00
Claude 3bf9fbe94d Ventures: an honesty review of the product code, after the cheap gates
Docscope's next fix round dodged the phrase check: 'This would be the real
implementation using DecompressionStream. For now, we'll create a realistic
representation...' and still returned canned parts. Phrase matching can be
talked around, so the inference tier now reads the code: a seat
(TIME_VENTURE_JUDGE, default grok) answers whether the output depends on
the user's input and lists features the page claims for the free product
that the code doesn't implement (paid tiers and stated limits don't count).
It runs only after the deterministic and browser gates pass, and its
absence (outage, no verdict) never blocks a launch.

Checked against the live pages: it passes MDecks and catches Docscope
('parseOfficeFile always returns createRealisticParsedParts(file), which
hardcodes identical XML regardless of input').

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:43:19 -07:00
Claude 67f7b4d2d9 Ventures: a failed fix round escalates to a stronger worker (TIME_VENTURE_ESCALATE, default grok 4.7)
The cost cascade, applied to fixes: the cheap worker gets the first round;
when its page still fails the checks, the next round goes to a stronger
model instead of the same one again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:32:40 -07:00
Claude 16cd9a6306 Venture plans: keep prices short (no parentheticals in the pricing card)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:31:31 -07:00
Claude c287a2acf6 Ventures: catch output the code admits is simulated
Docscope's fix round added 19KB of script that renders on load and passed
the scripts-on/off check, but the code says 'Simulate processing (in a real
implementation, this would parse the file)': a dropped file gets a canned
leak report. For a privacy tool that is worse than no tool.
- the checks flag the phrases generated code uses for canned output;
- the brief and fix rounds say the sample must be real input through the
  same code path as a user's input, and point at browser built-ins
  (DecompressionStream for zip, DOMParser for XML).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:30:20 -07:00
Claude 4fc82f0f9a Venture brief: build the product first (app.js), then the page around it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:23:31 -07:00
Claude 0f74a7d7c4 Ventures must ship a working product; launch drafts are checked against the build
Docscope launched as a facade: a well-shaped page with no script at all.
The checks verified structure, never that the product works. Now:
- deterministic: the page and its local scripts must carry real code;
- headless Chrome loads a scripts-off twin next to the live page and
  compares #try: if the product renders nothing of its own on first load
  (a preloaded sample run through the real code), the page fails;
- fix rounds get the product description, so 'no working product' is
  fixable in a round;
- at launch the chair rewrites the launch drafts against the README and
  the page as built, with no invented people, stories or results (a draft
  had made up a leaked-client-file anecdote); LAUNCH.md gets the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:21:21 -07:00
Claude 0c569ca5c2 Docs: Venice-first setup, and the venture loop in the setup skill
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:14:48 -07:00
Claude 79e38fa894 Ventures wait out provider outages; the worker falls back to another model
- A venture stage that fails because Venice stays overloaded now posts one
  'waiting for capacity' note and retries every tick without spending one
  of its three strikes (MDecks and Docscope were one 429 from being closed).
- The worker hands over to TIME_WORKER_FALLBACK (default grok build) when its
  model stays overloaded after the backoff retries.
- Fix: venture builds passed the worker spec with its venice: prefix, so the
  seat lookup missed and every venture was built by kimi's code model
  instead of the configured worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 16:04:58 -07:00
Claude 91b11019db Ride out Venice overloads: retry 429, 5xx and dropped connections with backoff
One overloaded reply in the middle of a worker run used to throw away the
whole build (the MDecks relaunch hit exactly that). Seats and the worker
now retry up to three times (15s, 60s, 135s) before giving up; a 4xx like
a bad key still fails at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 15:58:39 -07:00
Claude cdfe4c7199 Venture loop: launch products, not pages
Ported from the notplanned venture template:
- launch round: every board seat writes a launch plan (positioning, first
  users, pricing, channels, 7-day success and kill lines); one seat
  synthesizes them into LAUNCH.md with where each idea came from
- launch page brief: one primary call to action in the hero, the working
  product preloaded with a sample, why-this-exists with real star counts,
  pricing (free plus at most two honest early-access tiers), honest limits,
  a design direction that rotates and never repeats the last six
- signups through a GitHub issue form prefilled with venture and plan;
  each signup is thanked on its own issue
- checks: deterministic gates, then headless Chrome measuring at exactly
  390x844 and 1280x800 (horizontal scroll, button below the fold, JS
  errors); two fix rounds, then andon with needs-human and /time retry
- a contact-sheet screenshot of phone and desktop on the card
- 7-day decision on distinct people: v2 card, kill, or one more week then
  park; a next version counts only signups after its own launch
- scheduling: a card that is measuring never blocks the others; one new
  card a day; a tick runs light stages back to back and one worker run
- the studio index at /ventures/ is generated from ventures.json

Security: the worker's shell no longer sees tokens or keys, its checkout
keeps no token in .git/config, and workflows don't persist credentials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 15:48:21 -07:00
claude 66c427eba4 venture: with several open cards, work the furthest-along one first
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 14:47:22 -07:00
claude 5a185facd3 Venture loop: ventures, not tools
Pitches carry a name, who it's for, a next version and a CTA. The board may
rank nothing and needs two seats behind a winner. Every venture gets a
`signal` issue (👍 = I want the next version) and a Want-more section on the
page pointing at it. After ship: a launch stage drafts posts for the chair
(never sent), and a measure stage 24h later queues an iterate card or parks
the venture. Seats that reply in prose get re-asked once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 14:37:23 -07:00
claude bae0bdc0ef venture: statuses permission for the worker's test run; dedupe board rankings
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 14:14:30 -07:00
claude fd23310271 venture: parse JSON out of fenced or chatty seat replies
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 14:09:52 -07:00
claude 439cf7d1cd Venture loop on GitHub: trending repos -> pitches -> board -> build -> Pages
venture.py runs one stage per tick on a `venture` card: scout (GitHub search,
repos with the most new stars this week), pitch (each seat proposes one static
page product), board (Borda over seat rankings), build (the Venice worker on
gh-pages), ship (GitHub Pages). TIME_VENTURE=on starts it; off by default.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 14:05:41 -07:00
claude 8454c8f7a7 heartbeat: statuses: write for the worker's test-run check
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 13:00:57 -07:00
claude 3c44c60554 retry: count a /time retry posted in the same second as the last beat
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 12:57:11 -07:00
claude adae855675 landing: verdict window from PR creation; worker test runs become commit statuses
GitHub skips pull_request workflows for commits made with the Actions token,
so a Venice worker's own test run is recorded as a commit status and counted
as the PR's check. A verdict posted in the same beat as the pr beat now counts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 12:53:40 -07:00
claude c5b20a8654 heartbeat: 20 min so a Venice worker can finish a card
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 12:47:09 -07:00
claude c3d029cab9 promote_next: tolerate a card another beat already promoted
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 12:41:12 -07:00
claude 750d9a9fad Venice seats can be the worker: TIME_WORKER=venice:<seat>
A seat gets hands through the repo's own Actions token: list/read/write files on
branch time/card-N via the contents API, run shell on a checkout, open the PR.
Same contract as the Cursor worker (PR body says Closes #N), so review, landing
and next-card are unchanged. Workflow files are refused to the seat. Needs only
VENICE_API_KEY; no Cursor key.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 12:38:00 -07:00
claude 889e93799e Hands-off Time: spec planner, auto-merge on Approve + green checks, next card, update workflow
- spec issues (label spec) become queued cards, parsed deterministically, seat fallback
- TIME_AUTOMERGE=on: squash-merge when the latest trusted verdict is Approve and all checks pass
- Object, red or missing checks, or protected paths go to needs-human
- merged card frees the slot; the next unblocked queued card gets ready and dispatches in the same run
- update.yml pulls the engine from teahubxyz/time into project repos via PR
- ci.yml runs Time's own tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb
2026-10-11 11:46:43 -07:00