forgedock 1.1.1 → 1.1.3
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 +24 -2
- 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 +70 -20
- 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 +518 -361
- 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/deploy-pr.md +485 -0
- 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/fix-ci.md +577 -0
- 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/recover-orphans.md +576 -0
- 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 +16 -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 +120 -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 +386 -111
- 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 +254 -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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Recover full pipeline context after compaction — find the last active issue, reconstruct FORGE annotations, and resume work
|
|
3
3
|
argument-hint: [issue number (optional — auto-detects if omitted)]
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# /pipeline-resume — Context Recovery After Compaction
|
|
@@ -9,8 +10,11 @@ argument-hint: [issue number (optional — auto-detects if omitted)]
|
|
|
9
10
|
|
|
10
11
|
You are the context-recovery agent. When a session ends or context is compressed, all working state is lost. Your job is to restore it instantly — find the most recently active issue, read all FORGE annotations, reconstruct the current pipeline phase, and re-enter the pipeline exactly where it left off.
|
|
11
12
|
|
|
12
|
-
**Agent model policy**:
|
|
13
|
+
**Agent model policy**: `model: "haiku"`, `effort: low` (mechanical tier — context recovery, annotation reading, state reconstruction). Fallback: `model: "sonnet"` if rate-limited. Feature gate: pass `effort` only on Claude Code >= 2.1.154.
|
|
13
14
|
**NEVER use plan mode (EnterPlanMode).**
|
|
15
|
+
**NEVER use the Agent tool** — pipeline-resume re-enters the pipeline via `Skill(skill="work-on", ...)` only. Using the Agent tool would restart the pipeline in an untracked subprocess rather than resuming the existing tracked session.
|
|
16
|
+
|
|
17
|
+
<!-- FORGE:SPEC_LOADED — pipeline-resume.md loaded and active. Agent is bound by this spec. -->
|
|
14
18
|
|
|
15
19
|
---
|
|
16
20
|
|
|
@@ -36,6 +40,25 @@ If `forge.yaml` is missing: stop and tell the user to run `npx forgedock init` t
|
|
|
36
40
|
|
|
37
41
|
Check `$ARGUMENTS`:
|
|
38
42
|
|
|
43
|
+
- **`--all-stalled`** (fleet recovery mode): shell out to `npx forgedock resume-stalled` to scan all in-flight issues for expired leases and re-dispatch them. This delegates to the durable engine's `scanStalls` for ground-truth stall detection. Add `--dry-run` if only a report is needed. Then STOP — fleet recovery is complete.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Fleet recovery mode — delegate to the engine's resume-stalled CLI
|
|
47
|
+
if echo "$ARGUMENTS" | grep -q -- "--all-stalled"; then
|
|
48
|
+
DRY_RUN_FLAG=""
|
|
49
|
+
if echo "$ARGUMENTS" | grep -q -- "--dry-run"; then
|
|
50
|
+
DRY_RUN_FLAG="--dry-run"
|
|
51
|
+
fi
|
|
52
|
+
echo "Fleet stall recovery: scanning all in-flight issues for expired leases…"
|
|
53
|
+
npx forgedock resume-stalled $DRY_RUN_FLAG
|
|
54
|
+
echo ""
|
|
55
|
+
echo "Fleet recovery complete. To resume a single issue: /pipeline-resume <issue-number>"
|
|
56
|
+
# STOP — fleet recovery does not fall through to single-issue resume.
|
|
57
|
+
fi
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If `--all-stalled` was not specified, continue with single-issue resume:
|
|
61
|
+
|
|
39
62
|
- **Explicit issue number** (e.g., `610`, `#610`): strip `#`, set `TARGET_NUMBER={number}`, skip to Phase 2.
|
|
40
63
|
- **Empty**: proceed with auto-detection below.
|
|
41
64
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Pipeline-wide situational awareness — groups open issues by workflow state, shows active PRs, flags stale items, and reports milestone progress
|
|
3
3
|
argument-hint: [--repo <owner/repo> | --stale-days <N>]
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# /pipeline-status — Pipeline Status
|
|
@@ -184,6 +185,70 @@ echo ""
|
|
|
184
185
|
|
|
185
186
|
---
|
|
186
187
|
|
|
188
|
+
## Phase 3B: Engine Lease State (FORGE:STATE)
|
|
189
|
+
|
|
190
|
+
For issues managed by the durable engine, read the `FORGE:STATE` block from each issue body and display lease status. This gives ground-truth stall detection beyond `updatedAt` heuristics. Gracefully degrades — issues without a `FORGE:STATE` block are skipped (they use the Phase 3 timestamp-based detection above).
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
echo "=== Engine Lease State ==="
|
|
194
|
+
echo ""
|
|
195
|
+
|
|
196
|
+
# gh --label uses AND filtering, so query each label separately and merge
|
|
197
|
+
ENGINE_CANDIDATES=$(
|
|
198
|
+
for LABEL in "workflow:investigating" "workflow:ready-to-build" "workflow:building" "workflow:in-review"; do
|
|
199
|
+
gh issue list $GH_FLAG \
|
|
200
|
+
--state open \
|
|
201
|
+
--label "$LABEL" \
|
|
202
|
+
--limit 100 \
|
|
203
|
+
--json number,title,body 2>/dev/null || echo "[]"
|
|
204
|
+
done | jq -s 'flatten | unique_by(.number)'
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
NOW_MS=$(date +%s)000
|
|
208
|
+
ENGINE_OUTPUT=""
|
|
209
|
+
|
|
210
|
+
while IFS= read -r ROW; do
|
|
211
|
+
NUM=$(echo "$ROW" | jq -r '.number')
|
|
212
|
+
TITLE=$(echo "$ROW" | jq -r '.title[:55]')
|
|
213
|
+
BODY=$(echo "$ROW" | jq -r '.body // ""')
|
|
214
|
+
|
|
215
|
+
# Extract FORGE:STATE JSON block from issue body
|
|
216
|
+
STATE_JSON=$(echo "$BODY" | sed -n '/<!-- FORGE:STATE -->/,/<!-- \/FORGE:STATE -->/p' \
|
|
217
|
+
| grep -v '<!-- ' | tr -d '\n' 2>/dev/null || echo "")
|
|
218
|
+
|
|
219
|
+
if [ -z "$STATE_JSON" ]; then
|
|
220
|
+
continue # No engine state — skip, Phase 3 covers this issue via updatedAt
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
TERMINAL=$(echo "$STATE_JSON" | jq -r '.terminal // false' 2>/dev/null || echo "false")
|
|
224
|
+
if [ "$TERMINAL" = "true" ]; then
|
|
225
|
+
continue # Terminal — not interesting for stall detection
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
LEASE_UNTIL=$(echo "$STATE_JSON" | jq -r '.lease.until // 0' 2>/dev/null || echo "0")
|
|
229
|
+
LEASE_BY=$(echo "$STATE_JSON" | jq -r '.lease.by // "unknown"' 2>/dev/null || echo "unknown")
|
|
230
|
+
PHASE=$(echo "$STATE_JSON" | jq -r '.phase // "unknown"' 2>/dev/null || echo "unknown")
|
|
231
|
+
|
|
232
|
+
if [ "$LEASE_UNTIL" -gt "$NOW_MS" ] 2>/dev/null; then
|
|
233
|
+
REMAINING=$(( (LEASE_UNTIL - NOW_MS) / 60000 ))
|
|
234
|
+
ENGINE_OUTPUT="${ENGINE_OUTPUT} #${NUM} ${TITLE} [${PHASE}] LEASED (${LEASE_BY}, ${REMAINING}m left)\n"
|
|
235
|
+
else
|
|
236
|
+
EXPIRED_AGO=$(( (NOW_MS - LEASE_UNTIL) / 60000 ))
|
|
237
|
+
ENGINE_OUTPUT="${ENGINE_OUTPUT} #${NUM} ${TITLE} [${PHASE}] STALLED (lease expired ${EXPIRED_AGO}m ago)\n"
|
|
238
|
+
fi
|
|
239
|
+
done <<< "$(echo "$ENGINE_CANDIDATES" | jq -c '.[]')"
|
|
240
|
+
|
|
241
|
+
if [ -z "$ENGINE_OUTPUT" ]; then
|
|
242
|
+
echo " No engine-managed issues in flight (or none with FORGE:STATE blocks)."
|
|
243
|
+
else
|
|
244
|
+
echo -e "$ENGINE_OUTPUT"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
echo ""
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
187
252
|
## Phase 4: Milestone Progress
|
|
188
253
|
|
|
189
254
|
Show open milestones with open/closed issue counts and percentage complete.
|
package/commands/qa-sweep.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Full platform QA sweep — auto-discovers every page, tests every UI element via browser automation, creates GitHub issues for all findings
|
|
3
3
|
argument-hint: [all | dashboard | marketing | page <route> | journey | visual | functional | a11y]
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
7
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,9 +12,11 @@ argument-hint: [all | dashboard | marketing | page <route> | journey | visual |
|
|
|
11
12
|
|
|
12
13
|
You are the QA orchestrator. Auto-discover every page across the platform (dashboard, marketing, blog, auth, docs, pricing), then systematically test every UI element, interaction, workflow, and state transition using browser automation. Create GitHub issues for every finding.
|
|
13
14
|
|
|
14
|
-
**Agent model policy**:
|
|
15
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
15
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
16
17
|
|
|
18
|
+
<!-- FORGE:SPEC_LOADED — qa-sweep.md loaded and active. Agent is bound by this spec. -->
|
|
19
|
+
|
|
17
20
|
---
|
|
18
21
|
|
|
19
22
|
## Playwright MCP Prerequisite (MANDATORY — check before proceeding)
|
package/commands/quality-gate.md
CHANGED
|
@@ -13,7 +13,10 @@ You are a quality gate that runs AFTER implementation but BEFORE commit. Your jo
|
|
|
13
13
|
|
|
14
14
|
You do NOT post to GitHub. You do NOT create issues. You return findings directly to the builder agent that spawned you.
|
|
15
15
|
|
|
16
|
-
**Agent model policy**:
|
|
16
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
17
|
+
**NEVER use plan mode (EnterPlanMode).**
|
|
18
|
+
|
|
19
|
+
<!-- FORGE:SPEC_LOADED — quality-gate.md loaded and active. Agent is bound by this spec. -->
|
|
17
20
|
|
|
18
21
|
---
|
|
19
22
|
|
|
@@ -49,6 +52,7 @@ This quality gate uses **domain detection** to adapt to your project's actual te
|
|
|
49
52
|
| ROUTER_BUG | Router Python files where gate conditions are narrowed |
|
|
50
53
|
| FORGE_GRAPH | `commands/*.md` specs or `scripts/*` files change (ForgeDock self-consistency) |
|
|
51
54
|
| CONFIG_SCHEMA | Config files for external tools (`traefik/`, `infra/nginx/`, `k8s/`, `terraform/`, `*.conf`, `*.toml` in infra paths, or `docker-compose*.yml` with service definition changes) |
|
|
55
|
+
| BILLING | Files under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths |
|
|
52
56
|
|
|
53
57
|
**Stack-agnostic coverage**: The example domains above reflect common patterns caught in production; they are not requirements. A Go or Ruby project benefits from SECURITY checks. A Node.js project benefits from FRONTEND, PROXY, and DEPLOY checks. Any project benefits from DATABASE and WORKFLOW checks when those file types are present. The stack-specific examples (Python routers, SOPS secrets chain, FastAPI layouts, appleboy SSH deploys) are illustrative — the domain detection system applies the applicable subset to any codebase.
|
|
54
58
|
|
|
@@ -88,7 +92,8 @@ Before running checks, classify the changed files into domains. This avoids runn
|
|
|
88
92
|
| `*.py` in `routers/` or `route.ts` handlers | AUTH |
|
|
89
93
|
| `*.py`, `*.ts`, `*.tsx`, `*.sh` (any code file) | SECURITY (always) |
|
|
90
94
|
| Files containing `os.getenv` or `process.env` | DEPLOY |
|
|
91
|
-
| `*.sql
|
|
95
|
+
| `*.sql`, files in `migrations/`, or `*migrations/*.py` (Alembic) | DATABASE |
|
|
96
|
+
| Files under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths | BILLING |
|
|
92
97
|
| `*.tsx`, `*.ts` in `web/src/` (excluding `route.ts`) | FRONTEND |
|
|
93
98
|
| `*.tsx`, `*.ts` client components with fetch/useSWR | PROXY |
|
|
94
99
|
| `*.sh` files or scripts with `curl`/`wget` | SHELL |
|
|
@@ -116,7 +121,10 @@ DOMAINS=""
|
|
|
116
121
|
IFS=' ' read -ra CHANGED_FILES_ARR <<< "{CHANGED_FILES}"
|
|
117
122
|
for f in "${CHANGED_FILES_ARR[@]}"; do
|
|
118
123
|
case "$f" in
|
|
119
|
-
*.sql|*migrations
|
|
124
|
+
*.sql|*migrations/*|*migrations/*.py) DOMAINS="$DOMAINS DATABASE" ;;
|
|
125
|
+
esac
|
|
126
|
+
case "$f" in
|
|
127
|
+
*billing/*|*credits/*|*subscription*|*ledger*|*payment*) DOMAINS="$DOMAINS BILLING" ;;
|
|
120
128
|
esac
|
|
121
129
|
case "$f" in
|
|
122
130
|
*router*|*route.ts) DOMAINS="$DOMAINS AUTH" ;;
|
|
@@ -236,6 +244,9 @@ Run ONLY the checks whose domain was identified in Step 1.5. Skip checks for dom
|
|
|
236
244
|
- **2N (Runtime UID × filesystem write)**: Run if `INFRA` in DOMAINS
|
|
237
245
|
- **2O (Residual pattern check)**: Run if `ROUTER_BUG` in DOMAINS
|
|
238
246
|
- **2P (External tool config schema)**: Run if `CONFIG_SCHEMA` in DOMAINS
|
|
247
|
+
- **2Q (Billing)**: Run if `BILLING` in DOMAINS
|
|
248
|
+
- **2R (Registry checks)**: ALWAYS run — executes all promoted checks from `scripts/check-registry/manifest.json` <!-- Added: forge#1331 -->
|
|
249
|
+
- **2S (Test failure classification)**: Run if any Step 2 check invoked a test suite and it failed — classifies the failure as PRE_BROKEN, FLAKY, or REAL using `scripts/flaky-quarantine.sh` <!-- Added: forge#1336 -->
|
|
239
250
|
|
|
240
251
|
### 2A: Security (ALL files)
|
|
241
252
|
|
|
@@ -319,14 +330,15 @@ if [ -n "$NEW_ENVS" ]; then
|
|
|
319
330
|
fi
|
|
320
331
|
```
|
|
321
332
|
|
|
322
|
-
### 2D: Database quality (SQL migration files)
|
|
333
|
+
### 2D: Database quality (SQL migration files and Alembic Python migrations)
|
|
323
334
|
|
|
324
|
-
For each `.sql` file:
|
|
335
|
+
For each `.sql` file or Alembic migration `.py` file:
|
|
325
336
|
1. **NOT NULL without DEFAULT**: `ALTER TABLE ... ADD COLUMN ... NOT NULL` without `DEFAULT` locks table and fails on existing rows
|
|
326
337
|
2. **DROP without IF EXISTS**: Fails on fresh databases
|
|
327
338
|
3. **Missing indexes**: New columns used in WHERE/JOIN without indexes
|
|
328
339
|
4. **Unbounded queries**: SELECT without LIMIT or time-bound WHERE clause
|
|
329
340
|
5. **Migration number collision**: Check against origin branch
|
|
341
|
+
6. **IF NOT EXISTS guard**: `CREATE TABLE`/`CREATE INDEX` without `IF NOT EXISTS` guard fails on re-run
|
|
330
342
|
|
|
331
343
|
```bash
|
|
332
344
|
while IFS= read -r f; do
|
|
@@ -334,12 +346,25 @@ while IFS= read -r f; do
|
|
|
334
346
|
grep -nE "ADD COLUMN.*NOT NULL" "$f" | grep -v "DEFAULT" && echo "DB: NOT NULL without DEFAULT in $f"
|
|
335
347
|
grep -nE "DROP (TABLE|COLUMN|INDEX)" "$f" | grep -v "IF EXISTS" && echo "DB: DROP without IF EXISTS in $f"
|
|
336
348
|
grep -nE "SELECT.*FROM" "$f" | grep -v "LIMIT" | grep -v "WHERE.*created_at" && echo "DB: possibly unbounded query in $f"
|
|
337
|
-
|
|
349
|
+
# IF NOT EXISTS guard check — CREATE TABLE/INDEX without guard fails on replay or repeated apply
|
|
350
|
+
grep -nE "^\s*(CREATE TABLE|CREATE INDEX|CREATE UNIQUE INDEX)\b" "$f" | grep -iv "IF NOT EXISTS" && \
|
|
351
|
+
echo "DB-GUARD | MEDIUM | $f | CREATE TABLE/INDEX without IF NOT EXISTS guard — statement will fail on re-run or fresh database replay. Add IF NOT EXISTS."
|
|
352
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.sql$')
|
|
353
|
+
|
|
354
|
+
# Also run structural checks on Alembic Python migration files
|
|
355
|
+
while IFS= read -r f; do
|
|
356
|
+
[ -z "$f" ] && continue
|
|
357
|
+
# Check for op.create_table / op.create_index without if_not_exists or checkfirst guard
|
|
358
|
+
grep -nE "\bop\.create_table\b|\bop\.create_index\b" "$f" | grep -iv "if_not_exists\|checkfirst" && \
|
|
359
|
+
echo "DB-GUARD | MEDIUM | $f | op.create_table/op.create_index without if_not_exists/checkfirst guard — will fail on replay. Add if_not_exists=True or checkfirst=True."
|
|
360
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E 'migrations/.*\.py$')
|
|
338
361
|
|
|
339
362
|
# Migration prefix collision scan (Deploy Gate — HIGH)
|
|
340
|
-
# Runs whenever any SQL file changed. Scans the configured
|
|
341
|
-
# duplicate numeric prefixes
|
|
363
|
+
# Runs whenever any SQL or Alembic Python migration file changed. Scans the configured
|
|
364
|
+
# migrations directory for duplicate numeric prefixes (both .sql and .py files).
|
|
365
|
+
# Duplicate prefixes can cause ordering conflicts during deploy.
|
|
342
366
|
# <!-- Updated: forge#1349 — removed stale validate-migration-order.sh reference (script does not exist) -->
|
|
367
|
+
# <!-- Updated: forge#1329 — extended collision scan to cover .py Alembic migration files -->
|
|
343
368
|
MIGRATIONS_DIR="{WORKTREE_PATH}/infra/migrations"
|
|
344
369
|
# Support configured migration directory from forge.yaml (if set)
|
|
345
370
|
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
@@ -348,8 +373,8 @@ if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
|
348
373
|
[ -n "$_CONFIGURED_MIGRATIONS" ] && MIGRATIONS_DIR="{WORKTREE_PATH}/${_CONFIGURED_MIGRATIONS}"
|
|
349
374
|
fi
|
|
350
375
|
if [ -d "$MIGRATIONS_DIR" ]; then
|
|
351
|
-
# Find duplicate numeric prefixes across all migration files
|
|
352
|
-
DUPLICATE_PREFIXES=$(ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null \
|
|
376
|
+
# Find duplicate numeric prefixes across all migration files (.sql and .py)
|
|
377
|
+
DUPLICATE_PREFIXES=$( { ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null; ls "$MIGRATIONS_DIR"/*.py 2>/dev/null; } \
|
|
353
378
|
| sed 's|.*/\([0-9]*\)_.*|\1|' \
|
|
354
379
|
| sort | uniq -d)
|
|
355
380
|
for prefix in $DUPLICATE_PREFIXES; do
|
|
@@ -1002,6 +1027,219 @@ fi
|
|
|
1002
1027
|
|
|
1003
1028
|
**Separation of concerns**: ForgeDock reasons about architecture, logic, and security; CI runs deterministic tool validators with zero token cost. The quality gate enforces that the CI layer is complete — it does not replace it.
|
|
1004
1029
|
|
|
1030
|
+
### 2Q: Billing domain checks (billing/credits/subscription/ledger/payment code)
|
|
1031
|
+
|
|
1032
|
+
**Triggered when**: BILLING domain is set (changed files are under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths).
|
|
1033
|
+
|
|
1034
|
+
**Why this matters**: Billing code has three reliably grep-detectable defect classes that account for ~27% of review findings in the AlterLab health window: (1) `org_id` not propagated to debit/refund ledger write paths (silent data ownership loss — charges appear with wrong tenant); (2) `asyncio.sleep` / blocking sleep called inside an advisory-lock or open-transaction scope (holds DB connection/lock for the full sleep duration, causing connection exhaustion and deadlocks under load); (3) stdlib `logging.*()` calls passed structlog-style keyword arguments (`event=`, named context keys) that are not valid stdlib kwargs — crashes at runtime with `TypeError: unexpected keyword argument`. <!-- Added: forge#1329 --> <!-- allowlist: AlterLab -->
|
|
1035
|
+
|
|
1036
|
+
**2Q-1: org_id propagation in ledger write functions**
|
|
1037
|
+
|
|
1038
|
+
```bash
|
|
1039
|
+
# Grep billing Python files for ledger write function definitions missing an org_id parameter.
|
|
1040
|
+
# Heuristic: functions named debit/refund/charge/credit/apply_credit/record_usage whose
|
|
1041
|
+
# signature (def line) does not contain org_id are flagged as MEDIUM — reviewer verifies
|
|
1042
|
+
# with full context whether org_id is pulled from a passed object instead.
|
|
1043
|
+
while IFS= read -r f; do
|
|
1044
|
+
[ -z "$f" ] && continue
|
|
1045
|
+
grep -nE "^\s*def (debit|refund|charge|credit|apply_credit|record_usage|create_invoice|issue_credit)\b" "$f" 2>/dev/null | while IFS=: read -r lineno rest; do
|
|
1046
|
+
# Read the full function signature (up to 3 lines to cover multi-line sigs)
|
|
1047
|
+
sig=$(sed -n "${lineno},$((lineno+2))p" "$f" 2>/dev/null)
|
|
1048
|
+
echo "$sig" | grep -qE '\borg_id\b' || \
|
|
1049
|
+
echo "BILLING-1 | MEDIUM | $f:$lineno | Ledger write function '$(echo "$rest" | grep -oE 'def \w+' | head -1)' signature does not include org_id parameter — verify org_id is propagated to all debit/refund paths (AlterLab#25348 pattern)" <!-- allowlist: AlterLab -->
|
|
1050
|
+
done
|
|
1051
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**2Q-2: Blocking sleep inside advisory-lock or open-transaction scope**
|
|
1055
|
+
|
|
1056
|
+
```bash
|
|
1057
|
+
# Grep billing Python files for asyncio.sleep / time.sleep called inside an advisory-lock
|
|
1058
|
+
# or open-transaction context (within 20 lines after lock acquisition or BEGIN/session open).
|
|
1059
|
+
# Holding a lock or DB connection across a sleep starves the connection pool and causes
|
|
1060
|
+
# deadlocks under concurrent load (AlterLab#25725 pattern). <!-- allowlist: AlterLab -->
|
|
1061
|
+
while IFS= read -r f; do
|
|
1062
|
+
[ -z "$f" ] && continue
|
|
1063
|
+
grep -nE "\basyncio\.sleep\b|\btime\.sleep\b" "$f" 2>/dev/null | while IFS=: read -r lineno rest; do
|
|
1064
|
+
start=$((lineno > 20 ? lineno - 20 : 1))
|
|
1065
|
+
window=$(sed -n "${start},$((lineno-1))p" "$f" 2>/dev/null)
|
|
1066
|
+
echo "$window" | grep -qE 'advisory_lock|BEGIN\b|async with.*session|async with.*conn|async with.*transaction|with.*engine\b' && \
|
|
1067
|
+
echo "BILLING-2 | HIGH | $f:$lineno | asyncio.sleep/time.sleep inside advisory-lock or open-transaction scope — holds DB connection/lock for full sleep duration, causing connection exhaustion and deadlocks under load (AlterLab#25725 pattern). Move sleep outside lock/transaction scope." <!-- allowlist: AlterLab -->
|
|
1068
|
+
done
|
|
1069
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**2Q-3: stdlib logging calls with structlog-style kwargs**
|
|
1073
|
+
|
|
1074
|
+
```bash
|
|
1075
|
+
# Grep changed Python files for logging.*()/logger.*() calls that pass keyword arguments
|
|
1076
|
+
# that are NOT valid stdlib logging kwargs (exc_info, stack_info, stacklevel, extra).
|
|
1077
|
+
# Structlog-style kwargs (event=, extra_=, named context keys) are NOT accepted by stdlib
|
|
1078
|
+
# Logger methods and crash at runtime with TypeError: unexpected keyword argument.
|
|
1079
|
+
# (AlterLab#25715, #25837, #25819 pattern) <!-- allowlist: AlterLab -->
|
|
1080
|
+
while IFS= read -r f; do
|
|
1081
|
+
[ -z "$f" ] && continue
|
|
1082
|
+
# Match logging.info/warning/error/debug/critical and logger.info/etc. calls with
|
|
1083
|
+
# non-stdlib kwargs. Valid stdlib kwargs: exc_info, stack_info, stacklevel, extra.
|
|
1084
|
+
# Flag any named kwarg that is NOT one of those four.
|
|
1085
|
+
grep -nE "\b(logging|logger)\.(info|warning|error|debug|critical|exception)\s*\(.*\b\w+\s*=" "$f" 2>/dev/null | \
|
|
1086
|
+
grep -vE "\b(exc_info|stack_info|stacklevel|extra)\s*=" | \
|
|
1087
|
+
grep -v "^\s*#" && \
|
|
1088
|
+
echo "BILLING-3 | HIGH | $f | stdlib logging call with non-stdlib keyword argument detected — stdlib Logger.info/warning/error/etc. only accept exc_info, stack_info, stacklevel, extra as kwargs. Structlog-style kwargs (event=, named context keys) cause TypeError at runtime (AlterLab#25715 pattern). Use structlog.get_logger() instead of logging if structlog kwargs are needed." <!-- allowlist: AlterLab -->
|
|
1089
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
**Flag as findings**:
|
|
1093
|
+
- Ledger write function missing `org_id` parameter: **MEDIUM** — heuristic; reviewer verifies whether org_id is obtained from a passed object. Silent data ownership loss if omitted.
|
|
1094
|
+
- `asyncio.sleep`/`time.sleep` inside advisory-lock or transaction scope: **HIGH** — guaranteed connection/lock starvation under concurrent load.
|
|
1095
|
+
- stdlib `logging.*()` with non-stdlib kwargs: **HIGH** — guaranteed `TypeError` crash at runtime when the log call is reached.
|
|
1096
|
+
|
|
1097
|
+
**Note**: 2Q-3 (stdlib logging check) fires on ALL changed Python files regardless of path — stdlib logger misuse is not specific to billing paths and is a universal crash class. 2Q-1 and 2Q-2 fire only on billing-domain files.
|
|
1098
|
+
|
|
1099
|
+
### 2R: Registry checks (ALWAYS — promoted patterns from recurring review findings)
|
|
1100
|
+
|
|
1101
|
+
<!-- Added: forge#1331 -->
|
|
1102
|
+
|
|
1103
|
+
**Purpose**: Run all checks that have been promoted from recurring review findings into the registry. These are deterministic scripts that catch in milliseconds what previously required a full review cycle to detect. Each check was generated once by an LLM after the same defect class appeared 3+ times — thereafter it runs forever at zero LLM cost.
|
|
1104
|
+
|
|
1105
|
+
**Registry location**: `scripts/check-registry/manifest.json` (relative to repo root)
|
|
1106
|
+
|
|
1107
|
+
```bash
|
|
1108
|
+
REGISTRY_FILE="{WORKTREE_PATH}/scripts/check-registry/manifest.json"
|
|
1109
|
+
REGISTRY_FINDINGS=""
|
|
1110
|
+
|
|
1111
|
+
if [ ! -f "$REGISTRY_FILE" ]; then
|
|
1112
|
+
echo "2R: No check registry found at $REGISTRY_FILE — skipping registry checks."
|
|
1113
|
+
else
|
|
1114
|
+
# Parse manifest and run each registered check
|
|
1115
|
+
CHECK_COUNT=$(jq '.checks | length' "$REGISTRY_FILE" 2>/dev/null || echo 0)
|
|
1116
|
+
|
|
1117
|
+
if [ "$CHECK_COUNT" -eq 0 ]; then
|
|
1118
|
+
echo "2R: Registry is empty — no promoted checks to run."
|
|
1119
|
+
else
|
|
1120
|
+
echo "2R: Running $CHECK_COUNT registered check(s)..."
|
|
1121
|
+
CHECK_INDEX=0
|
|
1122
|
+
while [ "$CHECK_INDEX" -lt "$CHECK_COUNT" ]; do
|
|
1123
|
+
SCRIPT=$(jq -r ".checks[$CHECK_INDEX].script" "$REGISTRY_FILE" 2>/dev/null)
|
|
1124
|
+
SLUG=$(jq -r ".checks[$CHECK_INDEX].slug" "$REGISTRY_FILE" 2>/dev/null)
|
|
1125
|
+
SEVERITY=$(jq -r ".checks[$CHECK_INDEX].severity" "$REGISTRY_FILE" 2>/dev/null)
|
|
1126
|
+
|
|
1127
|
+
if [ -z "$SCRIPT" ] || [ "$SCRIPT" = "null" ]; then
|
|
1128
|
+
CHECK_INDEX=$((CHECK_INDEX + 1))
|
|
1129
|
+
continue
|
|
1130
|
+
fi
|
|
1131
|
+
|
|
1132
|
+
SCRIPT_PATH="{WORKTREE_PATH}/$SCRIPT"
|
|
1133
|
+
|
|
1134
|
+
if [ ! -x "$SCRIPT_PATH" ]; then
|
|
1135
|
+
echo "2R: WARN — registry check $SLUG script not found or not executable at $SCRIPT_PATH"
|
|
1136
|
+
CHECK_INDEX=$((CHECK_INDEX + 1))
|
|
1137
|
+
continue
|
|
1138
|
+
fi
|
|
1139
|
+
|
|
1140
|
+
# Run the check; pass changed files list and worktree path
|
|
1141
|
+
CHECK_OUTPUT=$("$SCRIPT_PATH" "{CHANGED_FILES}" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1142
|
+
CHECK_EXIT=$?
|
|
1143
|
+
|
|
1144
|
+
if [ "$CHECK_EXIT" -eq 1 ]; then
|
|
1145
|
+
# Check fired — append to findings
|
|
1146
|
+
while IFS= read -r line; do
|
|
1147
|
+
[ -z "$line" ] && continue
|
|
1148
|
+
REGISTRY_FINDINGS="${REGISTRY_FINDINGS}
|
|
1149
|
+
${line}"
|
|
1150
|
+
done <<< "$CHECK_OUTPUT"
|
|
1151
|
+
echo "2R: FAIL — $SLUG matched ($SEVERITY)"
|
|
1152
|
+
elif [ "$CHECK_EXIT" -eq 0 ] || [ "$CHECK_EXIT" -eq 2 ]; then
|
|
1153
|
+
echo "2R: PASS — $SLUG"
|
|
1154
|
+
else
|
|
1155
|
+
echo "2R: WARN — $SLUG exited with unexpected code $CHECK_EXIT"
|
|
1156
|
+
fi
|
|
1157
|
+
|
|
1158
|
+
CHECK_INDEX=$((CHECK_INDEX + 1))
|
|
1159
|
+
done
|
|
1160
|
+
fi
|
|
1161
|
+
fi
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
If any registry check produces findings, include them in the Step 3 findings list under the check's slug (e.g. `REGISTRY-migration-number-collision | HIGH | ...`). HIGH-severity registry findings are blocking; MEDIUM are advisory.
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
## Step 2S: Test failure classification (run when tests fail in Step 2)
|
|
1169
|
+
|
|
1170
|
+
<!-- Added: forge#1336 -->
|
|
1171
|
+
|
|
1172
|
+
**Triggered when**: any domain check in Step 2 invokes a test suite command and that command exits non-zero.
|
|
1173
|
+
|
|
1174
|
+
**Why this matters**: Builders and reviewers waste cycles on test failures that pre-exist the PR or are intermittently caused by fixture-state races. A test that fails on both the PR branch and the base branch is *pre-broken* and must not block an unrelated PR. A test that fails then passes on retry is *flaky* and should be quarantined, not "fixed" by the builder.
|
|
1175
|
+
|
|
1176
|
+
### Classification protocol
|
|
1177
|
+
|
|
1178
|
+
For each failing test ID (or test command) surfaced in Step 2:
|
|
1179
|
+
|
|
1180
|
+
```bash
|
|
1181
|
+
# PR_BASE is the target branch (e.g. "staging", "main", or "milestone/slug")
|
|
1182
|
+
# FAILING_TEST is the individual test identifier or re-runnable command
|
|
1183
|
+
# GH_REPO is owner/repo from forge.yaml (used to file the quarantine issue)
|
|
1184
|
+
# PR_ISSUE_NUMBER is the issue this quality gate was invoked from (optional)
|
|
1185
|
+
|
|
1186
|
+
CLASSIFIER="${FORGEDOCK_SCRIPTS:-scripts}/flaky-quarantine.sh"
|
|
1187
|
+
[ -f "$CLASSIFIER" ] || CLASSIFIER="{WORKTREE_PATH}/scripts/flaky-quarantine.sh"
|
|
1188
|
+
|
|
1189
|
+
if [ -f "$CLASSIFIER" ]; then
|
|
1190
|
+
RESULT=$(bash "$CLASSIFIER" \
|
|
1191
|
+
--test "{FAILING_TEST}" \
|
|
1192
|
+
--base "{PR_BASE}" \
|
|
1193
|
+
--worktree "{WORKTREE_PATH}" \
|
|
1194
|
+
--repo "{GH_REPO}" \
|
|
1195
|
+
--issue "{PR_ISSUE_NUMBER}" \
|
|
1196
|
+
--retries 3 \
|
|
1197
|
+
2>&1)
|
|
1198
|
+
echo "$RESULT"
|
|
1199
|
+
CLASSIFICATION=$(echo "$RESULT" | grep '^CLASSIFICATION:' | awk '{print $2}')
|
|
1200
|
+
else
|
|
1201
|
+
# Script not present — fall back to treating all test failures as real.
|
|
1202
|
+
CLASSIFICATION="REAL"
|
|
1203
|
+
echo "NOTE: scripts/flaky-quarantine.sh not found — treating test failure as REAL (no classification)"
|
|
1204
|
+
fi
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
### Classification outcomes
|
|
1208
|
+
|
|
1209
|
+
| Classification | Meaning | Gate behaviour |
|
|
1210
|
+
|---|---|---|
|
|
1211
|
+
| `PRE_BROKEN` | Fails on base branch — broken before this PR | **Does not block the gate.** Log as advisory. Quarantine manifest entry written; issue filed once. |
|
|
1212
|
+
| `FLAKY` | Intermittent — fails then passes on retry | **Does not block the gate.** Log as advisory. Quarantine manifest entry written; issue filed once. |
|
|
1213
|
+
| `REAL` | Deterministic on PR, passes on base | **Blocks the gate** — treated as a regression caused by this PR. |
|
|
1214
|
+
|
|
1215
|
+
### Gate behaviour
|
|
1216
|
+
|
|
1217
|
+
```
|
|
1218
|
+
if CLASSIFICATION == "PRE_BROKEN" or CLASSIFICATION == "FLAKY":
|
|
1219
|
+
# Test does not block the gate — record in findings as advisory only.
|
|
1220
|
+
# Emit a LOW finding so the builder and reviewer see it in the report.
|
|
1221
|
+
emit: "TEST-QUARANTINE | LOW | {FAILING_TEST} | classified {CLASSIFICATION} — quarantine entry recorded; does not block this PR"
|
|
1222
|
+
# Do NOT re-run the test or count it as a gate failure.
|
|
1223
|
+
|
|
1224
|
+
elif CLASSIFICATION == "REAL":
|
|
1225
|
+
# Test failure is caused by this PR — block the gate as before.
|
|
1226
|
+
emit: "TEST-REAL | HIGH | {FAILING_TEST} | deterministic failure on PR branch; passes on base — this PR introduced the regression"
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
### Quarantine manifest
|
|
1230
|
+
|
|
1231
|
+
The manifest lives at `.forgedock/quarantine.jsonl` in the project root (or at the path set in `FORGEDOCK_QUARANTINE_MANIFEST`). Each line is a JSON object:
|
|
1232
|
+
|
|
1233
|
+
```json
|
|
1234
|
+
{"test":"<id>","classification":"PRE_BROKEN|FLAKY","base":"<branch>","pr_branch":"<branch>","issue":"<num>","repo":"<owner/repo>","first_seen":"<ISO-8601>","runs_pr":3,"failures_pr":3,"runs_base":1,"failures_base":1}
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
The manifest is append-only. Duplicate entries (same test already quarantined) are skipped. A corresponding GitHub issue is filed automatically when the script has `--repo` and `gh` is authenticated.
|
|
1238
|
+
|
|
1239
|
+
**Reviewers**: when the manifest exists, read it during Phase 2 of `review-pr.md` to avoid re-filing known-quarantined tests as new findings.
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1005
1243
|
## Step 3: Compile findings
|
|
1006
1244
|
|
|
1007
1245
|
Format findings as a structured list:
|