Skip to content

Commit 96d1476

Browse files
Brian KrafftCopilot
andcommitted
feat: add fail-closed phase gate enforcement system
- phase-config.yml: Phase dependency graph (source of truth) - phase-enforce.yml: 5-layer enforcement engine with retry/backoff - bypass-audit.yml: Emergency bypass audit trail - ARCHITECTURE.md: Full threat model and decision matrix - configure-branch-protection.sh: One-shot setup script - Updated PR template with bypass reason section Pattern lineage: eight-eyes/circuit_breaker → squad-audit/label-enforce Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent aad0d8b commit 96d1476

6 files changed

Lines changed: 1088 additions & 0 deletions

File tree

.github/phase-config.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Phase Gate Dependency Configuration
2+
# =====================================
3+
# This file defines the dependency graph between project phases.
4+
# Milestone names in GitHub MUST match the phase names here exactly.
5+
#
6+
# Enforcement workflow reads this file on every PR event.
7+
# Changes to this file require a PR (enforced by branch protection).
8+
#
9+
# Dependency Graph:
10+
#
11+
# P0 (Emergency Hardening)
12+
# / \
13+
# / \
14+
# v v
15+
# P1 (Foundation) P2 (Smart Sandbox)
16+
# | |
17+
# v |
18+
# P3 (store.py) |
19+
# | |
20+
# v |
21+
# P4 (tool+mcp) |
22+
# | |
23+
# v v
24+
# P5 (evolver+grounding) <--+
25+
# |
26+
# v
27+
# P6 (Production Readiness)
28+
# |
29+
# v
30+
# P7 (Enforcement & Launch)
31+
32+
phases:
33+
"Phase 0 — Emergency Hardening":
34+
prerequisites: []
35+
description: "Stop the bleeding — critical security, error handling, env hygiene"
36+
37+
"Phase 1 — Foundation Architecture":
38+
prerequisites:
39+
- "Phase 0 — Emergency Hardening"
40+
description: "Hexagonal skeleton, DI container, type system, CI foundation"
41+
42+
"Phase 2 — Smart Sandbox":
43+
prerequisites:
44+
- "Phase 0 — Emergency Hardening"
45+
description: "E2B integration, capability leases, 9-stage pre-execution pipeline"
46+
47+
"Phase 3 — Extract store.py":
48+
prerequisites:
49+
- "Phase 1 — Foundation Architecture"
50+
description: "Decompose store.py god-class into 7 focused modules"
51+
52+
"Phase 4 — Extract tool_layer + mcp_server":
53+
prerequisites:
54+
- "Phase 3 — Extract store.py"
55+
description: "Decompose tool_layer.py (6 modules) and mcp_server.py (5 modules)"
56+
57+
"Phase 5 — Extract evolver + grounding":
58+
prerequisites:
59+
- "Phase 2 — Smart Sandbox"
60+
- "Phase 4 — Extract tool_layer + mcp_server"
61+
description: "Decompose evolver.py (9 modules) and grounding_agent.py (8 modules)"
62+
63+
"Phase 6 — Production Readiness":
64+
prerequisites:
65+
- "Phase 5 — Extract evolver + grounding"
66+
description: "Observability, SLOs, deployment architecture, DX polish"
67+
68+
"Phase 7 — Enforcement & Launch":
69+
prerequisites:
70+
- "Phase 6 — Production Readiness"
71+
description: "Eight-eyes integration, SkillGuard, launch checklist"
72+
73+
# Escape hatch configuration
74+
escape_hatch:
75+
label: "emergency:bypass"
76+
require_reason: true
77+
reason_pattern: "## Bypass Reason"
78+
min_reason_length: 10

.github/pull_request_template.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@ Closes #<!-- issue number -->
2222
## 📝 ADR
2323
- [ ] No architecture decisions made
2424
- [ ] ADR written: `docs/adr/ADR-XXX.md`
25+
26+
<!--
27+
## Bypass Reason
28+
(Delete this section unless using emergency:bypass label)
29+
(Explain why this PR must skip phase gate enforcement)
30+
-->

