Agency (Projects, Deals, Ops Snapshots)
The Agency group is Minute.tech's own operations record: every client we talk to, every project we build or run, every estimate, and the measured monthly numbers behind the plan pricing. It replaces the file-based core-tech repo. Clients live in the existing Clients (CRM) table, extended with pipeline, contacts, links and Stripe billing columns; Projects, Deals and Ops Snapshots are new tables under Admin > Agency.
The Agency tables share the Site Config > CRM switch with Clients. Until a root user turns CRM on, none of these tables are visible to anyone.
Rule zero: never invent a number
Pricing, hours, traffic, cost and dates are either measured or empty. Every measured value has a sibling measured field that says where it came from and when it was read ("Stripe dashboard, read 2026-08-22"). A number without a source is not trusted. Leave the field blank rather than guessing; agents and the month-end review both read these rows as truth.
Clients: pipeline, not status
The record status (live / draft / archived) is only admin visibility. The relationship state is pipeline:
- prospect - In conversation, nothing paid. An estimate may be out. Last activity is the date of the last real signal.
- stale - A prospect with no activity for 60+ days. A clear "no" is lost, not stale.
- active - Paying us (Stripe subscription or recent invoices) or currently being served.
- paused - Relationship exists, nothing happening; a next phase is waiting on them.
- churned - Was a paying client, no longer.
- lost - Prospect that declined or went elsewhere. Keep the row so we remember why.
- internal - Us (minute-tech).
Promote prospect to active when the first payment lands in Stripe. Move active to churned when the subscription is cancelled and no invoice has been paid in about 90 days; record last payment. Every client has a slug that matches its Drive folder, its ops-probe key and the project rows that point at it.
Billing comes from Stripe, deals come from estimates
The billing columns on a client (subscription, lifetime spend, refunds, last payment, open and past-due invoices) are refreshed every night from the Stripe API by the stripe-sync worker and carry a billing measured stamp of the form "Stripe API, 2026-09-08". A client is matched by its Stripe Customer ID, or by its Stripe email when exactly one Stripe customer has that address (the id is then filled in so next time the match is explicit). Clients that cannot be matched are listed in the worker's report, never guessed; set the Stripe Customer ID by hand to include them. The worker writes one Ops Snapshot (source stripe) per matched client per month with the plan's monthly revenue. Owed (not yet invoiced), unbilled balances, one-off payments and deals are never touched by the sync; they come from estimates and hand entry. Never mark a client active on a ClickUp folder alone.
A Deal is one estimate sheet from Drive Ops/Accounting/00_Estimates. There is no separate invoice document: the estimate is the bill, paid 50% before start and 50% before launch after a named discount (REF, FRIEND, FAM, Package, ARTS, EARLY_ADOPTER). A single Stripe payment is therefore usually half a deal; record what was paid and what is still owed on the deal, and mirror the not-yet-invoiced half in the client's owed (not yet invoiced) column.
Projects: tier and stage
A client is an organization; a project is one deliverable or app, usually a repo. A client can have several projects across tiers.
- tier - client (a service build delivered to a client), core (an app we own and operate), lab (an experiment). Graduation from lab to core is a tier change, once the project's own written graduation criteria are met.
- stage - idea, proposal, building, beta, active, maintenance, on-hold, archived. Archived rows stay so links never break. Every stage change appends a status-history entry with the date and why.
- verdict - build-now, build-next, park, kill; set after the proposal review. score is the weighted /85 from the idea-analysis rubric.
- plan sold - the plan name, price and hours for this site. Private: hidden from roles without update access.
- traffic, cost, margin - the latest measured values; each month's figures are Ops Snapshot rows.
- PLAN / PROPOSAL - the long-form documents, in Markdown, on the project row itself. There is no separate documents table. The proposal follows the idea-analysis playbook (5 to 10 pages; search ClickUp, Drive, GitHub and the web first).
Only public projects, and only their listed public fields, may reach the public site through a linked Experiment (/lab) or Case Study row. Commercials never go there.
Ops Snapshots: month-over-month
One row per month, source and project (or client): cost from ops-probe, visitors from Vercel Web Analytics, revenue from Stripe. Rows are created by the local ops/ tools and by hand, never invented. The latest values are also copied onto the project for the table view. Comparing months is a filter on this table.
Roles
Supers see everything. Four custom roles are created by the loader and can be edited under Admin > Roles:
- Ops (accounting) - clients incl. billing, deals, projects incl. plan price and cost, snapshots. Read and update, no deletes. Lab analysis fields hidden.
- Sales - clients without Stripe internals, deals, projects with plan price but no cost, margin or traffic.
- Design and Contractor - read-only on projects that list them under assignees; no plan, cost, margin or internal summary.
Money, hours, cost and contact columns are flat on purpose: role hidden-fields and the private flag only apply to top-level columns, never to keys inside a JSON field.
Agents and the External API
Tools inside this repo (records:load, ops:probe) write straight through Prisma with the app's own environment and need no key. Claude agents working in other repos read a project's PLAN and log notes through the External API (/api/v1/data, Bearer key) instead. One API key, minted by a super under Admin > API Keys once Site Config > Developer > API is on, with exactly these data permissions:
- project - read, read-many, update. Agents read planMd, stage and links, and append a status-history note when they change something material. Plan price, cost, margin and hours are private columns and are stripped from every external read and write, so the key cannot see or set them.
- opsSnapshot - read-many only. Agents may quote last month's measured cost or traffic; ops-probe and stripe-sync own those rows.
- client - blocked at the table level for every API key. Contacts and billing never leave through this door. deal is simply not granted.
The key lives in each repo's local, git-ignored .env as MTECH_API_URL / MTECH_API_KEY; deleting the key row revokes every agent at once. The AGENTS.md snippet to paste into other repos, the exact requests, and the mint checklist are in docs/agency-agent-access.md in this repo. Until the key exists, agents skip that section and say so.
Loading and tooling
- pnpm records:check - validates the file records under ops/records/ and prints the pipeline with MRR, owed and unbilled totals.
- pnpm records:load:dev (or :prod) - upserts every client, deal, project and the four roles from those files. Idempotent; add -- --dry-run to report without writing. Requires the Agency migration to be applied first.
- pnpm ops:probe:prod (or :dev) - measures Neon cost locally and writes one Ops Snapshot per matched project and per client rollup for the month, refreshing each project's cost columns, directly in the database with the app's own env. Re-running a month updates the same rows. Add -- --dry-run to review the writes first. Unmatched Neon projects and clients are listed, never guessed; add the Neon name to the project's Neon projects field, or set the client's ops-probe key, to include them.
- /api/workers/stripe-sync - the nightly Stripe billing refresh (03:00 UTC, with the compliance cron). A super can trigger it from a signed-in tab with ?dryRun=true to see what would change without writing; the response lists matched, unmatched and ambiguous clients and any per-client errors. Monitored by the stripe sync BetterStack heartbeat.
- ops/betterstack, ops/soc2-* - local terminal tools with their own .env. Their secrets never go into Vercel.
- ClickUp stays the system of record for tasks. The app stores folder, list and task ids only. Drive stays the file store; the app stores links.
Project GitHub onboarding
Project GitHub onboarding requires a local Super session, the configured two-factor and step-up checks, and GitHub organization-owner and repository-admin authorization. Approve a live client or core project explicitly as House or Camp, then observe and resume its durable setup job. Lab, archived and otherwise ineligible projects do not receive release access.
Repository approval stores immutable GitHub organization and repository IDs separately from these editable records. A repository URL, project note, tier change or ordinary table permission cannot authorize provisioning or release tokens. Agents may receive a separate, revocable credential for one approved project's onboarding requests; each request still requires operator approval. The existing Agency notes API key cannot perform GitHub operations.
The page reports missing configuration and incomplete readback as blocked. Enrollment does not switch workflow credentials. The isolated release service also requires an approved workflow digest, exact main-branch CI and GitHub Actions identity. Archiving a project revokes its approval and request grants; reactivating the record does not restore them automatically.