87 lines
4.7 KiB
Markdown
87 lines
4.7 KiB
Markdown
# 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-task` issues,
|
|
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:
|
|
|
|
1. Fetch linked issue, parse the acceptance-criteria checklist.
|
|
2. Checkout head in a worktree; run the repo's test command + build + `git diff --check`.
|
|
3. Optional secret scan (reuse worker-ops patterns).
|
|
4. LLM review pass against the acceptance criteria (files allowed per issue spec,
|
|
scope-creep check, correctness, edge cases) — evidence-quoted verdict.
|
|
5. Post verdict comment: APPROVE / BOUNCE (with findings) / NEEDS-GUIDANCE (cannot verify).
|
|
6. 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.
|