Skip to content

Commit 03e7259

Browse files
committed
Enforce full refresh cadence and branch protection audit
1 parent 12dd181 commit 03e7259

14 files changed

Lines changed: 194 additions & 13 deletions

.github/workflows/branch-protection-audit.yml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,37 @@ on:
88
permissions:
99
administration: read
1010
contents: read
11+
issues: write
1112

1213
jobs:
1314
audit:
1415
runs-on: ubuntu-latest
1516
steps:
1617
- uses: actions/checkout@v4
1718
- name: Compare main protection to expected spec
19+
id: audit
1820
env:
1921
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2022
run: |
2123
python scripts/verify_expected_branch_protection.py
22-
python scripts/audit_branch_protection_api.py --warn-only
23-
- name: Record audit note
24+
python scripts/export_branch_protection_snapshot.py --out benchmarks/reports/branch_protection_remote_snapshot.json || true
25+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
26+
python scripts/audit_branch_protection_api.py
27+
else
28+
python scripts/audit_branch_protection_api.py --warn-only
29+
fi
30+
- name: Upload remote protection snapshot
31+
if: always()
32+
uses: actions/upload-artifact@v4
33+
with:
34+
name: branch-protection-remote-snapshot
35+
path: benchmarks/reports/branch_protection_remote_snapshot.json
36+
if-no-files-found: ignore
37+
- name: Open issue when protection mismatches (dispatch only)
38+
if: failure() && github.event_name == 'workflow_dispatch'
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2441
run: |
25-
echo "Attach Settings screenshot to docs/BRANCH_PROTECTION_RECORD.md when protection changes."
42+
gh issue create \
43+
--title "branch protection drift on main" \
44+
--body "Run \`python scripts/print_branch_protection_gh_recipe.py\` and attach screenshot to docs/BRANCH_PROTECTION_RECORD.md. Workflow: branch-protection-audit."

.github/workflows/reference-authority.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
run: python scripts/verify_expected_branch_protection.py
3131
- name: Reference refresh cadence (max 35 days)
3232
run: python scripts/check_reference_refresh_cadence.py --max-days 35
33+
- name: Full flagship refresh cadence (live-export-full)
34+
run: python scripts/check_flagship_full_refresh_cadence.py --max-days 35
3335
- name: Flagship refresh trigger paths
3436
run: python scripts/check_flagship_refresh_triggers.py --base-ref ${{ github.event.pull_request.base.sha || github.event.before || 'origin/main' }}
3537
- name: Reference system status artifact

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Reference system (host-realistic flagship)
44

5+
### 2026-05-28 — Full refresh cadence gate and branch protection audit
6+
7+
- `check_flagship_full_refresh_cadence.py` (requires `live-export-full` + `authority_ok` within 35 days).
8+
- `batch_public_narrative` in reference system status; branch protection snapshot export workflow.
9+
510
### 2026-05-28 — Refresh #4 full cycle (licensed)
611

712
- `make host-realistic-refresh-cycle-licensed` on WSL; refresh #4 amended to `live-export-full` with `authority_ok`.

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ else
2323
PYTHON ?= python
2424
endif
2525

26-
.PHONY: test test-reference test-slow test-solver test-vendor-moreau smoke-solver smoke-check env-check reference-correctness perf-benchmark diff-check trust-dashboard parity-native-licensed artifact-validation-report parity-report audit dashboard validate-fixture lint typecheck format format-check cov cov-gates compile-deps verify-extended bootstrap-moreau upgrade-host-realistic-vendor host-realistic-rehearsal export-upstream-rehearsal batch-solve-report check-batch-acceptance check-batch-throughput-advisory validate-published-bundle-profile sync-community-metadata check-reference-refresh-cadence verify-branch-protection-expectations verify-reference-system reference-authority-check reference-authority-snapshot reference-system-status reference-system-status-check capture-inter-sim-graph refresh-live-upstream-export refresh-live-upstream-export-live host-realistic-refresh-cycle host-realistic-refresh-cycle-licensed host-realistic-refresh-milestone sync-published-readmes
26+
.PHONY: test test-reference test-slow test-solver test-vendor-moreau smoke-solver smoke-check env-check reference-correctness perf-benchmark diff-check trust-dashboard parity-native-licensed artifact-validation-report parity-report audit dashboard validate-fixture lint typecheck format format-check cov cov-gates compile-deps verify-extended bootstrap-moreau upgrade-host-realistic-vendor host-realistic-rehearsal export-upstream-rehearsal batch-solve-report check-batch-acceptance check-batch-throughput-advisory validate-published-bundle-profile sync-community-metadata check-reference-refresh-cadence check-flagship-full-refresh-cadence verify-branch-protection-expectations verify-reference-system reference-authority-check reference-authority-snapshot reference-system-status reference-system-status-check capture-inter-sim-graph refresh-live-upstream-export refresh-live-upstream-export-live host-realistic-refresh-cycle host-realistic-refresh-cycle-licensed host-realistic-refresh-milestone sync-published-readmes
2727

