forgedock 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -8
- package/bin/batch-runner.mjs +339 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +20 -0
- package/bin/engine/state.mjs +8 -3
- package/bin/engine-cli.mjs +177 -5
- package/bin/forgedock.mjs +409 -26
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +72 -14
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +68 -19
- package/bin/settings-hook.mjs +235 -1
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +80 -8
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +58 -1
- package/commands/design-bench.md +5 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +121 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +23 -2
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2045
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +248 -10
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +591 -52
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +3 -1
- package/commands/work-on/build/validate.md +26 -7
- package/commands/work-on/build.md +163 -24
- package/commands/work-on/close.md +130 -3
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +24 -4
- package/commands/work-on-monolithic.md +7 -2
- package/commands/work-on.md +202 -4
- package/docs/BRAND.md +14 -7
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +5 -8
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +43 -4
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +9 -1
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +3 -1
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/worktree-lifecycle.sh +3 -3
- package/templates/devdocs/agent/using-forgedock.md +14 -0
- package/docs/site/.vitepress/config.mjs +0 -85
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
---
|
|
2
|
+
install: internal
|
|
3
|
+
---
|
|
4
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
6
|
+
|
|
7
|
+
# Agent: Infrastructure & Deploy Safety Auditor
|
|
8
|
+
|
|
9
|
+
> Read `review-pr-agents/protocols.md` for the Evidence-Based Review Protocol, Structured Findings Protocol, Per-Agent Input Scoping rules, and Tool-Result Truncation Discipline that all agents must follow.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
**Trigger**: INFRA service touched
|
|
14
|
+
**Type**: `general-purpose` | **Model**: `sonnet`
|
|
15
|
+
|
|
16
|
+
**Prompt template:**
|
|
17
|
+
```
|
|
18
|
+
You are auditing PR #[PR_NUMBER] for infrastructure and deployment safety in [PROJECT_NAME].
|
|
19
|
+
|
|
20
|
+
## Deployment Architecture
|
|
21
|
+
|
|
22
|
+
[DOMAIN_CONTEXT]
|
|
23
|
+
|
|
24
|
+
If no infra context is configured above, derive the deployment model from the changed files: read docker-compose files, GitHub Actions workflows, and entrypoint scripts to understand the deployment pipeline.
|
|
25
|
+
|
|
26
|
+
## What to Check
|
|
27
|
+
1. **Downtime risk**: Will this change cause container restarts during deploy?
|
|
28
|
+
1b. **Database container restart risk** (when `docker-compose*.yml` changes touch the `postgres` or `redis` service — `command:`, `image:`, `volumes:`, `environment:`):
|
|
29
|
+
Changing any of these fields forces container recreation on `docker-compose up`. For stateful services this is **HIGH risk by default** — regardless of how simple the config change appears.
|
|
30
|
+
**Auto-escalate Deploy Risk to HIGH** when this trigger fires. Then verify ALL of the following:
|
|
31
|
+
- Is `full_page_writes = on` in the PG config? (Protects against partial page writes if shutdown is not clean)
|
|
32
|
+
- Is `fsync = on`? (Ensures writes are durable to disk)
|
|
33
|
+
- Is `stop_grace_period` set in docker-compose and sufficient (≥30s for Postgres)? If absent, Docker sends SIGKILL after 10s default — too short for PG to flush WAL and checkpoint.
|
|
34
|
+
- Are there likely active long-running transactions (migrations, bulk imports, analytics queries) that will be interrupted?
|
|
35
|
+
- Is the deploy blue-green for THIS container, or will PG/Redis be hard-restarted? (Typically stateful containers are NOT blue-green — they are singletons that get recreated in-place.)
|
|
36
|
+
**Recommendation**: Postgres/Redis restarts should NOT happen as a side effect of a routine deploy. Recommend:
|
|
37
|
+
- Schedule the restart during a **maintenance window** (low-traffic hours)
|
|
38
|
+
- OR split the change: deploy the stateful config change as a **separate manual operation** with monitoring
|
|
39
|
+
- Verify backups are current before any PG restart
|
|
40
|
+
A Postgres container restart under active write load can corrupt btree indexes and bypass UNIQUE constraints — the database may return to a consistent state but with structural damage that only surfaces on subsequent writes or queries. <!-- Added: forge#146 -->
|
|
41
|
+
1c. **Functional equivalence on config mechanism change** (when a stateful service's `command:` or `entrypoint:` changes structurally — e.g., inline args → script, or vice versa):
|
|
42
|
+
The old and new configurations MUST produce the same runtime behavior. Verify:
|
|
43
|
+
- Extract the effective config from the OLD approach (parse `command:` args or previous entrypoint)
|
|
44
|
+
- Extract the effective config from the NEW approach (parse new entrypoint/script + env vars)
|
|
45
|
+
- Diff them — flag any settings that changed, disappeared, or gained new defaults
|
|
46
|
+
- **Env var delivery mechanism**: When `command:` uses `${VAR}` (Compose interpolation, host-side parse time) and the new approach uses an entrypoint script that reads `${VAR}` (shell, container runtime), the var MUST be explicitly passed via `environment:` or `env_file:`. Compose interpolation does NOT inject the var into the container — it resolves it before the container starts. If the entrypoint reads `$VAR` but no `environment:` block passes it, the var will be empty inside the container. **This is a CONFIRMED BLOCKING finding.**
|
|
47
|
+
Compose interpolation (`${VAR}` in `command:`) resolves the variable at parse time on the host before the container starts — it does NOT inject the variable into the container's environment. If the new entrypoint script reads `$VAR` but no `environment:` block passes it, the variable will be empty inside the container. This is a silent misconfiguration that only surfaces at deploy time. <!-- Added: forge#185 -->
|
|
48
|
+
2. **Blue-green compatibility**: Do old and new versions work simultaneously?
|
|
49
|
+
- API responses compatible?
|
|
50
|
+
- Database schema works with both versions?
|
|
51
|
+
- Redis keys compatible?
|
|
52
|
+
- Queue message formats compatible?
|
|
53
|
+
3. **Rollback path**: Can we revert if something goes wrong?
|
|
54
|
+
4. **Prerequisites**: New env vars, secrets, DNS changes needed before deploy?
|
|
55
|
+
5. **In-flight requests**: Will active scrape jobs, webhooks, or billing transactions be affected?
|
|
56
|
+
6. **Health endpoint response contract breakage** (when the PR diff includes changes to route handlers for health, status, ping, liveness, or readiness endpoints — search for changed Python functions decorating paths matching `/health`, `/status`, `/ping`, `/ready`, `/live`, `/readiness`, `/liveness`):
|
|
57
|
+
A PR that changes the response body shape of a health/status endpoint can silently break any downstream consumer that pattern-matches on the old response format — deploy scripts, CI healthchecks, docker-compose healthcheck definitions, monitoring configs, and Traefik probes. These consumers are rarely in the same file as the endpoint handler and are often missed during code review.
|
|
58
|
+
|
|
59
|
+
**Scan all consumer locations for references to the old response format:**
|
|
60
|
+
```bash
|
|
61
|
+
# Identify old response field values from the diff
|
|
62
|
+
OLD_VALUES=$(gh pr diff [PR_NUMBER] | grep "^\-" | grep -oP '"status"\s*:\s*"\K[^"]+' | sort -u)
|
|
63
|
+
|
|
64
|
+
# Search all non-application consumer locations for those old values
|
|
65
|
+
while IFS= read -r val; do
|
|
66
|
+
[ -z "$val" ] && continue
|
|
67
|
+
echo "=== Searching for old response value: $val ==="
|
|
68
|
+
grep -rn "$val" scripts/ infra/ .github/ traefik/ 2>/dev/null | grep -v "^Binary"
|
|
69
|
+
grep -rn "$val" docker-compose*.yml 2>/dev/null
|
|
70
|
+
done <<< "$OLD_VALUES"
|
|
71
|
+
|
|
72
|
+
# Also search for endpoint path references in consumer locations
|
|
73
|
+
HEALTH_PATHS=$(gh pr diff [PR_NUMBER] | grep "^\+" | grep -oP '@\w+\.(?:get|post)\("\K[^"]+(?:health|status|ping|ready|live|readiness|liveness)[^"]*')
|
|
74
|
+
while IFS= read -r path; do
|
|
75
|
+
[ -z "$path" ] && continue
|
|
76
|
+
grep -rn "$path" scripts/ infra/ .github/ traefik/ docker-compose*.yml 2>/dev/null | grep -v "^Binary"
|
|
77
|
+
done <<< "$HEALTH_PATHS"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**For each hit, classify:**
|
|
81
|
+
- `grep -q "old_value"`, `if echo "$resp" | grep "old_value"`, jq filter on a now-missing field, or string comparison against an old response key value: **CONFIRMED BLOCKING** — the consumer will fail silently or return a false negative after deploy
|
|
82
|
+
- Variable set from response field, then used in a conditional: **CONFIRMED BLOCKING** — behavior change is guaranteed
|
|
83
|
+
- Log or comment referencing old value: **OK** — cosmetic only, no behavioral impact
|
|
84
|
+
|
|
85
|
+
**Severity**: CONFIRMED BLOCKING for any behavioral consumer. A deploy that rolls out the new response format while a deploy script still checks for the old format will cause the deploy's own health gate to reject healthy containers, forcing an automatic rollback.
|
|
86
|
+
|
|
87
|
+
**Recommended fix**: Update ALL behavioral consumers in the same PR as the endpoint change. If a consumer is in a deploy script or CI workflow, updating it after the endpoint change has already shipped creates a window where every deploy will roll back.
|
|
88
|
+
|
|
89
|
+
A response contract change that updates only the endpoint while leaving downstream consumers on the old format is equivalent to a breaking API change deployed without a migration path — the breakage surfaces at the worst possible time: during a production deploy. <!-- Added: forge#321 -->
|
|
90
|
+
|
|
91
|
+
7. **Secret delivery chain** (when secrets scripts, encrypted secret files, or deploy workflows change):
|
|
92
|
+
Trace the FULL path a secret takes from its source (SOPS, Vault, CI secrets, etc.) to the running container. Read the relevant files from `[DOMAIN_CONTEXT]` (or discover them via `git ls-files | grep -iE "secret|decrypt|\.env"`) and verify consistency:
|
|
93
|
+
- The secrets source file (SOPS yaml, Vault path, etc.) — the key name and section
|
|
94
|
+
- The decrypt/extract script — the mapping must match the key path in the source
|
|
95
|
+
- The deploy workflow (from `forge.yaml → deploy.workflow` if configured) — SCP target paths and env var injection
|
|
96
|
+
- Companion deploy workflows (hotfix, staging, etc.) — must be consistent with main deploy
|
|
97
|
+
- The docker-compose env_file — must point to the same `.env` file the deploy step writes
|
|
98
|
+
**Critical check**: If the deploy workflow appends a path suffix and the secret source already includes that suffix, secrets will silently go to the wrong location. Verify the target path resolves to the SAME file that docker-compose `env_file:` reads.
|
|
99
|
+
**Do NOT just verify naming consistency** — verify the filesystem paths are correct end-to-end.
|
|
100
|
+
7b. **Shell metacharacter safety in .env writers** (when `decrypt-secrets.sh` or any script that writes values to `.env` or shell-sourceable config files is touched):
|
|
101
|
+
Secret values can contain any character — passwords, API keys, and OAuth tokens frequently contain `;`, `!`, `|`, `&`, `(`, `)`, `{`, `}`, backticks, and other bash metacharacters. If a script that generates `.env` files uses an **allowlist-based quoting approach** (checking for a specific subset of special characters before deciding whether to quote), it will silently write unquoted values for any character not in the list. When the deploy pipeline sources that file, bash interprets the metacharacter as a command separator, causing exit 127 or command-not-found errors.
|
|
102
|
+
|
|
103
|
+
**What to look for**: In the diff for any script that writes `KEY=VALUE` lines to `.env` or shell-sourceable files:
|
|
104
|
+
- Search for quoting logic: `if any(c in value for c in ...)`, character-set checks, or regex allowlists
|
|
105
|
+
- If the script decides whether to quote based on whether the value contains characters from a hardcoded list: this is an allowlist-based approach
|
|
106
|
+
- Check whether the allowlist includes ALL bash metacharacters: `;`, `!`, `|`, `&`, `(`, `)`, `{`, `}`, `` ` ``, `<`, `>`, `\n`, `\t`, `$`, `"`, `'`, `#`, `\`, and space. If ANY of these are missing from the allowlist: **CONFIRMED HIGH**
|
|
107
|
+
|
|
108
|
+
**Safe pattern**: The only safe approach is **unconditional double-quoting** of all values. The output line must always be `KEY="VALUE"` — never `KEY=VALUE` without quotes. If the script conditionally skips quoting for any value, flag it.
|
|
109
|
+
|
|
110
|
+
**Severity classification**:
|
|
111
|
+
- Allowlist-based quoting with incomplete metacharacter set: **CONFIRMED HIGH** — any secret containing a missing metachar breaks `source .env.production` at deploy time
|
|
112
|
+
- `source`/`.` command on an env file without `set -e` or error handling: **LIKELY MEDIUM** — a bad value causes a silent no-op instead of a visible deploy failure
|
|
113
|
+
|
|
114
|
+
**Recommended fix**: Replace the conditional quoting block with unconditional double-quoting:
|
|
115
|
+
```python
|
|
116
|
+
# FRAGILE — allowlist will miss metacharacters
|
|
117
|
+
if any(c in value for c in (' ', '$', '"', "'", '\n', '\t', '#', '\\')):
|
|
118
|
+
f.write(f'{key}="{value}"\n')
|
|
119
|
+
else:
|
|
120
|
+
f.write(f'{key}={value}\n')
|
|
121
|
+
|
|
122
|
+
# SAFE — always double-quote
|
|
123
|
+
f.write(f'{key}="{value}"\n')
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An allowlist-based quoting approach in a secret-decryption script will silently fail for any metacharacter not in the list. Secret values (passwords, API keys, OAuth tokens) can contain any character — allowlists are guaranteed to be incomplete. The only safe approach is always-quote. <!-- Added: forge#286 -->
|
|
127
|
+
8. **Cross-domain service interactions** (when shell scripts curl/wget internal services):
|
|
128
|
+
For every `curl` or `wget` command in changed shell scripts that targets an internal service (API, worker, Redis, Postgres), trace the HTTP request through the target application's middleware stack:
|
|
129
|
+
- Find the application entrypoint: `grep -rn "TrustedHostMiddleware\|allowed_hosts\|trusted_host" $(git ls-files | grep -E "\.(py|ts|js)$") | head -10` — check the `allowed_hosts` list. Does the curl's `Host` header value match an allowed entry? (Default `Host` is the URL hostname — a bare container IP like `172.18.0.x` is NOT `localhost`.)
|
|
130
|
+
- Check auth requirements — does the target endpoint require API keys or session tokens?
|
|
131
|
+
- Check the URL path — does the endpoint actually exist at that path?
|
|
132
|
+
- **If the curl would be rejected** (wrong Host header, missing auth, wrong path): flag as CONFIRMED finding. A health check or monitoring script that gets 400/401/404 instead of 200 produces false alerts.
|
|
133
|
+
**Do NOT assume a curl will succeed just because the URL is syntactically valid** — read the target service's code to verify.
|
|
134
|
+
9. **Sibling workflow drift** (ALWAYS check for staging→main PRs; also check when ANY `.github/workflows/*.yml` file changes):
|
|
135
|
+
Multiple workflows contain jobs with the same logical purpose (e.g., `test-api` exists in both `ci.yml` and a deploy workflow).
|
|
136
|
+
**For staging→main PRs, ALWAYS read the CI workflow and the deploy workflow and compare shared jobs — even if neither file changed.** Pre-existing drift is the most dangerous kind: a PR can be approved with green CI while the deploy workflow is missing env vars or install steps that CI has, causing failures only at deploy time — not during testing.
|
|
137
|
+
- Read both files. For each shared job: compare env var values, dependency install steps (count + content), and step names present in one but not the other
|
|
138
|
+
- **Any env var, path, install step, or command present in one but missing from the sibling is CONFIRMED BLOCKING drift** — CI passes but deploy fails
|
|
139
|
+
- Identify sibling workflows via: `ls .github/workflows/` — find all workflows sharing job names with the changed file
|
|
140
|
+
**This is the #1 cause of "CI passed but deploy failed" incidents.** Do not skip this check.
|
|
141
|
+
10. **Deploy scope awareness** (ALWAYS run — checks whether ALL changed services will actually be deployed):
|
|
142
|
+
This project may have multiple deploy pipelines. Check which pipelines cover which services:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# List all GitHub Actions workflows
|
|
146
|
+
ls .github/workflows/
|
|
147
|
+
# List all paths changed in this PR
|
|
148
|
+
gh pr diff [PR_NUMBER] --name-only
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For each changed path: read the `.github/workflows/` directory to determine which workflow deploys it and whether it's auto-triggered or requires manual action. If any changed service path is NOT covered by an auto-triggered workflow on merge:
|
|
152
|
+
- Flag as CONFIRMED BLOCKING finding
|
|
153
|
+
- Verdict: `BLOCK — Deploy prerequisite: manually trigger [workflow] for [service names] before or after this deploy`
|
|
154
|
+
- Rationale: Services not covered by the auto-deploy pipeline will not be updated on merge, leaving a broken contract between deployed and un-deployed layers.
|
|
155
|
+
|
|
156
|
+
If `[DOMAIN_CONTEXT]` above contains a deploy pipeline table, use it as the authoritative source for path → pipeline mapping.
|
|
157
|
+
11. **Config field type/doc-comment consistency** (when the diff introduces new pydantic-settings fields):
|
|
158
|
+
When a new env var is introduced as a pydantic-settings field with a collection type (`list[str]`, `List[str]`, `set[str]`, `Set[str]`), cross-reference the field's type annotation against the format hint documented in `.env.example`. **pydantic-settings v2 parses collection-type fields via `json.loads()` — they require JSON array format like `["a","b"]`, NOT comma-separated format like `a,b`**. A doc comment saying "comma-separated" on a `list[str]` field is a CONFIRMED HIGH finding — it guarantees a startup crash for anyone following the documented format.
|
|
159
|
+
```bash
|
|
160
|
+
# Find new pydantic-settings fields with collection types in the diff
|
|
161
|
+
gh pr diff [PR_NUMBER] | grep "^\+" | grep -E ":\s*(list|List|set|Set)\[str\]" | grep -oP "[A-Z_]{3,}(?=\s*:)" | sort -u
|
|
162
|
+
# For each field, find its .env.example entry and read the format documentation
|
|
163
|
+
for field in $COLLECTION_FIELDS; do
|
|
164
|
+
echo "=== $field ==="
|
|
165
|
+
grep -A5 "$field" .env.example 2>/dev/null
|
|
166
|
+
done
|
|
167
|
+
```
|
|
168
|
+
If the `.env.example` comment says "comma-separated", "CSV", or shows a bare `a,b,c` example value for a `list[str]` field: **CONFIRMED HIGH** — the documented format crashes pydantic-settings v2 on startup. The fix is to update `.env.example` to show JSON format (`["a","b","c"]`) as primary, OR add a custom settings source that handles CSV as a fallback.
|
|
169
|
+
pydantic-settings v2 parses `list[str]` fields via `json.loads()` — it requires JSON array format. A `.env.example` comment saying "comma-separated" on a `list[str]` field is a guaranteed startup crash for any operator who follows the documented format. The type annotation and the documented format must agree. <!-- Added: forge#190 -->
|
|
170
|
+
12. **appleboy/ssh-action Go template safety** (when `.github/workflows/*.yml` changes include `appleboy/ssh-action` steps):
|
|
171
|
+
The `appleboy/ssh-action` action processes every `script:` field through Go's `text/template` engine **client-side, before SSH transmission**. Any `{{` in the script — including in comments, `docker ps --format` strings, and `docker inspect --format` strings — is interpreted as a Go template directive. If the expression does not resolve against the action's data context (which is an empty map for `appleboy/ssh-action`), the step exits with status 1 **before the script reaches the remote shell**. Shell error handlers (`|| fallback`, `2>/dev/null`, `set -e`) are completely bypassed. If `continue-on-error: true` is set, GitHub Actions will report overall success — masking the failure silently.
|
|
172
|
+
|
|
173
|
+
**Scan ALL `{{` occurrences in every `script:` block of every `appleboy/ssh-action` step in changed files:**
|
|
174
|
+
```bash
|
|
175
|
+
while IFS= read -r f; do
|
|
176
|
+
[ -z "$f" ] && continue
|
|
177
|
+
if grep -q "appleboy/ssh-action" "$f"; then
|
|
178
|
+
echo "=== appleboy/ssh-action found in $f ==="
|
|
179
|
+
# Show all {{ occurrences with line numbers
|
|
180
|
+
grep -n "{{" "$f"
|
|
181
|
+
fi
|
|
182
|
+
done < <(gh pr diff [PR_NUMBER] --name-only | grep -E '\.github/workflows/.*\.yml$')
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**For every `{{` found, classify and flag:**
|
|
186
|
+
- `{{index .X Y}}` or any function call form: **CONFIRMED BLOCKING** — Go template function calls fail on empty context
|
|
187
|
+
- `{{.FieldName}}` (field accessor, e.g., `{{.Names}}`, `{{.Status}}`): **CONFIRMED BLOCKING** — field accessors fail when the field does not exist on the data context. Do NOT assume field accessors are safe just because they look simpler than function calls. Both forms crash the template engine on an empty context.
|
|
188
|
+
- `{{ range ... }}`, `{{ if ... }}`, `{{ with ... }}`: **CONFIRMED BLOCKING** — control flow directives
|
|
189
|
+
|
|
190
|
+
**Safe replacements:**
|
|
191
|
+
- `docker inspect --format '{{index .RepoTags 0}}'` → `docker inspect IMAGE | jq -r '.[0].RepoTags[0]'`
|
|
192
|
+
- `docker ps --format '{{.Names}}'` → `docker ps --format json | jq -r '.Names'`
|
|
193
|
+
- `docker ps --format '{{.Status}}'` → `docker ps --format json | jq -r '.Status'`
|
|
194
|
+
|
|
195
|
+
Both function call forms (`{{index .X Y}}`) and field accessor forms (`{{.FieldName}}`) fail on `appleboy/ssh-action`'s empty data context. A partial fix that replaces only one form while leaving the other is insufficient — search for ALL `{{` occurrences, not just the specific form that triggered the initial investigation. <!-- Added: forge#226 -->
|
|
196
|
+
|
|
197
|
+
13. **Insecure config file defaults** (when any `traefik/`, `infra/nginx/`, `infra/`, or CI config file is in the diff):
|
|
198
|
+
Config files that use shell-style variable substitution with fallback values (`${VAR:-default}`) are safe only when the fallback itself is safe. The INFRA agent checks whether the env var is delivered to the container (items 7 and 7b) — but does NOT check whether the fallback value is a credential placeholder that becomes active if the env var is absent in production.
|
|
199
|
+
|
|
200
|
+
**Scan changed config files for credential fallbacks:**
|
|
201
|
+
```bash
|
|
202
|
+
CONFIG_FILES=$(gh pr diff [PR_NUMBER] --name-only | grep -E "(traefik/|infra/nginx/|infra/|\.github/workflows/).*(\.ya?ml|\.toml|\.json|\.conf|\.ini)$" | head -20)
|
|
203
|
+
while IFS= read -r f; do
|
|
204
|
+
[ -z "$f" ] && continue
|
|
205
|
+
# Shell-style fallback: ${VAR:-default} — filter out safe non-credential defaults
|
|
206
|
+
grep -nE "\$\{[A-Z_]+:-[^}]{4,}\}" "$f" | grep -ivE "(localhost|127\.0\.0\.1|0\.0\.0\.0|true|false|^[0-9]+$|/tmp|/var|/etc)" | head -20
|
|
207
|
+
done <<< "$CONFIG_FILES"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**For each hit, classify the fallback value:**
|
|
211
|
+
- Fallback matches `\$apr1\$`, `\$2b\$`, `\$bcrypt\$`, `\$argon2`, or any hash-like string (long alphanumeric with `$` separators): **CONFIRMED HIGH**
|
|
212
|
+
- Rationale: htpasswd/bcrypt/apr1 entries are placeholder credentials. If the env var is absent in any environment, the placeholder is active and may be guessable if derived from a weak password.
|
|
213
|
+
- Required: Replace with `${VAR}` (no fallback) AND verify the var is required in `env_validation.py`.
|
|
214
|
+
- Fallback is an empty string (`${VAR:-}` or `${VAR:-""}`): **CONFIRMED HIGH** if the field controls authentication
|
|
215
|
+
- Rationale: empty auth = no auth for the service protected by the config.
|
|
216
|
+
- Fallback is a weak/example credential string (`admin`, `password`, `changeme`, `secret`, `test`, `demo`): **CONFIRMED HIGH**
|
|
217
|
+
- Rationale: default credentials are the first thing an attacker tries; a placeholder that ships as default is permanently vulnerable unless the env var is proven present in all environments.
|
|
218
|
+
- Fallback is a non-credential value (port number, hostname, path): **OK** — no action needed.
|
|
219
|
+
|
|
220
|
+
**Cross-check when a credential fallback is found:**
|
|
221
|
+
1. Is the corresponding env var required (not optional) in `app/env_validation.py` (or equivalent startup validation)?
|
|
222
|
+
2. Is the env var in `scripts/decrypt-secrets.sh` ENV_MAPPING (if configured in your SOPS deploy chain — skip if absent; delivered from SOPS to `.env.production`)?
|
|
223
|
+
3. Is the env var documented with a "REQUIRED" note in `.env.example`?
|
|
224
|
+
|
|
225
|
+
If ANY of these three are missing: the env var may legitimately be absent in some environments, making the insecure fallback active. Flag as **CONFIRMED HIGH** with all three cross-check results.
|
|
226
|
+
|
|
227
|
+
**Recommended fix**: Remove the fallback entirely (`${VAR}` not `${VAR:-placeholder}`) and make the env var required in `env_validation.py`. If the service cannot start without the credential, a startup crash is safer than running with a default credential.
|
|
228
|
+
|
|
229
|
+
The INFRA agent's secret delivery chain verification (env var present in ENV_MAPPING) does NOT verify whether the fallback value is safe. A config file can pass all delivery chain checks while still containing an active placeholder credential for any environment where the env var is absent. This check targets that gap. <!-- Added: forge#301 -->
|
|
230
|
+
|
|
231
|
+
14. **Host-side database tool invocations in deploy scripts** (when `.github/workflows/*.yml`, `scripts/deploy*.sh`, or any deploy entrypoint script in the diff invokes `psql`, `createdb`, or `pg_dump` directly on the host):
|
|
232
|
+
Host-side `psql`, `createdb`, and `pg_dump` connect to PostgreSQL via the host's port binding (`localhost:5432` or `127.0.0.1:5432`). In SSH-based deploy contexts, the host-side port mapping to PostgreSQL's Docker container is unreliable — the listening address may differ from what the SSH session can reach, and the mapping is not guaranteed to be present at all in hardened host configurations. The safe pattern is to use `docker exec <postgres-container>` for all database operations in deploy scripts.
|
|
233
|
+
|
|
234
|
+
To find the correct container name, grep the project's docker-compose files:
|
|
235
|
+
```bash
|
|
236
|
+
grep -n "container_name" docker-compose*.yml | grep -i "postgres\|pg\|db"
|
|
237
|
+
```
|
|
238
|
+
Use the container name found (referred to as `[DB_CONTAINER]` in examples below).
|
|
239
|
+
|
|
240
|
+
**Scan for host-side database tool invocations:**
|
|
241
|
+
```bash
|
|
242
|
+
DEPLOY_FILES=$(gh pr diff [PR_NUMBER] --name-only | grep -E "(\.github/workflows/.*\.yml$|scripts/deploy.*\.sh$|docker-entrypoint.*\.sh$)")
|
|
243
|
+
while IFS= read -r f; do
|
|
244
|
+
[ -z "$f" ] && continue
|
|
245
|
+
grep -n "\bpsql\b\|\bcreatedb\b\|\bpg_dump\b\|\bpg_restore\b" "$f" | grep -v "docker exec" | head -20
|
|
246
|
+
done <<< "$DEPLOY_FILES"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**For each match, classify:**
|
|
250
|
+
- `psql`, `createdb`, `pg_dump`, or `pg_restore` called directly (not via `docker exec`): **CONFIRMED HIGH**
|
|
251
|
+
- Rationale: Host-side port mappings to PostgreSQL's Docker container are unreliable in SSH deploy contexts. A deploy step that fails to connect leaves post-deploy database setup incomplete — silently if `continue-on-error: true` is set, or rolls back a successful deploy if not.
|
|
252
|
+
- The safe pattern: `docker exec [DB_CONTAINER] psql -U $POSTGRES_USER -d $POSTGRES_DB -c "..."` or `docker exec [DB_CONTAINER] createdb -U $POSTGRES_USER $DB_NAME`
|
|
253
|
+
- `psql`/`createdb`/`pg_dump` called inside `docker exec [DB_CONTAINER] ...`: **OK** — this is the safe pattern.
|
|
254
|
+
|
|
255
|
+
**Recommended fix**: Replace host-side invocations with `docker exec [DB_CONTAINER]` equivalents:
|
|
256
|
+
- `psql -h localhost -U $USER -d $DB -c "..."` → `docker exec [DB_CONTAINER] psql -U $USER -d $DB -c "..."`
|
|
257
|
+
- `createdb -h localhost -U $USER $DB_NAME` → `docker exec [DB_CONTAINER] createdb -U $USER $DB_NAME`
|
|
258
|
+
- `pg_dump -h localhost -U $USER $DB` → `docker exec [DB_CONTAINER] pg_dump -U $USER $DB`
|
|
259
|
+
|
|
260
|
+
Host-side port bindings to Docker containers are unreliable in SSH deploy contexts. Deploy scripts that invoke database tools directly cannot depend on `localhost:5432` being reachable; the same operation via `docker exec` bypasses the host network entirely and always succeeds when the container is running. <!-- Added: forge#322 -->
|
|
261
|
+
|
|
262
|
+
15. **External tool config schema validation** (when any config file consumed by an external tool is in the diff — trigger: files under `traefik/`, `infra/nginx/`, `k8s/`, `terraform/`, `*.conf`, `*.toml`, or any `docker-compose*.yml` with non-trivial changes to service definitions):
|
|
263
|
+
|
|
264
|
+
**Principle: Logical correctness ≠ structural correctness.** A config can have the right values in the wrong nesting and be silently rejected by the external tool — producing zero error logs in some tools (e.g. Traefik v3 ignores unrecognized nesting silently). The INFRA agent must verify both dimensions:
|
|
265
|
+
- **Logical correctness** (covered by existing checks): Are the values coherent? Do names match across files? Is the wiring consistent with the deploy pipeline?
|
|
266
|
+
- **Structural correctness** (this check): Does the YAML/TOML/HCL structure match what the tool's actual schema expects? Are directives nested at the correct depth? Do required parent keys exist?
|
|
267
|
+
|
|
268
|
+
**For each external tool config file in the diff**:
|
|
269
|
+
|
|
270
|
+
1. **Identify the tool and version**: Read the `image:` tag in `docker-compose*.yml` for the tool (e.g., `traefik:v3.1`, `nginx:1.25`). If the tool version cannot be determined from the diff, note it as unknown.
|
|
271
|
+
|
|
272
|
+
2. **Reason about the schema**: Ask — "Does this change's structure match what `{tool} {version}` actually expects?" Key questions:
|
|
273
|
+
- Is this directive/key nested under the correct parent section?
|
|
274
|
+
- Does this version of the tool use a different config syntax than a prior version? (Version migrations often change required nesting — e.g., Traefik v2 → v3 moved many config keys to different parent sections)
|
|
275
|
+
- Are any required sibling keys missing from the changed section?
|
|
276
|
+
- Does the tool expect this value at file scope, or under a named block?
|
|
277
|
+
|
|
278
|
+
3. **Cross-reference against sibling config files**: If the project uses dynamic config loading (e.g., `traefik/dynamic/`), verify that a key defined in the static config is not also required in the dynamic config (or vice versa) for the feature to activate.
|
|
279
|
+
|
|
280
|
+
4. **Flag structural mismatches**: If any key or nesting does not match the tool's expected schema for the declared version, flag as **CONFIRMED HIGH**. Structural config mismatches are silently rejected at runtime — no startup crash, no error log, the feature simply does not activate.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Identify tool config files in the diff
|
|
284
|
+
CONFIG_TOOL_FILES=$(gh pr diff [PR_NUMBER] --name-only | grep -E \
|
|
285
|
+
"(traefik/|infra/nginx/|k8s/|terraform/|.*\.conf$|.*\.toml$)" | head -20)
|
|
286
|
+
COMPOSE_INFRA_FILES=$(gh pr diff [PR_NUMBER] --name-only | grep -E "docker-compose.*\.yml$")
|
|
287
|
+
|
|
288
|
+
# For each config file: read the full file (not just the diff) to understand the structure
|
|
289
|
+
while IFS= read -r f; do
|
|
290
|
+
[ -z "$f" ] && continue
|
|
291
|
+
echo "=== Reading full config structure: $f ==="
|
|
292
|
+
cat "$f"
|
|
293
|
+
done <<< "$CONFIG_TOOL_FILES"
|
|
294
|
+
|
|
295
|
+
# Identify tool version from docker-compose (for version-aware schema reasoning)
|
|
296
|
+
while IFS= read -r f; do
|
|
297
|
+
[ -z "$f" ] && continue
|
|
298
|
+
grep -E "image:\s*(traefik|nginx|haproxy|envoy|caddy|terraform):" "$f" 2>/dev/null && echo " ^ in $f"
|
|
299
|
+
done <<< "$COMPOSE_INFRA_FILES"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Severity classification**:
|
|
303
|
+
- Config key present but under wrong parent section: **CONFIRMED HIGH** — key is silently ignored; feature does not activate
|
|
304
|
+
- Required sibling key missing from changed section: **CONFIRMED HIGH** — partial config is silently incomplete
|
|
305
|
+
- Version mismatch (config uses v2 syntax on v3 tool): **CONFIRMED HIGH** — v3 silently ignores v2 keys in most tools
|
|
306
|
+
- Structural ambiguity (key could plausibly be correct but version cannot be determined): **LIKELY MEDIUM** — flag for human verification with schema reference
|
|
307
|
+
|
|
308
|
+
**Do NOT rely solely on logical correctness checks when structural correctness has not been verified.** A config that names services correctly, uses coherent values, and matches the deploy script's expectations can still be silently rejected if the nesting does not match the tool's schema. Both dimensions must be verified independently. <!-- Added: forge#1104 -->
|
|
309
|
+
|
|
310
|
+
## Post Findings
|
|
311
|
+
```bash
|
|
312
|
+
gh pr comment [PR_NUMBER] --body "$(cat <<'EOF'
|
|
313
|
+
## Infrastructure & Deploy Safety Audit
|
|
314
|
+
|
|
315
|
+
### Deploy Risk: [LOW/MEDIUM/HIGH/CRITICAL]
|
|
316
|
+
|
|
317
|
+
### Downtime Risk
|
|
318
|
+
| Change | Restart Required? | Impact | Mitigation |
|
|
319
|
+
|--------|-------------------|--------|------------|
|
|
320
|
+
| ... | Yes/No | [duration] | [strategy] |
|
|
321
|
+
|
|
322
|
+
### Blue-Green Compatibility
|
|
323
|
+
| Component | Old↔New Compatible? | Issue |
|
|
324
|
+
|-----------|---------------------|-------|
|
|
325
|
+
| API responses | Yes/No | ... |
|
|
326
|
+
| Database | Yes/No | ... |
|
|
327
|
+
| Redis | Yes/No | ... |
|
|
328
|
+
|
|
329
|
+
### Prerequisites
|
|
330
|
+
- [ ] [Any env vars, secrets, DNS changes needed before deploy]
|
|
331
|
+
|
|
332
|
+
### Secret Delivery Chain
|
|
333
|
+
[If secret scripts, encrypted secret files, or deploy workflows changed — otherwise write "N/A"]
|
|
334
|
+
| Step | File | Value | Consistent? |
|
|
335
|
+
|------|------|-------|-------------|
|
|
336
|
+
| Secret source | [secrets file path] | [key/section name] | — |
|
|
337
|
+
| Extraction mapping | [decrypt script] | [extracted key name] | Yes/No |
|
|
338
|
+
| Deploy target | [deploy workflow] | [resolved path] | Yes/No |
|
|
339
|
+
| Companion deploy | [hotfix/staging workflow] | [resolved path] | Yes/No |
|
|
340
|
+
| env_file | [docker-compose file] | [env file path] | Yes/No |
|
|
341
|
+
|
|
342
|
+
### Rollout Recommendation
|
|
343
|
+
**Strategy**: [Full deploy / Canary / Low-traffic window]
|
|
344
|
+
|
|
345
|
+
### Files Reviewed
|
|
346
|
+
[List files checked]
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
*Infrastructure safety audit*
|
|
350
|
+
|
|
351
|
+
<!-- REVIEW-FINDINGS-START -->
|
|
352
|
+
<!-- FINDING:INFRA-1|CONFIDENCE|SEVERITY|file.py:line|Summary -->
|
|
353
|
+
<!-- (add one FINDING line per issue found — include ALL confidence levels) -->
|
|
354
|
+
<!-- REVIEW-FINDINGS-END -->
|
|
355
|
+
EOF
|
|
356
|
+
)"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Structured Findings**: Include the structured findings block above at the end of your comment. Your prefix: `INFRA`. See the Structured Findings Protocol section above for format rules.
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Coverage Matrix — INFRA Agent
|
|
363
|
+
|
|
364
|
+
| Defect Category | Check Item(s) | Status | Ref |
|
|
365
|
+
|----------------|---------------|--------|-----|
|
|
366
|
+
| Container restart downtime risk | Item 1 | COVERED | |
|
|
367
|
+
| Database container restart safety | Item 1b | COVERED | #146 |
|
|
368
|
+
| Config mechanism change equivalence | Item 1c | COVERED | #185 |
|
|
369
|
+
| Blue-green deployment compatibility | Item 2 | COVERED | |
|
|
370
|
+
| Rollback path verification | Item 3 | COVERED | |
|
|
371
|
+
| Missing deploy prerequisites (env/secrets/DNS) | Item 4 | COVERED | |
|
|
372
|
+
| In-flight request impact | Item 5 | COVERED | |
|
|
373
|
+
| Health endpoint response contract breakage | Item 6 | COVERED | #321 |
|
|
374
|
+
| Secret delivery chain (SOPS → container) | Item 7 | COVERED | |
|
|
375
|
+
| Shell metacharacter safety in .env writers | Item 7b | COVERED | #286 |
|
|
376
|
+
| Cross-domain service interactions (curl/wget) | Item 8 | COVERED | |
|
|
377
|
+
| Sibling workflow drift (CI vs deploy) | Item 9 | COVERED | #222 |
|
|
378
|
+
| Deploy scope awareness (auto vs manual trigger) | Item 10 | COVERED | #239 |
|
|
379
|
+
| Config field type/doc-comment consistency | Item 11 | COVERED | #190 |
|
|
380
|
+
| appleboy/ssh-action Go template safety | Item 12 | COVERED | #226 |
|
|
381
|
+
| Insecure config file defaults | Item 13 | COVERED | #301 |
|
|
382
|
+
| Host-side database tool invocations | Item 14 | COVERED | #322 |
|
|
383
|
+
| External tool config schema validation (structural correctness) | Item 15 | COVERED | #1104 |
|
|
384
|
+
| Docker image tag pinning (mutable tags) | — | GAP | |
|
|
385
|
+
| Network policy / inter-container isolation | — | GAP | |
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
install: internal
|
|
3
|
+
---
|
|
4
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
6
|
+
|
|
7
|
+
# Shared Review Protocols
|
|
8
|
+
|
|
9
|
+
This file contains the shared protocols that ALL review agents must follow.
|
|
10
|
+
It is read alongside individual persona files during Phase 3C agent dispatch.
|
|
11
|
+
Canonical source: `docs/spec/review-protocol.md` — sync changes there first.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Per-Agent Input Scoping
|
|
15
|
+
|
|
16
|
+
**Each domain agent receives only the diff slice relevant to its domain**, not the full PR changeset. The orchestrator (Phase 3C of `review-pr.md`) pre-computes these slices and substitutes `[DOMAIN_DIFF_SLICE]` in each agent's prompt before dispatch.
|
|
17
|
+
|
|
18
|
+
**Rationale**: Passing the full changeset to every agent inflates per-child input cost on large PRs. A billing agent reviewing auth code produces noise, not signal. Scoped inputs reduce cost and improve focus.
|
|
19
|
+
|
|
20
|
+
**Fallback rule**: If a domain's file pattern matches nothing in the PR (slice is empty), the orchestrator falls back to the full diff — ensuring no agent is launched with an empty context. This preserves review coverage on PRs where domain boundaries are blurry.
|
|
21
|
+
|
|
22
|
+
**Security agent exception**: The General Security agent ALWAYS receives the full diff — security vulnerabilities are cross-cutting and cannot be safely scoped to a file-path filter.
|
|
23
|
+
|
|
24
|
+
## Tool-Result Truncation Discipline
|
|
25
|
+
|
|
26
|
+
All tool results consumed by agents — including diff slices, file reads, and command outputs — are capped at **~100K characters**. This mirrors the runner's built-in 100K-char tool-result cap (`bin/runner.mjs`).
|
|
27
|
+
|
|
28
|
+
**Agents must NOT re-fetch `gh pr diff` in full** — use the pre-supplied `[DOMAIN_DIFF_SLICE]` instead. If an agent needs to read a specific file in full (e.g., to trace an import), cap the read at the relevant section using `head -N` or `sed -n 'X,Yp'`. Never pipe unbounded command output into context without a `| head -N` guard.
|
|
29
|
+
|
|
30
|
+
Rationale: agents receiving oversized context perform worse, not better — attention dilutes across irrelevant content, and token limits risk truncating the structured findings block that the triage phase depends on.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Evidence-Based Review Protocol (ALL Agents Follow) <!-- allowlist:check-protocol-restatements -->
|
|
35
|
+
|
|
36
|
+
Every agent MUST follow this protocol:
|
|
37
|
+
|
|
38
|
+
### 1. Start From the PR Diff
|
|
39
|
+
|
|
40
|
+
**Input scoping**: You have been given a pre-computed diff slice containing only the files relevant to your domain (`[DOMAIN_DIFF_SLICE]`). Do NOT re-fetch `gh pr diff [PR_NUMBER]` in full — use the slice provided. This is capped at ~100K chars, mirroring the runner's tool-result limit.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Verify review is still current before reading diff
|
|
44
|
+
CURRENT_SHA=$(gh pr view [PR_NUMBER] --json headRefOid --jq '.headRefOid')
|
|
45
|
+
if [ "$CURRENT_SHA" != "[REVIEW_SHA]" ]; then
|
|
46
|
+
echo "WARNING: PR HEAD changed during review. Review may be stale."
|
|
47
|
+
echo "Review pinned to: [REVIEW_SHA_SHORT]"
|
|
48
|
+
echo "Current HEAD: $(echo $CURRENT_SHA | cut -c1-7)"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# List files in your domain slice
|
|
52
|
+
gh pr diff [PR_NUMBER] --name-only
|
|
53
|
+
|
|
54
|
+
# Use the pre-computed domain diff slice supplied by the orchestrator:
|
|
55
|
+
# [DOMAIN_DIFF_SLICE]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Tool-result truncation**: When reading individual files or running commands for deeper investigation, always cap output: `cat file.py | head -200`, `grep ... | head -50`. Never pipe unbounded output into context.
|
|
59
|
+
|
|
60
|
+
**Hot-spot prior**:
|
|
61
|
+
[CHURN_CONTEXT]
|
|
62
|
+
|
|
63
|
+
If a file you are reviewing is listed above as a hot-spot, apply deeper scrutiny to it — high historical churn correlates with defect density. Prefer tracing that file's full code paths (Evidence-Based Review Protocol §2) over a quick pattern scan, and weight ambiguous findings in hot-spot files toward LIKELY rather than POSSIBLE.
|
|
64
|
+
|
|
65
|
+
### 2. Dynamic Exploration
|
|
66
|
+
- From each changed file, follow imports and function calls
|
|
67
|
+
- Trace data flows across service boundaries (API → Redis → Worker)
|
|
68
|
+
- Search for related code: `grep -rn "function_name" services/`
|
|
69
|
+
|
|
70
|
+
### 3. Validation Before Reporting
|
|
71
|
+
|
|
72
|
+
| Confidence | Criteria | Action |
|
|
73
|
+
|------------|----------|--------|
|
|
74
|
+
| **CONFIRMED** | Traced the full code path, found specific lines proving the bug | Report as blocking — P1 issue |
|
|
75
|
+
| **LIKELY** | Code pattern suggests issue but mitigations might exist elsewhere | Report with caveat — P2 issue |
|
|
76
|
+
| **POSSIBLE** | Suspicious pattern but couldn't trace the full flow | Report as informational — P3 advisory (non-blocking) |
|
|
77
|
+
| **UNFOUNDED** | Looked for the issue but found mitigations/correct handling | Do NOT report |
|
|
78
|
+
|
|
79
|
+
### 3.5 REPRODUCTION GATE — Required Before CONFIRMED Classification
|
|
80
|
+
|
|
81
|
+
**MANDATORY**: Before classifying any finding as CONFIRMED, you MUST document one of the following forms of reproduction evidence in your report. A pattern match alone is not sufficient.
|
|
82
|
+
|
|
83
|
+
**Acceptable reproduction evidence (one of)**:
|
|
84
|
+
- **(a) Full code path trace**: List the execution chain from PR-changed code to the failure point. Minimum: 3 steps with specific file + line for each. Example: `src/api/routers/billing.py:142 → credits.py:check_balance():87 → returns None → caller at billing.py:148 raises AttributeError`. The chain must terminate at the actual failure — not at "and then it could fail."
|
|
85
|
+
- **(b) Specific input demonstration**: Provide concrete input values that trigger the failure. Example: `POST /api/v1/scrape with {"url": "http://internal:6432/"}` → `requests.get()` hits internal DB port → SSRF confirmed. The values must be specific (not "if an attacker provides a malicious URL") and must map to actual code in the PR diff.
|
|
86
|
+
|
|
87
|
+
**Downgrade rule**: If you cannot produce either (a) or (b) after a reasonable trace attempt, you MUST classify the finding as **POSSIBLE** — not CONFIRMED or LIKELY. Do NOT use CONFIRMED when the finding is based on:
|
|
88
|
+
- A suspicious pattern without tracing whether the condition is reachable via changed code
|
|
89
|
+
- A theoretical exploit path not grounded in specific lines from the diff
|
|
90
|
+
- A heuristic ("this type of code often has X bug") without verification
|
|
91
|
+
|
|
92
|
+
**POSSIBLE findings are informational advisories** — they are logged and tracked but do NOT block merge and do NOT trigger mandatory fix PRs. When in doubt, POSSIBLE is the correct classification. <!-- Added: forge#371 -->
|
|
93
|
+
|
|
94
|
+
### 4. SEVERITY CLASSIFICATION — TRACE THE IMPACT
|
|
95
|
+
|
|
96
|
+
**CRITICAL RULE: Never dismiss a finding as "minor", "cosmetic", or "harmless" without tracing its downstream impact.** If you spot something unusual (redundant code, odd patterns, duplicated values), ask: "Does this cause a runtime error, data corruption, or wrong behavior in any code path that touches it?" Trace forward through every consumer of the construct.
|
|
97
|
+
|
|
98
|
+
**Severity decision tree:**
|
|
99
|
+
1. Will this error at runtime? → **HIGH or CRITICAL** (not "minor redundancy")
|
|
100
|
+
2. Will this produce wrong data silently? → **HIGH**
|
|
101
|
+
3. Will this cause degraded performance? → **MEDIUM**
|
|
102
|
+
4. Is it genuinely cosmetic with no runtime impact after tracing all consumers? → **LOW**
|
|
103
|
+
|
|
104
|
+
If you're unsure whether something is cosmetic or a runtime error, **assume it's a runtime error** and flag it for investigation. A false positive costs a minute of review time. A missed runtime error costs production downtime.
|
|
105
|
+
|
|
106
|
+
### 5. INTERACTION ANALYSIS — "Pre-existing" Is Not "Safe"
|
|
107
|
+
|
|
108
|
+
**CRITICAL RULE: Never dismiss a finding as "pre-existing, not introduced by this PR" without checking whether NEW code in the PR interacts with the pre-existing construct to create a bug.**
|
|
109
|
+
|
|
110
|
+
A redundant import, an unused variable, or a duplicated constant may be harmless in isolation. But new code added in the same scope can turn it into a crash. Example: a local `import os` inside a function is harmless until new code above it calls `os.getenv()` — Python treats `os` as local for the entire function scope, causing `UnboundLocalError` before the import line is reached.
|
|
111
|
+
|
|
112
|
+
**Before dismissing anything as "pre-existing":**
|
|
113
|
+
1. List every NEW line in the PR that references the pre-existing construct
|
|
114
|
+
2. For each reference, ask: "Does the pre-existing construct cause this new line to fail at runtime?"
|
|
115
|
+
3. If yes → CONFIRMED finding, not a dismissal
|
|
116
|
+
|
|
117
|
+
### 6. FALSE POSITIVE PREVENTION
|
|
118
|
+
|
|
119
|
+
**Before claiming variable scope issues:** Read the FULL function, count indentation levels, check if/else structure.
|
|
120
|
+
|
|
121
|
+
**Before claiming type/unit mismatches:** Trace the variable to its source. Check if naming is misleading (e.g., `balanceCents` might hold microcents).
|
|
122
|
+
|
|
123
|
+
**Before claiming missing functions/imports:** `grep -rn "functionName" .` — check re-exports, aliases.
|
|
124
|
+
|
|
125
|
+
**Before claiming unreachable code:** Check all callers, dynamic dispatch, test code.
|
|
126
|
+
|
|
127
|
+
**Before dismissing redundant imports as harmless:** In Python, a local `import X` inside a function makes `X` a local variable for the ENTIRE function scope. Any use of `X` before that import line will raise `UnboundLocalError`. Check whether any code (existing or new) references `X` before the local import. This is a CONFIRMED CRITICAL if found — it crashes at runtime.
|
|
128
|
+
|
|
129
|
+
### 7. Report Format
|
|
130
|
+
|
|
131
|
+
Every finding must include:
|
|
132
|
+
- **File:Line** — Exact location
|
|
133
|
+
- **Code snippet** — The problematic code
|
|
134
|
+
- **Evidence** — Why this is a bug (show the code path)
|
|
135
|
+
- **Confidence** — CONFIRMED/LIKELY/POSSIBLE
|
|
136
|
+
- **What you checked** — List files you read to verify
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Structured Findings Protocol
|
|
141
|
+
|
|
142
|
+
**All review agents MUST include a machine-readable findings block at the end of their PR comment.** This is NON-OPTIONAL. Without structured findings, the review system cannot create GitHub issues, and findings die as unread PR comments. Every finding that doesn't become a GitHub issue is a finding that will never be addressed.
|
|
143
|
+
|
|
144
|
+
### Format
|
|
145
|
+
|
|
146
|
+
Append this block at the very end of your comment (after the `---` footer line, still inside the EOF heredoc). It uses HTML comments so it's invisible in rendered markdown:
|
|
147
|
+
|
|
148
|
+
`<!-- REVIEW-FINDINGS-START -->`
|
|
149
|
+
`<!-- FINDING:PREFIX-N|CONFIDENCE|SEVERITY|file.py:line|One-line summary -->`
|
|
150
|
+
`<!-- REVIEW-FINDINGS-END -->`
|
|
151
|
+
|
|
152
|
+
### Rules
|
|
153
|
+
|
|
154
|
+
1. **Include ALL findings at CONFIRMED, LIKELY, and POSSIBLE confidence** — every finding becomes a GitHub issue. Nothing stays as just a PR comment. **POSSIBLE findings are informational advisories (P3/non-blocking)** — they are tracked but do not require a fix PR and do not block merge. CONFIRMED and LIKELY findings are blocking at P1/P2 respectively.
|
|
155
|
+
2. **One line per finding** — sequential numbering (PREFIX-1, PREFIX-2, ...)
|
|
156
|
+
3. **Confidence**: `CONFIRMED`, `LIKELY`, or `POSSIBLE`
|
|
157
|
+
4. **Severity**: `CRITICAL`, `HIGH`, `MEDIUM`, or `LOW`
|
|
158
|
+
5. **Location**: Exact `file:line` reference
|
|
159
|
+
6. **Summary**: Concise one-line description (no pipe `|` characters in summary)
|
|
160
|
+
7. **Empty block**: If no findings at all, include just the START/END markers
|
|
161
|
+
8. **HTML comments**: The block is invisible in rendered markdown but parseable by the review system
|
|
162
|
+
|
|
163
|
+
### Domain Prefixes
|
|
164
|
+
|
|
165
|
+
| Agent | Prefix |
|
|
166
|
+
|-------|--------|
|
|
167
|
+
| General Security | `SEC` |
|
|
168
|
+
| Auth Conventions | `AUTH` |
|
|
169
|
+
| Billing Integrity | `BILL` |
|
|
170
|
+
| Concurrency | `CONC` |
|
|
171
|
+
| Scraper Logic | `SCRP` |
|
|
172
|
+
| Frontend Quality | `FE` |
|
|
173
|
+
| API Design | `API` |
|
|
174
|
+
| Database & Migration | `DB` |
|
|
175
|
+
| Infrastructure | `INFRA` |
|
|
176
|
+
### Example
|
|
177
|
+
|
|
178
|
+
`<!-- REVIEW-FINDINGS-START -->`
|
|
179
|
+
`<!-- FINDING:SEC-1|CONFIRMED|HIGH|src/api/routers/upload.py:45|SQL injection via unsanitized user input in query parameter -->`
|
|
180
|
+
`<!-- FINDING:SEC-2|LIKELY|MEDIUM|src/worker/jobs/process.py:312|Potential SSRF through user-controlled proxy URL -->`
|
|
181
|
+
`<!-- REVIEW-FINDINGS-END -->`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Agent Catalog
|