4.7 KiB
gitea-saas-core — Monetization Core
Two products, one billing core. The lane system (issue -> branch -> PR -> review -> merge) is the delivery machine; this service is the client-facing moat around it.
Roles
- Billing core (shared): clients, ledger, holds, Stripe top-ups, credits. Pure Python + SQLite. No knowledge of lanes or Gitea.
- Factory front-end (option 1): client repo provisioning, issue intake with acceptance criteria, funding gate at intake, delivery receipts on merge.
- Eval farm front-end (option 2): batch task intake -> internal
eval-taskissues, results table, emitter (webhook or file drop). - Review gate (shared, THE quality moat): automated PR reviewer. Parses the linked issue's acceptance criteria, runs tests/build/diff checks, posts APPROVE/BOUNCE verdicts, auto-merges per client tier policy. Replaces the operator's manual review for client work.
Billing invariants (fail-closed)
- balance = credits - debits - active holds (never negative)
- every mutation is idempotent via an
idempotency_key(Stripe event id, intake uuid, etc.) - holds expire (default 30 min) and release back automatically
- debit happens exactly once per delivered issue (key = gitea repo + issue number + merge sha)
Gating model (intake-side, no dispatcher coupling)
A client's work NEVER becomes a Gitea issue while unfunded. submit_issue checks the
client balance: funded -> issue created immediately; unfunded -> row in pending_issues
queued locally. unlock(client_id) flushes the queue to Gitea with the tier's priority
label. The dispatcher scans all repos but only ever sees funded work — zero changes to
the lane/dispatcher scripts, no label-gating in the survey, no coupling between the
monetization service and deployment infrastructure.
Review gate
reviewer.py polls PRs (or is webhook-driven) that carry in-review. For each:
- Fetch linked issue, parse the acceptance-criteria checklist.
- Checkout head in a worktree; run the repo's test command + build +
git diff --check. - Optional secret scan (reuse worker-ops patterns).
- LLM review pass against the acceptance criteria (files allowed per issue spec, scope-creep check, correctness, edge cases) — evidence-quoted verdict.
- Post verdict comment: APPROVE / BOUNCE (with findings) / NEEDS-GUIDANCE (cannot verify).
- Policy per client tier (config): auto-merge on APPROVE+green for gold tier; manual confirm for standard tier; BOUNCE routes back to the implementing lane.
The review gate applies to client repos; the operator's internal fleet can opt in per
repo. Bounced PRs re-enter the normal lane flow (existing bounced label machinery).
Module ownership: gitea_saas/gitea_client.py
gitea_saas/gitea_client.py (the Gitea API client seam) is owned by issue #4
(factory intake) and lands once, from #4's PR. Every other front-end — review gate,
eval farm, dashboard, delivery — imports that module; new Gitea API surface is
added by extending it under #4's scope, never by writing another client. Open issues
whose allowed-files set grants gitea_saas/gitea_client.py to any issue other than #4
are scoping errors (issue #21 owns this rule).
Layout (target)
gitea_saas/
billing_core.py # ledger schema + operations (pure, no HTTP)
server.py # Flask app (billing API + dashboards)
stripe_client.py # Stripe adapter (mockable seam, env keys)
gitea_client.py # Gitea API client (mockable seam, env token)
intake_factory.py # option 1: provision client repo + funded issue creation
intake_eval.py # option 2: eval batches -> issues + results + emitter
reviewer.py # review gate: acceptance parse, tests, verdicts, policy
delivery.py # merge event -> receipt debit + comment + lock-check
config.py # tier config, rates (loaded from env/config, never hardcoded)
schema.sql / db.py # SQLite migration
tests/ # pytest, hermetic (mock Stripe + Gitea, tmp SQLite)
dashboard/ # html/static if needed (mirror botmanager pattern)
Environment (provided by operator at deploy time, mocked in CI)
- STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET (optional: absent -> mock mode)
- GITEA_BASE_URL / GITEA_TOKEN (factory/eval/reviewer)
- SAAS_DB_PATH, SAAS_API_KEY (X-API-Key auth), SAAS_PORT
Tier/rate config (operator sets later; centralize in config.py)
Gold: priority p1, auto-merge on reviewer APPROVE+green. Standard: p2, manual merge confirm. Rates per issue, config not code. First client onboarding is manual; self-serve (Stripe checkout -> auto-provision) is the stated next step after core + both front-ends land.