Files
zeekayandHanzo Dev fad96e267f no hogs: the root config, the tooling, and the receiver baseline
.stamphog/ was a config directory at the repo root — policy.yml,
review-guidance.md — read by the PR review agent. It is .stamp/ now, and the 34
files that name it follow: the workflows, tools/pr-approval-agent, tach.toml,
the sandbox Dockerfile, bin/debrand.

One behaviour change worth naming rather than burying: the agent finds its own
previous PR comment by an HTML marker, `<!-- stamphog:v1 ... -->`, and that
marker is now `<!-- stamp:v1 ... -->`. Open PRs will get one new comment instead
of an edit, once, and then match again.

insights/test/setup_receivers_baseline.txt records registered signal receivers
by module path, so it names products.review.backend now.

stamphog: zero occurrences anywhere in the tree. review_hog: only inside
migration files, whose names are their identity in django_migrations.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:21:41 -07:00

221 lines
9.2 KiB
YAML

# Stamp merge-gate policy - see .stamp/README.md for how changes are
# proposed and reviewed. Keep pattern scalars single-quoted so regex
# backslashes survive YAML parsing.
version: 1
deny:
auth:
description: 'Authentication and authorization surfaces.'
rationale: "Only file paths hard-deny; titles surface as scrutiny flags. Past participles (authenticated/authorized) live in titles-only because as path patterns they hard-deny the wrong things (web analytics' authorized_urls.py is domain config, not the auth system). session/token/permission are path-only with tighter compounds because they match too broadly in titles and non-auth paths (SessionAnalysisWarning, tokenizer, permission helpers)."
match:
any:
- 'auth'
- 'login'
- 'signup'
- 'oauth'
- 'saml'
- 'sso'
- 'oidc'
- 'credential'
- 'password'
- '2fa'
- 'mfa'
- 'authentication'
- 'authenticate'
- 'authorize'
- 'authorization'
- 'two[_-]?factor'
titles:
- 'authenticated'
- 'authorized'
paths:
- 'session_auth'
- 'session_token'
- 'auth/session'
- 'auth/token'
- 'permission'
exempt_path_prefixes:
- 'products/warehouse_sources/backend/temporal/data_imports/sources/'
crypto_secrets:
description: 'Cryptography, secrets, and key material.'
rationale: 'key/secret/cert/signing are too broad for titles (key matches keyboard, hotkey, localStorage key), so they are path-only with compound patterns.'
match:
any:
- 'crypto'
- 'encrypt'
- 'decrypt'
- 'vault'
paths:
- 'secret'
- 'api[_-]?key'
- 'secret[_-]?key'
- 'private[_-]?key'
- 'signing[_-]?key'
- 'certificate'
- '\.env'
- '\.pem'
migrations:
description: 'Database and schema migrations.'
rationale: 'The migrations/ substring is load-bearing - it also catches rust *_migrations/ dirs applied by sqlx at deploy.'
match:
paths:
- 'migrations/'
- 'schema_change'
infra_cicd:
description: 'Infrastructure, CI, and deployment artifacts.'
rationale: 'routing and bare deploy are excluded on purpose: every historical match was app-level (DRF routers, message-routing tests, deploy-timing docs), never infrastructure. Narrow deploy literals (bin/deploy, deploy.sh, .github/pr-deploy) cover real deployment artifacts without the false positives.'
match:
any:
- 'terraform'
- 'kubernetes'
- 'helm'
paths:
- 'k8s'
- 'dockerfile'
- 'docker-compose'
- '\.github/workflows'
- '\.github/pr-deploy'
- 'iam'
- 'cloudflare'
- 'cdn'
- 'waf'
- '(?:^|/)bin/deploy'
- 'deploy\.sh'
billing:
description: 'Payments and billing.'
rationale: 'subscription is excluded on purpose: in this repo it means scheduled insight/report deliveries, not payments. Real billing surfaces still match via the other words.'
match:
any:
- 'billing'
- 'payment'
- 'stripe'
- 'invoice'
- 'pricing'
exempt_path_prefixes:
- 'products/warehouse_sources/backend/temporal/data_imports/sources/'
public_api:
description: 'Public API contracts and schemas.'
rationale: 'Changes to the published API surface need human review.'
match:
any:
- 'openapi'
- 'api_schema'
- 'swagger'
- 'public_api'
deps_toolchain:
description: 'Dependency lockfiles and toolchain/build files.'
rationale: "All path-only literal filenames. Manifests deliberately don't hard-deny: without a lockfile change they cannot pull in third-party code, and manifest scripts/hooks are guarded by the reviewer prompt and kept out of the T0 fast path. requirements.txt stays (pins installed code directly). .nvmrc/.tool-versions stay (they change the runtime for every CI job). Makefile/Dockerfile stay (they execute)."
# NOTE: partially code-sourced. The lockfile-name path patterns are derived
# in the loader from DEPENDENCY_ECOSYSTEMS (re.escape over every ecosystem's
# lockfiles) and spliced in ahead of the literals below - do not copy them
# here. Only the non-derived literal patterns live in this file.
match:
paths:
- 'requirements[-\w]*\.(txt|in)'
- 'Makefile'
- 'Dockerfile'
- '\.tool-versions'
- '\.nvmrc'
stamp_policy:
description: "Stamp's own policy files, engine, and gate inputs."
rationale: "Changes to the merge gate's own policy or engine always require human review - the gate cannot be trusted to approve edits to itself. Ownership sources (CODEOWNERS, owners.yaml, product.yaml owners) are gate inputs: an ownership edit changes which team the reviewer treats as owning future PRs, and owners.yaml/product.yaml would otherwise ride the .yaml allow-list to a T0 auto-approve (Jul 2026 review finding)."
match:
paths:
- '\.stamp/'
- 'AGENT_APPROVALS\.md'
- 'tools/pr-approval-agent/'
- 'products/stamp/backend/logic/policy_defaults/'
- 'tools/owners/'
- 'CODEOWNERS'
- 'owners\.yaml'
- 'product\.yaml'
allow:
path_patterns:
- 'docs/'
- 'README'
- 'CHANGELOG'
- 'LICENSE'
- 'CONTRIBUTING'
- '.github/CODEOWNERS'
- '.gitignore'
- '.editorconfig'
- 'generated/'
- '__snapshots__/'
extensions_only:
- '.cfg'
- '.csv'
- '.gif'
- '.ico'
- '.ini'
- '.jpeg'
- '.jpg'
- '.json'
- '.lock'
- '.md'
- '.mdx'
- '.png'
- '.rst'
- '.snap'
- '.svg'
- '.toml'
- '.txt'
- '.webp'
- '.yaml'
- '.yml'
size_gate:
max_lines: 800
max_files: 30
tiers:
t1_subclasses:
T1a-trivial:
max_lines: 20
max_files: 3
breadth: 'single-area'
T1b-small:
max_lines: 100
max_files: 5
breadth: 'not-cross-cutting'
T1c-medium:
max_lines: 300
max_files: 15
breadth: 'not-cross-cutting'
dismiss:
trivial_extensions:
- '.md'
- '.mdx'
trivial_name_prefixes:
- 'readme'
- 'changelog'
test_regex: '(?:^|/)(?:__tests__|tests?|fixtures)/|(?:^|/)test_[^/]+\.py$|_test\.(py|go)$|\.test\.(ts|tsx|js|jsx)$|\.spec\.(ts|tsx|js|jsx)$|(?:^|/)conftest\.py$'
# The bare-filename alternative is pinned to the one path ci-backend.yml regenerates and
# diffs. A general `(?:^|/)generated\.ts$` would retain an approval for any file a
# contributor chose to name that, and only this path has the drift check behind it.
generated_regex: '(?:^|/)generated/.*\.(ts|tsx|js|jsx|json|md|snap|pyi|txt)$|^services/mcp/src/api/generated\.ts$|\.gen\.(ts|tsx|js|jsx)$|\.generated\.(ts|tsx|js|jsx)$|^frontend/src/queries/schema/'
overrides:
'size_gate.max_files':
ceiling: 50
# Author-familiarity signal - judgment layer only, never a gate. Non-delegable
# (absent from `overrides`). STRONG = blame overlap ≥ min_blame_overlap_pct;
# MODERATE = both of its keys satisfied; else NONE.
# Rationale: calibrated against 120 days of refused/escalated verdict outcomes
# (Jul 2026 backtest). Blame overlap was the only metric that rose monotonically
# with the human rubber-stamp rate; prior-PR counts, previously-modified-file
# fractions, and recency were flat at every cutoff, so STRONG is blame-only and
# MODERATE is advisory. Tuning these is a YAML-only diff.
familiarity:
strong:
min_blame_overlap_pct: 70
moderate:
min_prior_prs: 5
max_days_since_touch: 180
# Ownership sources feeding the reviewer's team context - advisory only, never a
# gate. The insightscli-resolver source delegates to the shared insightscli OwnersResolver,
# which walks the distributed owners.yaml / product.yaml files from the repo root
# (the `path` locator) and merges nearest-file-wins, so stamp reads the same
# view the reviewer auto-assigner builds. Sources are read from the master
# checkout (the workflow pins ref: master), so a PR editing them cannot influence
# its own review. Locators must stay repo-relative (no absolute paths, no '..').
ownership:
sources:
- path: .
format: insightscli-resolver