From 7c21fdcc0ccf272abd09f470ba4bb7ee8b2b27d8 Mon Sep 17 00:00:00 2001 From: Aaron Stainback Date: Sun, 26 Apr 2026 07:30:56 -0400 Subject: [PATCH 1/5] docs(operations): branch-protection-as-substrate (Otto-329 Phase 4 starter) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per auditor's #1 recommendation (Aaron 2026-04-26 dispatch) — close the live-lock hallucination class structurally by checking actual GitHub branch-protection config into the repo so agents reading substrate see the real gates, not training-data defaults. Files: - `docs/operations/branch-protection.md` — human-readable summary + diagnostic flow + refresh cadence - `docs/operations/branch-protection-lfg-main.json` — raw `gh api` output for Lucent-Financial-Group/Zeta main - `docs/operations/branch-protection-acehack-main.json` — raw output for AceHack/Zeta main Key facts now visible in repo: - LFG main: `required_approving_review_count: 0` (review approval NOT a gate) - Actual gates: CI checks + thread resolution + Copilot review on push + linear history (squash only) Per leaked Claude Code system prompt (verified 2026-04-26): NO "review-approval required" defaults in the harness layer; the hallucination is purely training-data + statistical prior. Substrate-in-repo therefore IS sufficient to override (the multi-layer- dominance analysis in the live-lock memory was off by one layer; only 2 layers fight, not 3). Composes with: - Otto-329 Phase 4 (full backups including settings) - Otto-341 (mechanism over vigilance) - Otto-247 (training-data defaults drift) - `feedback_blocked_status_is_not_review_gating_*` (the live-lock memory) Owed follow-up: - AGENTS.md required-reading entry pointing here (so agent cold-start encounters this naturally) - `tools/hygiene/check-branch-protection-snapshot-stale.sh` (warn if JSON >30 days old) Co-Authored-By: Claude Opus 4.7 --- .../branch-protection-acehack-main.json | 56 ++++++++++ .../branch-protection-lfg-main.json | 56 ++++++++++ docs/operations/branch-protection.md | 102 ++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 docs/operations/branch-protection-acehack-main.json create mode 100644 docs/operations/branch-protection-lfg-main.json create mode 100644 docs/operations/branch-protection.md diff --git a/docs/operations/branch-protection-acehack-main.json b/docs/operations/branch-protection-acehack-main.json new file mode 100644 index 00000000..de5306fe --- /dev/null +++ b/docs/operations/branch-protection-acehack-main.json @@ -0,0 +1,56 @@ +[ + { + "type": "deletion", + "ruleset_source_type": "Repository", + "ruleset_source": "AceHack/Zeta", + "ruleset_id": 15524390 + }, + { + "type": "non_fast_forward", + "ruleset_source_type": "Repository", + "ruleset_source": "AceHack/Zeta", + "ruleset_id": 15524390 + }, + { + "type": "copilot_code_review", + "parameters": { + "review_on_push": true, + "review_draft_pull_requests": true + }, + "ruleset_source_type": "Repository", + "ruleset_source": "AceHack/Zeta", + "ruleset_id": 15524390 + }, + { + "type": "code_quality", + "parameters": { + "severity": "all" + }, + "ruleset_source_type": "Repository", + "ruleset_source": "AceHack/Zeta", + "ruleset_id": 15524390 + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "allowed_merge_methods": [ + "squash" + ] + }, + "ruleset_source_type": "Repository", + "ruleset_source": "AceHack/Zeta", + "ruleset_id": 15524390 + }, + { + "type": "required_linear_history", + "ruleset_source_type": "Repository", + "ruleset_source": "AceHack/Zeta", + "ruleset_id": 15524390 + } +] diff --git a/docs/operations/branch-protection-lfg-main.json b/docs/operations/branch-protection-lfg-main.json new file mode 100644 index 00000000..963eda12 --- /dev/null +++ b/docs/operations/branch-protection-lfg-main.json @@ -0,0 +1,56 @@ +[ + { + "type": "deletion", + "ruleset_source_type": "Repository", + "ruleset_source": "Lucent-Financial-Group/Zeta", + "ruleset_id": 15256879 + }, + { + "type": "non_fast_forward", + "ruleset_source_type": "Repository", + "ruleset_source": "Lucent-Financial-Group/Zeta", + "ruleset_id": 15256879 + }, + { + "type": "copilot_code_review", + "parameters": { + "review_on_push": true, + "review_draft_pull_requests": true + }, + "ruleset_source_type": "Repository", + "ruleset_source": "Lucent-Financial-Group/Zeta", + "ruleset_id": 15256879 + }, + { + "type": "code_quality", + "parameters": { + "severity": "all" + }, + "ruleset_source_type": "Repository", + "ruleset_source": "Lucent-Financial-Group/Zeta", + "ruleset_id": 15256879 + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "allowed_merge_methods": [ + "squash" + ] + }, + "ruleset_source_type": "Repository", + "ruleset_source": "Lucent-Financial-Group/Zeta", + "ruleset_id": 15256879 + }, + { + "type": "required_linear_history", + "ruleset_source_type": "Repository", + "ruleset_source": "Lucent-Financial-Group/Zeta", + "ruleset_id": 15256879 + } +] diff --git a/docs/operations/branch-protection.md b/docs/operations/branch-protection.md new file mode 100644 index 00000000..4e545b07 --- /dev/null +++ b/docs/operations/branch-protection.md @@ -0,0 +1,102 @@ +# Branch protection — actual gates per repo settings + +**Purpose:** make the actual GitHub branch-protection config visible IN-REPO so agents reading the substrate see the real gates, not training-data defaults. Closes the live-lock hallucination class where "BLOCKED" was misdiagnosed as "review-approval gated" when the actual gates are CI checks + thread resolution. + +**Snapshot source:** `gh api repos///rules/branches/main` (run periodically; see [Refresh cadence](#refresh-cadence)). + +**Last snapshot:** 2026-04-26 — see `branch-protection-lfg-main.json` and `branch-protection-acehack-main.json` for the raw GitHub API output. + +--- + +## LFG (Lucent-Financial-Group/Zeta) main branch — actual gates + +```text +- deletion: forbidden +- non_fast_forward: forbidden +- copilot_code_review: review_on_push: true +- code_quality: severity all +- pull_request: + required_approving_review_count: 0 ← NO HUMAN REVIEW REQUIRED + required_review_thread_resolution: true ← all threads must resolve + allowed_merge_methods: [squash] +- required_linear_history: enforced +``` + +**To merge a PR on LFG main, the following gates must clear:** + +1. **All CI checks PASS** (`code_quality: all`) +2. **All review threads RESOLVED** (`required_review_thread_resolution: true`) +3. **Copilot has REVIEWED the latest push** (`copilot_code_review.review_on_push: true`) +4. **Linear history** (no merge commits — squash only) + +**Human review approval is EXPLICITLY NOT required** (`required_approving_review_count: 0`). + +## AceHack (AceHack/Zeta) main branch — actual gates + +See `branch-protection-acehack-main.json`. Per Aaron 2026-04-26 (HB-005 settings-sync), AceHack is intended to be symmetric with LFG where platform allows; merge queue is the known asymmetric exception (org-only feature). + +--- + +## Why this file exists + +**The live-lock hallucination class:** in many GitHub repos, branch protection requires ≥1 human review approval. Most training data biases agents toward this assumption. When an agent sees `mergeStateStatus: BLOCKED` on a PR, the statistical prior says "blocked on review-approval." + +**Zeta config is non-standard:** `required_approving_review_count: 0`. Review approval is NOT a gate. The actual blockers are CI checks + thread resolution. + +**Without this file**, agents working on Zeta repeatedly misdiagnose BLOCKED PRs as review-approval-gated, then sit waiting for a review that's never required, while failing CI checks or unresolved threads accumulate as the actual blocker. + +**With this file** (and the `AGENTS.md` required-reading entry that points here), agents reading the substrate during normal work will encounter the actual gates and override the training-data prior. + +Per Otto-341 (mechanism over vigilance): this file is the mechanism. Memory-only reminders haven't held across sessions; substrate-in-repo gives agents a primary source they encounter naturally during repo navigation. + +Per Otto-329 Phase 4 (full backups including settings): this is a step toward full host-layer backup. Future expansion: snapshot rulesets, repo-level toggles, secret names (not values), workflow permissions, etc. + +## Diagnostic flow for "is this PR actually gated on review-approval?" + +```bash +# Step 1: actual blocker classification +gh pr view --repo --json statusCheckRollup,reviewDecision --jq '{ + review: .reviewDecision, + fails: ([.statusCheckRollup[] | select(.conclusion=="FAILURE")] | length), + running: ([.statusCheckRollup[] | select(.status=="IN_PROGRESS" or .status=="QUEUED")] | length), + success: ([.statusCheckRollup[] | select(.conclusion=="SUCCESS")] | length) +}' + +# Step 2: thread resolution count +gh api graphql -f query='query { repository(owner: "OWNER", name: "REPO") { pullRequest(number: N) { reviewThreads(first: 50) { nodes { isResolved } } } } }' | \ + jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length' +``` + +Output classes: + +- `fails: 0, threads: 0, BLOCKED` → likely Copilot review on latest push not yet posted; wait +- `fails: >0` → fix the failing checks (often transient `curl 502` infra flakes; check log via `gh run view --log-failed | grep "exit code|502|fatal"`) +- `running: >0` → CI in flight; wait for green +- `threads: >0` → drain the threads +- **NEVER** "review-approval gated" without `reviewDecision == "REVIEW_REQUIRED"` AND `required_approving_review_count > 0` in this file + +## Refresh cadence + +Run when: + +- Aaron changes branch-protection settings via GitHub UI +- A new ruleset is added to either repo +- Quarterly drift check (audit substrate vs live state) + +Refresh command: + +```bash +gh api repos/Lucent-Financial-Group/Zeta/rules/branches/main | python3 -m json.tool > docs/operations/branch-protection-lfg-main.json +gh api repos/AceHack/Zeta/rules/branches/main | python3 -m json.tool > docs/operations/branch-protection-acehack-main.json +# Update the "Last snapshot" date above + edit the LFG / AceHack tables if rules changed +``` + +Future automation: a `tools/hygiene/check-branch-protection-snapshot-stale.sh` that warns if the JSON files are >30 days old. Owed work. + +## Composes with + +- **Otto-329 Phase 4** (full backups including host-layer settings) — this file is one step toward full Phase 4 +- **Otto-341** (mechanism over vigilance) — substrate-as-mechanism, not memory-as-reminder +- **Otto-247** (training-data defaults drift) — the failure mode this file prevents +- **`memory/feedback_blocked_status_is_not_review_gating_check_status_checks_failure_first_otto_live_lock_2026_04_26.md`** — the live-lock memory this file structurally supports +- **AGENTS.md** required-reading section (separate update owed) — should reference this file so agent cold-start encounters it From 0d8c93b3ae570436f21357235a2d88f5dae1ed98 Mon Sep 17 00:00:00 2001 From: Aaron Stainback Date: Sun, 26 Apr 2026 08:00:34 -0400 Subject: [PATCH 2/5] docs(operations #589 threads): address 6 Codex+Copilot findings on Phase 4 substrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Codex P1 + Copilot reviews on PR #589: 1. Codex P1 (line 90): refresh command missed classic branch protection. Now snapshots BOTH endpoints (rulesets + classic). Classic JSON files added: branch-protection-{lfg,acehack}-main-classic.json. 2. Codex P1 (line 72): "BLOCKED → Copilot review" rule has strict-mode caveat. Added explicit `strict: false` note + future-state safeguard. 3. Copilot (line 67) pagination: GraphQL `first: 50` not paginated. Added pagination caveat + observation that current Zeta PR shape stays under the limit (max observed: 50 on #132). 4. Copilot (line 36): "Aaron" name-attribution → "the human maintainer" (current-state docs surface; per AGENT-BEST-PRACTICES no-name rule). 5. Copilot (line 30): doc was incomplete on classic protection. Now covers BOTH ruleset + classic gates as separate sections + lists actual required_status_checks contexts. 6. Copilot (line 67): output format mismatch between Step 1 (object) and Step 2 (bare integer). Added explicit "intentional" note + variable-naming convention to make composition unambiguous. Also added: cross-reference to docs/GITHUB-SETTINGS.md (which already flagged classic protection as separate axis). Co-Authored-By: Claude Opus 4.7 --- ...ranch-protection-acehack-main-classic.json | 73 +++++++++++++++ .../branch-protection-lfg-main-classic.json | 83 +++++++++++++++++ docs/operations/branch-protection.md | 91 ++++++++++++++----- 3 files changed, 222 insertions(+), 25 deletions(-) create mode 100644 docs/operations/branch-protection-acehack-main-classic.json create mode 100644 docs/operations/branch-protection-lfg-main-classic.json diff --git a/docs/operations/branch-protection-acehack-main-classic.json b/docs/operations/branch-protection-acehack-main-classic.json new file mode 100644 index 00000000..39e6f9fc --- /dev/null +++ b/docs/operations/branch-protection-acehack-main-classic.json @@ -0,0 +1,73 @@ +{ + "url": "https://api.github.com/repos/AceHack/Zeta/branches/main/protection", + "required_status_checks": { + "url": "https://api.github.com/repos/AceHack/Zeta/branches/main/protection/required_status_checks", + "strict": false, + "contexts": [ + "build-and-test (ubuntu-22.04)", + "lint (semgrep)", + "lint (shellcheck)", + "lint (actionlint)", + "lint (markdownlint)" + ], + "contexts_url": "https://api.github.com/repos/AceHack/Zeta/branches/main/protection/required_status_checks/contexts", + "checks": [ + { + "context": "build-and-test (ubuntu-22.04)", + "app_id": 15368 + }, + { + "context": "lint (semgrep)", + "app_id": 15368 + }, + { + "context": "lint (shellcheck)", + "app_id": 15368 + }, + { + "context": "lint (actionlint)", + "app_id": 15368 + }, + { + "context": "lint (markdownlint)", + "app_id": 15368 + } + ] + }, + "required_pull_request_reviews": { + "url": "https://api.github.com/repos/AceHack/Zeta/branches/main/protection/required_pull_request_reviews", + "dismiss_stale_reviews": true, + "require_code_owner_reviews": false, + "require_last_push_approval": false, + "required_approving_review_count": 0 + }, + "required_signatures": { + "url": "https://api.github.com/repos/AceHack/Zeta/branches/main/protection/required_signatures", + "enabled": false + }, + "enforce_admins": { + "url": "https://api.github.com/repos/AceHack/Zeta/branches/main/protection/enforce_admins", + "enabled": false + }, + "required_linear_history": { + "enabled": true + }, + "allow_force_pushes": { + "enabled": false + }, + "allow_deletions": { + "enabled": false + }, + "block_creations": { + "enabled": false + }, + "required_conversation_resolution": { + "enabled": true + }, + "lock_branch": { + "enabled": false + }, + "allow_fork_syncing": { + "enabled": false + } +} diff --git a/docs/operations/branch-protection-lfg-main-classic.json b/docs/operations/branch-protection-lfg-main-classic.json new file mode 100644 index 00000000..9f02ba65 --- /dev/null +++ b/docs/operations/branch-protection-lfg-main-classic.json @@ -0,0 +1,83 @@ +{ + "url": "https://api.github.com/repos/Lucent-Financial-Group/Zeta/branches/main/protection", + "required_status_checks": { + "url": "https://api.github.com/repos/Lucent-Financial-Group/Zeta/branches/main/protection/required_status_checks", + "strict": false, + "contexts": [ + "lint (semgrep)", + "lint (shellcheck)", + "lint (actionlint)", + "lint (markdownlint)", + "build-and-test (macos-26)", + "build-and-test (ubuntu-24.04)", + "build-and-test (ubuntu-24.04-arm)" + ], + "contexts_url": "https://api.github.com/repos/Lucent-Financial-Group/Zeta/branches/main/protection/required_status_checks/contexts", + "checks": [ + { + "context": "lint (semgrep)", + "app_id": 15368 + }, + { + "context": "lint (shellcheck)", + "app_id": 15368 + }, + { + "context": "lint (actionlint)", + "app_id": 15368 + }, + { + "context": "lint (markdownlint)", + "app_id": 15368 + }, + { + "context": "build-and-test (macos-26)", + "app_id": 15368 + }, + { + "context": "build-and-test (ubuntu-24.04)", + "app_id": 15368 + }, + { + "context": "build-and-test (ubuntu-24.04-arm)", + "app_id": 15368 + } + ] + }, + "required_pull_request_reviews": { + "url": "https://api.github.com/repos/Lucent-Financial-Group/Zeta/branches/main/protection/required_pull_request_reviews", + "dismiss_stale_reviews": true, + "require_code_owner_reviews": false, + "require_last_push_approval": false, + "required_approving_review_count": 0 + }, + "required_signatures": { + "url": "https://api.github.com/repos/Lucent-Financial-Group/Zeta/branches/main/protection/required_signatures", + "enabled": false + }, + "enforce_admins": { + "url": "https://api.github.com/repos/Lucent-Financial-Group/Zeta/branches/main/protection/enforce_admins", + "enabled": false + }, + "required_linear_history": { + "enabled": true + }, + "allow_force_pushes": { + "enabled": false + }, + "allow_deletions": { + "enabled": false + }, + "block_creations": { + "enabled": false + }, + "required_conversation_resolution": { + "enabled": true + }, + "lock_branch": { + "enabled": false + }, + "allow_fork_syncing": { + "enabled": false + } +} diff --git a/docs/operations/branch-protection.md b/docs/operations/branch-protection.md index 4e545b07..8a30433b 100644 --- a/docs/operations/branch-protection.md +++ b/docs/operations/branch-protection.md @@ -1,15 +1,22 @@ # Branch protection — actual gates per repo settings -**Purpose:** make the actual GitHub branch-protection config visible IN-REPO so agents reading the substrate see the real gates, not training-data defaults. Closes the live-lock hallucination class where "BLOCKED" was misdiagnosed as "review-approval gated" when the actual gates are CI checks + thread resolution. +**Purpose:** make the actual GitHub branch-protection config visible IN-REPO so agents reading the substrate see the real gates, not training-data defaults. Closes the live-lock hallucination class where "BLOCKED" was misdiagnosed as "review-approval gated" when the actual gates are CI checks + thread resolution + classic-protection required-status-checks. -**Snapshot source:** `gh api repos///rules/branches/main` (run periodically; see [Refresh cadence](#refresh-cadence)). +**Snapshot sources (TWO endpoints — both gate merges):** -**Last snapshot:** 2026-04-26 — see `branch-protection-lfg-main.json` and `branch-protection-acehack-main.json` for the raw GitHub API output. +- `gh api repos///rules/branches/main` — **rulesets** (modern API; copilot review on push, code quality, etc.) +- `gh api repos///branches/main/protection` — **classic branch protection** (required status checks list, strict mode, dismiss-stale-reviews, etc.) + +Both must be snapshotted; missing either gives an incomplete picture and re-creates the hallucination class at a different layer. + +**Last snapshot:** 2026-04-26 — see `branch-protection-lfg-main.json` (rulesets) + `branch-protection-lfg-main-classic.json` (classic) + the AceHack equivalents for raw GitHub API output. --- ## LFG (Lucent-Financial-Group/Zeta) main branch — actual gates +### From rulesets endpoint (`branch-protection-lfg-main.json`) + ```text - deletion: forbidden - non_fast_forward: forbidden @@ -22,18 +29,37 @@ - required_linear_history: enforced ``` -**To merge a PR on LFG main, the following gates must clear:** +### From classic branch protection (`branch-protection-lfg-main-classic.json`) + +```text +- required_status_checks: + strict: false ← NOT requiring up-to-date branch + contexts: [ + lint (semgrep), + lint (shellcheck), + lint (actionlint), + lint (markdownlint), + build-and-test (macos-26), + build-and-test (ubuntu-24.04), + build-and-test (ubuntu-24.04-arm), + ] +``` -1. **All CI checks PASS** (`code_quality: all`) -2. **All review threads RESOLVED** (`required_review_thread_resolution: true`) -3. **Copilot has REVIEWED the latest push** (`copilot_code_review.review_on_push: true`) -4. **Linear history** (no merge commits — squash only) +### To merge a PR on LFG main, ALL of these must clear: -**Human review approval is EXPLICITLY NOT required** (`required_approving_review_count: 0`). +1. **All required_status_checks PASS** (specific list above; ubuntu-slim is NOT required, macos-26 IS required) — from classic protection +2. **`code_quality: all`** rule from rulesets (broader CI gate) +3. **All review threads RESOLVED** (`required_review_thread_resolution: true`) +4. **Copilot has REVIEWED the latest push** (`copilot_code_review.review_on_push: true`) +5. **Linear history** (squash only) + +**Human review approval is EXPLICITLY NOT required** (`required_approving_review_count: 0` per rulesets; classic protection has no `required_pull_request_reviews` block). This is non-standard for typical GitHub repos. + +**Strict mode is OFF** (`strict: false`) — out-of-date branches are NOT auto-blocked from merging. So "BLOCKED with green checks" is NOT a strict-mode-stale-branch issue. ## AceHack (AceHack/Zeta) main branch — actual gates -See `branch-protection-acehack-main.json`. Per Aaron 2026-04-26 (HB-005 settings-sync), AceHack is intended to be symmetric with LFG where platform allows; merge queue is the known asymmetric exception (org-only feature). +See `branch-protection-acehack-main.json` (rulesets) + `branch-protection-acehack-main-classic.json` (classic). Per the human maintainer 2026-04-26 (HB-005 settings-sync), AceHack is intended to be symmetric with LFG where platform allows; merge queue is the known asymmetric exception (org-only feature). --- @@ -41,20 +67,22 @@ See `branch-protection-acehack-main.json`. Per Aaron 2026-04-26 (HB-005 settings **The live-lock hallucination class:** in many GitHub repos, branch protection requires ≥1 human review approval. Most training data biases agents toward this assumption. When an agent sees `mergeStateStatus: BLOCKED` on a PR, the statistical prior says "blocked on review-approval." -**Zeta config is non-standard:** `required_approving_review_count: 0`. Review approval is NOT a gate. The actual blockers are CI checks + thread resolution. +**Zeta config is non-standard:** `required_approving_review_count: 0`. Review approval is NOT a gate. The actual blockers are CI checks (BOTH classic-required-status-checks AND ruleset code_quality) + thread resolution + Copilot review on push. -**Without this file**, agents working on Zeta repeatedly misdiagnose BLOCKED PRs as review-approval-gated, then sit waiting for a review that's never required, while failing CI checks or unresolved threads accumulate as the actual blocker. +**Without this file**, agents working on Zeta repeatedly misdiagnose BLOCKED PRs as review-approval-gated, then sit waiting for a review that's never required, while the actual blockers (failing CI / unresolved threads / pending Copilot review) accumulate. **With this file** (and the `AGENTS.md` required-reading entry that points here), agents reading the substrate during normal work will encounter the actual gates and override the training-data prior. Per Otto-341 (mechanism over vigilance): this file is the mechanism. Memory-only reminders haven't held across sessions; substrate-in-repo gives agents a primary source they encounter naturally during repo navigation. -Per Otto-329 Phase 4 (full backups including settings): this is a step toward full host-layer backup. Future expansion: snapshot rulesets, repo-level toggles, secret names (not values), workflow permissions, etc. +Per Otto-329 Phase 4 (full backups including settings): this is a step toward full host-layer backup. Future expansion: snapshot all rulesets (not just main), repo-level toggles, secret names (not values), workflow permissions, etc. + +## Diagnostic flow for "what is actually blocking this PR?" -## Diagnostic flow for "is this PR actually gated on review-approval?" +The two queries below produce **different output formats** intentionally — Step 1 returns a structured object; Step 2 returns a bare integer. The "Output classes" interpretation table below combines them as named variables (`fails`, `threads`). ```bash -# Step 1: actual blocker classification +# Step 1 — output: object {review, fails, running, success} gh pr view --repo --json statusCheckRollup,reviewDecision --jq '{ review: .reviewDecision, fails: ([.statusCheckRollup[] | select(.conclusion=="FAILURE")] | length), @@ -62,36 +90,48 @@ gh pr view --repo --json statusCheckRollup,reviewDecision --jq success: ([.statusCheckRollup[] | select(.conclusion=="SUCCESS")] | length) }' -# Step 2: thread resolution count +# Step 2 — output: bare integer (unresolved thread count) +# NOTE: `first: 50` is NOT paginated. PRs with >50 threads need pagination +# (GraphQL `pageInfo { hasNextPage endCursor }`) for accurate counts. +# For Zeta's typical PR shape this is sufficient (largest single-PR thread +# count observed: 50 on #132). If a future PR exceeds 50, paginate. gh api graphql -f query='query { repository(owner: "OWNER", name: "REPO") { pullRequest(number: N) { reviewThreads(first: 50) { nodes { isResolved } } } } }' | \ jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length' ``` -Output classes: +Output classes (treat Step 1's structured output's `fails` / `running` / `success` keys + Step 2's integer as named variables `threads`): -- `fails: 0, threads: 0, BLOCKED` → likely Copilot review on latest push not yet posted; wait -- `fails: >0` → fix the failing checks (often transient `curl 502` infra flakes; check log via `gh run view --log-failed | grep "exit code|502|fatal"`) -- `running: >0` → CI in flight; wait for green -- `threads: >0` → drain the threads -- **NEVER** "review-approval gated" without `reviewDecision == "REVIEW_REQUIRED"` AND `required_approving_review_count > 0` in this file +- `fails == 0, threads == 0, running == 0, BLOCKED` → likely Copilot review on latest push not yet posted; wait. **Caveat:** if `strict: true` were set in classic protection AND `required_status_checks` had pending checks waiting for the branch to update against base, BLOCKED could persist with green checks; current Zeta config has `strict: false` so this caveat does NOT apply, but stays in this doc as a future-state safeguard. +- `fails > 0` → fix the failing checks. ALWAYS verify the actual failure line via `gh run view --log-failed | grep -iE "exit code|502|fatal|404"` because the check NAME may differ from the ACTUAL failing step (often transient `curl 502` infra flakes during `tools/setup/install.sh`, not content issues). +- `running > 0` → CI in flight; wait for green +- `threads > 0` → drain the threads +- **NEVER** claim "review-approval gated" without verifying `reviewDecision == "REVIEW_REQUIRED"` AND `required_approving_review_count > 0` in this file's snapshot. Both must be true; with current config (`required_approving_review_count: 0`), review-approval is structurally NOT a gate. ## Refresh cadence Run when: -- Aaron changes branch-protection settings via GitHub UI +- The human maintainer changes branch-protection settings via GitHub UI - A new ruleset is added to either repo +- Classic branch protection is updated (required status check list change, strict mode flip, etc.) - Quarterly drift check (audit substrate vs live state) -Refresh command: +Refresh command (snapshots BOTH endpoints — the prior version of this command missed classic protection): ```bash +# Rulesets endpoint gh api repos/Lucent-Financial-Group/Zeta/rules/branches/main | python3 -m json.tool > docs/operations/branch-protection-lfg-main.json gh api repos/AceHack/Zeta/rules/branches/main | python3 -m json.tool > docs/operations/branch-protection-acehack-main.json + +# Classic protection endpoint (CRITICAL — without this, the snapshot misses +# required_status_checks list and strict-mode setting) +gh api repos/Lucent-Financial-Group/Zeta/branches/main/protection | python3 -m json.tool > docs/operations/branch-protection-lfg-main-classic.json +gh api repos/AceHack/Zeta/branches/main/protection | python3 -m json.tool > docs/operations/branch-protection-acehack-main-classic.json + # Update the "Last snapshot" date above + edit the LFG / AceHack tables if rules changed ``` -Future automation: a `tools/hygiene/check-branch-protection-snapshot-stale.sh` that warns if the JSON files are >30 days old. Owed work. +Future automation: `tools/hygiene/check-branch-protection-snapshot-stale.sh` warns if any of the 4 JSON files are >30 days old. Owed work. ## Composes with @@ -99,4 +139,5 @@ Future automation: a `tools/hygiene/check-branch-protection-snapshot-stale.sh` t - **Otto-341** (mechanism over vigilance) — substrate-as-mechanism, not memory-as-reminder - **Otto-247** (training-data defaults drift) — the failure mode this file prevents - **`memory/feedback_blocked_status_is_not_review_gating_check_status_checks_failure_first_otto_live_lock_2026_04_26.md`** — the live-lock memory this file structurally supports +- **`docs/GITHUB-SETTINGS.md`** — the existing settings-discipline doc that flagged classic branch protection as a separate axis; this file completes the snapshot - **AGENTS.md** required-reading section (separate update owed) — should reference this file so agent cold-start encounters it From ea9256d59d60c92465882d8ed5b6e96dd5672a21 Mon Sep 17 00:00:00 2001 From: Aaron Stainback Date: Sun, 26 Apr 2026 08:22:02 -0400 Subject: [PATCH 3/5] docs(operations #589 threads): fix line-56 + line-58 Codex findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two unresolved Codex review threads on PR #589 addressed: **Line 56 (P2) — incorrect classic-block claim, FIXED:** Old claim: "classic protection has no required_pull_request_reviews block." Codex correctly identified this contradicts the snapshot in the same commit: `branch-protection-lfg-main-classic.json` lines 47-53 DOES include the block. Rewritten to acknowledge the block exists *and* that its `required_approving_review_count: 0` lands at the same "no review approval required" outcome as the rulesets endpoint. Both API surfaces agree on the gate; only the supporting wording was wrong. **Line 58 (P1) — strict-mode divergence between live and canonical baseline, RECONCILED:** Codex correctly identified that the doc reports `strict: false` (live snapshot in `branch-protection-lfg-main-classic.json` line 5) while `tools/hygiene/github-settings.expected.json` line 142 declares `default_branch_protection.required_status_checks.strict: true`. Without reconciliation, agents diagnosing BLOCKED PRs could take opposite actions depending on which file they read. Doc now explicitly names the divergence as **settings drift** and gives triage guidance for both questions: - "Is my PR currently blocked on stale-base?" -> read the live snapshot (today: `strict: false` -> not blocked). - "Should the repo be blocking on stale-base?" -> read the canonical baseline (today: `strict: true` -> yes, by declared policy). The divergence is flagged as open work for the future `tools/hygiene/check-branch-protection-snapshot-stale.sh` follow-up (Otto-329 Phase 4 owed item). Both findings show why substrate-as-mechanism (Phase 4's central thesis) needs the substrate to be internally consistent — Codex caught this on the first review pass exactly because the JSON artefacts and the prose claims live in the same commit and can be mechanically cross-checked. --- docs/operations/branch-protection.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/operations/branch-protection.md b/docs/operations/branch-protection.md index 8a30433b..ffc520ca 100644 --- a/docs/operations/branch-protection.md +++ b/docs/operations/branch-protection.md @@ -53,9 +53,14 @@ Both must be snapshotted; missing either gives an incomplete picture and re-crea 4. **Copilot has REVIEWED the latest push** (`copilot_code_review.review_on_push: true`) 5. **Linear history** (squash only) -**Human review approval is EXPLICITLY NOT required** (`required_approving_review_count: 0` per rulesets; classic protection has no `required_pull_request_reviews` block). This is non-standard for typical GitHub repos. +**Human review approval is EXPLICITLY NOT required.** Both API surfaces agree: rulesets have `required_approving_review_count: 0`, and classic protection has a `required_pull_request_reviews` block (in `branch-protection-lfg-main-classic.json` lines 47-53) with the *same* `required_approving_review_count: 0`. The block exists; the count it requires is zero. Either way, no human review approval is gated. This is non-standard for typical GitHub repos. -**Strict mode is OFF** (`strict: false`) — out-of-date branches are NOT auto-blocked from merging. So "BLOCKED with green checks" is NOT a strict-mode-stale-branch issue. +**Strict mode — live vs canonical divergence.** The live snapshot in `branch-protection-lfg-main-classic.json` line 5 reports `"strict": false`, so right now out-of-date branches are NOT auto-blocked from merging — "BLOCKED with green checks" is NOT a strict-mode-stale-branch issue at this point in time. However, the canonical baseline at `tools/hygiene/github-settings.expected.json` line 142 declares `default_branch_protection.required_status_checks.strict: true`. This is **settings drift** — live state diverges from the declared baseline. Two implications for triage: + +- **For "is my PR currently blocked on stale-base?"** — read the live snapshot (`strict: false` -> answer: no, not blocked on staleness today). +- **For "should the repo be blocking on stale-base?"** — read the canonical baseline (`strict: true` -> answer: yes, by declared policy). The drift is open work; file as a settings-sync task or fix directly when authorised. + +Future re-snapshots of these files (Otto-329 Phase 4 follow-up: `tools/hygiene/check-branch-protection-snapshot-stale.sh`) should reconcile this divergence either by aligning live state to the baseline or by updating the baseline if the policy intent has changed. ## AceHack (AceHack/Zeta) main branch — actual gates From dac3a14d352e8e976885885bc111683c73738af4 Mon Sep 17 00:00:00 2001 From: Aaron Stainback Date: Sun, 26 Apr 2026 08:36:56 -0400 Subject: [PATCH 4/5] docs(operations #589): fix MD026 trailing-punctuation on line-48 heading --- docs/operations/branch-protection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/branch-protection.md b/docs/operations/branch-protection.md index ffc520ca..c5f2be2d 100644 --- a/docs/operations/branch-protection.md +++ b/docs/operations/branch-protection.md @@ -45,7 +45,7 @@ Both must be snapshotted; missing either gives an incomplete picture and re-crea ] ``` -### To merge a PR on LFG main, ALL of these must clear: +### To merge a PR on LFG main, ALL of these must clear 1. **All required_status_checks PASS** (specific list above; ubuntu-slim is NOT required, macos-26 IS required) — from classic protection 2. **`code_quality: all`** rule from rulesets (broader CI gate) From eee690b0ae08d8411d9222da92e74ab2bac1d8f9 Mon Sep 17 00:00:00 2001 From: Aaron Stainback Date: Sun, 26 Apr 2026 09:00:25 -0400 Subject: [PATCH 5/5] docs(operations #589 threads): 4 Codex+Copilot findings addressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NM59qEM0 (P1, line 93): Step-1 jq expression now treats CANCELLED, TIMED_OUT, ACTION_REQUIRED, and STARTUP_FAILURE as blocking outcomes in addition to FAILURE. Required-check states that finish with these non-success terminal outcomes also block merge per GitHub branch protection semantics; treating only FAILURE as failed undercounted real blockers. NM59qA22 (P1, line 146): rephrased the broken in-repo cross-reference to memory/feedback_blocked_status_*.md to clearly indicate the file is per-user (~/.claude/projects//memory/), NOT mirrored in repo. Reviewer's diagnosis was correct: the link as written implied an in-repo file that doesn't exist. NM59qA3S (P1, line 139): tools/hygiene/check-branch-protection-snapshot-stale.sh explicitly marked as "planned, NOT YET in repo". Otto-329 Phase 4 follow-up tasks reference. Same caveat added to line 63 where the same script was referenced. NM59qA3X (P2): PR description update — addressed via separate `gh pr edit` step (4 JSON files: rulesets + classic for both LFG + AceHack, not 2 as originally described). --- docs/operations/branch-protection.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/operations/branch-protection.md b/docs/operations/branch-protection.md index c5f2be2d..305bcbf3 100644 --- a/docs/operations/branch-protection.md +++ b/docs/operations/branch-protection.md @@ -60,7 +60,7 @@ Both must be snapshotted; missing either gives an incomplete picture and re-crea - **For "is my PR currently blocked on stale-base?"** — read the live snapshot (`strict: false` -> answer: no, not blocked on staleness today). - **For "should the repo be blocking on stale-base?"** — read the canonical baseline (`strict: true` -> answer: yes, by declared policy). The drift is open work; file as a settings-sync task or fix directly when authorised. -Future re-snapshots of these files (Otto-329 Phase 4 follow-up: `tools/hygiene/check-branch-protection-snapshot-stale.sh`) should reconcile this divergence either by aligning live state to the baseline or by updating the baseline if the policy intent has changed. +Future re-snapshots of these files (Otto-329 Phase 4 follow-up: a planned `tools/hygiene/check-branch-protection-snapshot-stale.sh` lint, NOT YET in repo) should reconcile this divergence either by aligning live state to the baseline or by updating the baseline if the policy intent has changed. ## AceHack (AceHack/Zeta) main branch — actual gates @@ -90,7 +90,7 @@ The two queries below produce **different output formats** intentionally — Ste # Step 1 — output: object {review, fails, running, success} gh pr view --repo --json statusCheckRollup,reviewDecision --jq '{ review: .reviewDecision, - fails: ([.statusCheckRollup[] | select(.conclusion=="FAILURE")] | length), + fails: ([.statusCheckRollup[] | select(.conclusion=="FAILURE" or .conclusion=="CANCELLED" or .conclusion=="TIMED_OUT" or .conclusion=="ACTION_REQUIRED" or .conclusion=="STARTUP_FAILURE")] | length), running: ([.statusCheckRollup[] | select(.status=="IN_PROGRESS" or .status=="QUEUED")] | length), success: ([.statusCheckRollup[] | select(.conclusion=="SUCCESS")] | length) }' @@ -136,13 +136,13 @@ gh api repos/AceHack/Zeta/branches/main/protection | python3 -m json.tool > docs # Update the "Last snapshot" date above + edit the LFG / AceHack tables if rules changed ``` -Future automation: `tools/hygiene/check-branch-protection-snapshot-stale.sh` warns if any of the 4 JSON files are >30 days old. Owed work. +**Future automation (planned, NOT YET in repo):** a `tools/hygiene/check-branch-protection-snapshot-stale.sh` lint that warns if any of the 4 JSON files are >30 days old. Owed work; see Otto-329 Phase 4 follow-up tasks. ## Composes with - **Otto-329 Phase 4** (full backups including host-layer settings) — this file is one step toward full Phase 4 - **Otto-341** (mechanism over vigilance) — substrate-as-mechanism, not memory-as-reminder - **Otto-247** (training-data defaults drift) — the failure mode this file prevents -- **`memory/feedback_blocked_status_is_not_review_gating_check_status_checks_failure_first_otto_live_lock_2026_04_26.md`** — the live-lock memory this file structurally supports +- **Per-user memory** `feedback_blocked_status_is_not_review_gating_check_status_checks_failure_first_otto_live_lock_2026_04_26.md` (lives at `~/.claude/projects//memory/`, NOT mirrored in repo `memory/` — per-Claude-Code-user-instance scope) — the live-lock memory this file structurally supports - **`docs/GITHUB-SETTINGS.md`** — the existing settings-discipline doc that flagged classic branch protection as a separate axis; this file completes the snapshot - **AGENTS.md** required-reading section (separate update owed) — should reference this file so agent cold-start encounters it