2828
test:
2929
$(PYTHON) -m pytest -q
@@ -129,7 +129,10 @@ sync-community-metadata:
129129
check-reference-refresh-cadence:
130130
$(PYTHON) scripts/check_reference_refresh_cadence.py --max-days 35
131131

132-
verify-reference-system: reference-authority-check reference-system-status-check validate-published-bundle-profile sync-community-metadata
132+
check-flagship-full-refresh-cadence:
133+
$(PYTHON) scripts/check_flagship_full_refresh_cadence.py --max-days 35
134+
135+
verify-reference-system: reference-authority-check reference-system-status-check check-flagship-full-refresh-cadence validate-published-bundle-profile sync-community-metadata
133136
$(PYTHON) -m pytest \
134137
tests/governance/test_published_run_index.py \
135138
tests/governance/test_host_realistic_publish_evidence.py \
@@ -145,6 +148,7 @@ verify-reference-system: reference-authority-check reference-system-status-check
145148
tests/governance/test_reference_refresh_cadence.py \
146149
tests/governance/test_record_reference_refresh.py \
147150
tests/governance/test_reference_system_status.py \
151+
tests/governance/test_flagship_full_refresh_cadence.py \
148152
tests/governance/test_run_host_realistic_publish.py \
149153
tests/governance/test_host_realistic_refresh_cycle.py \
150154
tests/governance/test_batch_solve_report.py \

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ flowchart LR
3737
| Flagship | [`host-realistic-20260525`](benchmarks/published_runs/host-realistic-20260525/) |
3838
| Family | `conicshield-transition-bank-v1` |
3939
| Tier | `vendor_native`, `live_upstream_dump` |
40-
| Map | [`docs/REFERENCE_AUTHORITY.md`](docs/REFERENCE_AUTHORITY.md) |
40+
| Map | [`docs/REFERENCE_SYSTEM.md`](docs/REFERENCE_SYSTEM.md) |
41+
| Status | [`benchmarks/reports/reference_system_status.json`](benchmarks/reports/reference_system_status.json) |
4142

4243
**Qualification:** export loop is closed in-repo; graph is host-realistic **fork** via inter-sim `RLEnvironment`**not** a Maps/session navigation graph.
4344

@@ -47,7 +48,7 @@ flowchart LR
4748
| Index | [`benchmarks/PUBLISHED_RUN_INDEX.json`](benchmarks/PUBLISHED_RUN_INDEX.json) |
4849
| Release pointer | [`benchmarks/releases/conicshield-transition-bank-v1/CURRENT.json`](benchmarks/releases/conicshield-transition-bank-v1/CURRENT.json) |
4950

50-
Refresh: `make host-realistic-refresh-cycle`[`docs/HOST_REALISTIC_REFRESH_PROCEDURE.md`](docs/HOST_REALISTIC_REFRESH_PROCEDURE.md).
51+
Refresh: `make host-realistic-refresh-cycle-licensed`[`docs/HOST_REALISTIC_CADENCE_POLICY.md`](docs/HOST_REALISTIC_CADENCE_POLICY.md).
5152

5253
**Constraints implemented:** `simplex`, `turn_feasibility`, `box`, `rate`. `progress` / `clearance`: deferred — [adr](docs/adr/001-progress-clearance-constraints.md).
5354

