Both are Django apps, and both pin `label` in apps.py — "stamp" and "review". django_migrations keys a row on (app_label, name), and app_label comes from that pin and not from the import path, so moving the directory does not orphan a single migration. All six stay where they are, untouched. products/stamphog -> products/stamp name = products.stamp.backend products/review_hog -> products/review name = products.review.backend StamphogScene/ -> StampScene/, and stamphogSceneLogic.ts with it 189 files follow. An earlier attempt at this move failed twice for reasons worth recording: `git mv A B` where B exists puts A INSIDE B, and a stray products/stamp/node_modules from an abandoned run was enough to make B exist — the app ended up nested two deep. And `git ls-files` does not list a file moved with plain mv and not yet staged, so a pass over "every tracked file" silently skips it; that is how StampScene.tsx kept importing ./stampSceneLogic after the file beside it had been renamed back. bin/check-imports-are-committed: all first-party imports across 15,843 files resolve. Co-authored-by: Hanzo Dev <[email protected]>
3.1 KiB
Stamp
Approve-first PR review: an LLM reviewer that runs deterministic gates plus a scoped review over a pull request and, when the policy allows it, posts an actual GitHub approval — not just comments. Repos opt in per-repo; everything else stays untouched.
Two runtimes, one engine
The review engine lives in tools/pr-approval-agent/ and runs in two places:
- GitHub Action — runs in the repo's own CI with the repo's own secrets (
review_pr.py). - Hosted (this product) — a GitHub App delivers webhooks here; reviews run in an isolated Modal sandbox with per-run minted credentials (
review_local.pyconsumes a pre-fetched context, no GitHub token inside the sandbox).
Hosted flow: webhook → Celery (backend/tasks/tasks.py) → Temporal (backend/temporal/workflow.py) → sandboxed engine → verdict posted back (post_verdict). The workflow dismisses stale approvals first, waits out other in-flight reviewer bots, then reviews.
There is one non-webhook entry: self-driving inbox PRs. When a self-driving Inbox implementation run opens its (bot-authored, draft) PR, review's inbox receiver calls the queue_inbox_pr_review facade — gated by the assigned reviewers' per-user stamp_review_inbox_prs toggles on Review's settings (any opted-in reviewer is enough) — and the initial review runs while the PR is still a draft so the verdict is ready at Inbox triage time. Later pushes re-review through the normal webhook path via a positively identified carve-out (task linkage through the tasks facade, toggle re-checked through facade/inbox_hooks.py); every other bot author stays refused at every layer. See AGENTS.md § the self-driving carve-out.
The digest
Independently of reviews, a repo can enable a daily Slack digest of its merged PRs (backend/tasks/digest.py): merges are stamped with an audience (author's GitHub team, or a channel the repo declares under digest: in .stamp/policy.yml), summarized with a small model, and posted per channel. Review-enabled repos digest only stamp-approved merges; digest-only repos (review off) digest every merge.
Configuration
Per-repo settings live on StampRepoConfig (synced via the GitHub App install flow, managed in the Stamp scene): review on/off, review mode (auto vs trigger label), digest on/off. Review policy (gates, deny-lists, tiers, ownership) is read from .stamp/policy.yml on the repo's default branch — never from the PR head — layered over hosted defaults in backend/logic/policy_defaults/.
Security model, in one paragraph
The sandbox runs an LLM over untrusted PR content, so it holds no long-lived secrets: it gets a per-run OAuth token (scoped to llm_gateway:read + the server-mint marker) that only works against the gateway's stamp route, egress is fenced to an explicit domain allowlist, posted bodies are scrubbed and markdown-image-neutralized, and approvals are governed by a strict supersession protocol so no approval survives events it shouldn't (pushes, re-reviews, repo disable). Details and invariants: AGENTS.md.