.github/workflows/bypass-audit.yml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# ============================================================================
2+
# Emergency Bypass Audit — Track and report all enforcement overrides
3+
# ============================================================================
4+
#
5+
# Fires when the `emergency:bypass` label is added or removed.
6+
# Creates a persistent audit record as a repo issue with the `audit` label.
7+
# Also removes the bypass label after merge to prevent re-use.
8+
9+
name: Bypass Audit Trail
10+
11+
on:
12+
pull_request:
13+
types: [closed, labeled]
14+
branches: [main]
15+
16+
permissions:
17+
contents: read
18+
issues: write
19+
pull-requests: write
20+
21+
jobs:
22+
# Record when bypass label is applied
23+
audit-bypass-applied:
24+
if: >
25+
github.event.action == 'labeled' &&
26+
github.event.label.name == 'emergency:bypass'
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Record bypass activation
30+
uses: actions/github-script@v7
31+
with:
32+
script: |
33+
const pr = context.payload.pull_request;
34+
const actor = context.actor;
35+
36+
// Extract bypass reason from PR body
37+
const body = pr.body || '';
38+
const reasonIdx = body.indexOf('## Bypass Reason');
39+
let reason = '(No reason provided)';
40+
if (reasonIdx !== -1) {
41+
reason = body.slice(reasonIdx + '## Bypass Reason'.length).trim().split('\n')[0];
42+
}
43+
44+
// Create audit issue
45+
await github.rest.issues.create({
46+
owner: context.repo.owner,
47+
repo: context.repo.repo,
48+
title: `🔓 Bypass Activated: PR #${pr.number} by @${actor}`,
49+
body: [
50+
`## Emergency Bypass Audit Record`,
51+
``,
52+
`| Field | Value |`,
53+
`|-------|-------|`,
54+
`| **PR** | #${pr.number} |`,
55+
`| **Title** | ${pr.title} |`,
56+
`| **Actor** | @${actor} |`,
57+
`| **Timestamp** | ${new Date().toISOString()} |`,
58+
`| **Reason** | ${reason} |`,
59+
`| **PR Labels** | ${pr.labels.map(l => '`' + l.name + '`').join(', ')} |`,
60+
``,
61+
`> This record was auto-generated by the bypass audit system.`,
62+
`> Bypass records are permanent and cannot be deleted.`,
63+
].join('\n'),
64+
labels: ['audit', 'emergency:bypass'],
65+
});
66+
67+
core.info(`📝 Audit record created for bypass on PR #${pr.number}`);
68+
69+
# Clean up bypass label after merge + create completion record
70+
audit-bypass-merged:
71+
if: >
72+
github.event.action == 'closed' &&
73+
github.event.pull_request.merged == true &&
74+
contains(github.event.pull_request.labels.*.name, 'emergency:bypass')
75+
runs-on: ubuntu-latest
76+
steps:
77+
- name: Record bypass merge and clean up
78+
uses: actions/github-script@v7
79+
with:
80+
script: |
81+
const pr = context.payload.pull_request;
82+
83+
// Remove the bypass label so it can't be re-used if PR is reopened
84+
try {
85+
await github.rest.issues.removeLabel({
86+
owner: context.repo.owner,
87+
repo: context.repo.repo,
88+
issue_number: pr.number,
89+
name: 'emergency:bypass',
90+
});
91+
core.info('Removed emergency:bypass label from merged PR');
92+
} catch (err) {
93+
core.warning(`Could not remove bypass label: ${err.message}`);
94+
}
95+
96+
// Find the audit issue for this PR and add merge note
97+
const auditIssues = await github.rest.issues.listForRepo({
98+
owner: context.repo.owner,
99+
repo: context.repo.repo,
100+
labels: 'audit,emergency:bypass',
101+
state: 'open',
102+
});
103+
104+
const auditIssue = auditIssues.data.find(i =>
105+
i.title.includes(`PR #${pr.number}`)
106+
);
107+
108+
if (auditIssue) {
109+
await github.rest.issues.createComment({
110+
owner: context.repo.owner,
111+
repo: context.repo.repo,
112+
issue_number: auditIssue.number,
113+
body: [
114+
`## ✅ Bypass Merge Complete`,
115+
``,
116+
`PR #${pr.number} was merged at ${new Date().toISOString()}.`,
117+
`Merge commit: ${pr.merge_commit_sha}`,
118+
``,
119+
`This audit record will remain open for 30-day review.`,
120+
].join('\n'),
121+
});
122+
}

0 commit comments

Comments
 (0)