benchmarks/reports/reference_system_status.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schema_version": "conicshield_reference_system_status/v1",
3-
"generated_at_utc": "2026-05-28T17:05:22Z",
3+
"generated_at_utc": "2026-05-28T17:07:59Z",
44
"flagship_run_id": "host-realistic-20260525",
55
"family_id": "conicshield-transition-bank-v1",
66
"reference_authority_aligned": true,
@@ -9,9 +9,12 @@
99
"last_flagship_refresh_at_utc": "2026-05-28T17:00:56Z",
1010
"refresh_count": 4,
1111
"full_cycle_refresh_count": 1,
12-
"cadence_age_days": 0.003079939814814815,
12+
"last_full_refresh_at_utc": "2026-05-28T17:00:56Z",
13+
"cadence_age_days": 0.004896942418981481,
1314
"cadence_policy_ok": true,
15+
"full_refresh_cadence_ok": true,
1416
"batch_story": "throughput_win",
17+
"batch_public_narrative": "viability_only",
1518
"inter_sim_revision": "f1f04ee11d064262f5ee2810abfcb01715260182",
1619
"expected_required_checks": [
1720
"quality",

docs/HOST_REALISTIC_CADENCE_POLICY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Each refresh must complete, in order:
3838

3939
```bash
4040
python scripts/check_reference_refresh_cadence.py --max-days 35
41+
python scripts/check_flagship_full_refresh_cadence.py --max-days 35
4142
```
4243

43-
Fails CI when the last `EXPORT_PROVENANCE.last_flagship_refresh_at_utc` is older than 35 days (monthly policy + slack).
44+
Fails CI when export or **full** (`live-export-full` + `authority_ok`) refresh is older than 35 days.

docs/REFERENCE_SYSTEM.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ Licensed command: `make host-realistic-refresh-cycle-licensed`
4545

4646
```bash
4747
make verify-reference-system
48-
make reference-authority-check
48+
python scripts/check_flagship_full_refresh_cadence.py --max-days 35
4949
python scripts/generate_reference_system_status.py --check
50-
python scripts/check_reference_refresh_cadence.py --max-days 35
5150
```
5251

5352
Branch protection (admin token): `python scripts/audit_branch_protection_api.py`
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
"""Fail when no live-export-full refresh with authority_ok occurred within max-days."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import json
8+
import sys
9+
from datetime import UTC, datetime
10+
from pathlib import Path
11+
12+
13+
def _repo_root() -> Path:
14+
return Path(__file__).resolve().parents[1]
15+
16+
17+
def main() -> int:
18+
p = argparse.ArgumentParser(description=__doc__)
19+
p.add_argument("--max-days", type=int, default=35)
20+
args = p.parse_args()
21+
root = _repo_root()
22+
prov = json.loads(
23+
(root / "benchmarks" / "external_evidence" / "EXPORT_PROVENANCE.json").read_text(encoding="utf-8")
24+
)
25+
history = prov.get("refresh_history") or []
26+
full = [
27+
h
28+
for h in history
29+
if h.get("workflow") == "live-export-full" and h.get("authority_ok") is True
30+
]
31+
if not full:
32+
print("No live-export-full refresh with authority_ok in history.", file=sys.stderr)
33+
return 1
34+
last = full[-1]
35+
completed = str(last.get("completed_at_utc", ""))
36+
then = datetime.strptime(completed, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC)
37+
age = (datetime.now(UTC) - then).total_seconds() / 86400.0
38+
if age > args.max_days:
39+
print(
40+
f"Last full refresh {completed} is {age:.1f} days old (max {args.max_days}). "
41+
"Run: make host-realistic-refresh-cycle-licensed",
42+
file=sys.stderr,
43+
)
44+
return 1
45+
print(f"Full refresh cadence OK: {completed} ({age:.1f} days ago)")
46+
return 0
47+
48+
49+
if __name__ == "__main__":
50+
raise SystemExit(main())
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
"""Write remote main branch protection snapshot for audit trail."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import json
8+
import os
9+
import sys
10+
from datetime import UTC, datetime
11+
from pathlib import Path
12+
13+
_SCRIPTS = Path(__file__).resolve().parent
14+
if str(_SCRIPTS) not in sys.path:
15+
sys.path.insert(0, str(_SCRIPTS))
16+
from audit_branch_protection_api import _fetch_protection, _github_repo, _required_contexts # noqa: E402
17+
18+
19+
def _repo_root() -> Path:
20+
return Path(__file__).resolve().parents[1]
21+
22+
23+
def main() -> int:
24+
p = argparse.ArgumentParser(description=__doc__)
25+
p.add_argument(
26+
"--out",
27+
type=Path,
28+
default=None,
29+
help="Default: benchmarks/reports/branch_protection_remote_snapshot.json",
30+
)
31+
args = p.parse_args()
32+
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
33+
if not token:
34+
print("No GITHUB_TOKEN", file=sys.stderr)
35+
return 2
36+
owner, repo = _github_repo()
37+
if not owner:
38+
print("GITHUB_REPOSITORY not set", file=sys.stderr)
39+
return 2
40+
protection = _fetch_protection(owner=owner, repo=repo, token=token)
41+
root = _repo_root()
42+
out = args.out or (root / "benchmarks" / "reports" / "branch_protection_remote_snapshot.json")
43+
payload = {
44+
"schema_version": "conicshield_branch_protection_remote_snapshot/v1",
45+
"generated_at_utc": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
46+
"repository": f"{owner}/{repo}",
47+
"branch": "main",
48+
"configured": protection is not None,
49+
"required_contexts": sorted(_required_contexts(protection)) if protection else [],
50+
"raw": protection,
51+
}
52+
out.parent.mkdir(parents=True, exist_ok=True)
53+
out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
54+
print(out)
55+
return 0 if protection else 1
56+
57+
58+
if __name__ == "__main__":
59+
raise SystemExit(main())

0 commit comments

Comments
 (0)