Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,16 @@ jobs:
./actionlint --version

- name: Run actionlint
run: ./actionlint -color
# -ignore 'unknown permission scope "administration"' is a
# documented workaround for a known actionlint gap — the
# `administration` permission IS valid in GitHub Actions
# (used by .github/workflows/github-settings-drift.yml to
# read rulesets + branch protection + security_and_analysis)
# but actionlint v1.7.x has not yet added it to its allow-
# list. Remove when actionlint catches up. See:
# https://github.com/rhysd/actionlint/issues (search
# "administration permission").
run: ./actionlint -color -ignore 'unknown permission scope "administration"'

lint-no-empty-dirs:
# Fail if a committed directory has no files — almost always a
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/github-settings-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: github-settings-drift

# Weekly drift detector for GitHub repo settings that live outside the
# declarative-in-tree surface (rulesets, branch protection, Actions
# variables, environments, Pages, CodeQL default-setup state, etc.).
# Compares live `gh api` output against the checked-in expected snapshot
# at tools/hygiene/github-settings.expected.json. Drift blocks this
# workflow; resolve by either reverting the setting in GitHub or
# re-snapshotting and committing the new expected.
#
# Security note (safe-pattern compliance): this workflow only consumes
# first-party trusted context — `secrets.GITHUB_TOKEN` and
# `github.repository` (the owner/repo string). No user-authored
# fields (issue title, PR body, commit message, head_ref, etc.) are
# referenced. Both trusted values are passed via env: into the run
# block and quoted there, matching the recommended safe pattern from
# https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/
#
# See docs/GITHUB-SETTINGS.md + docs/FACTORY-HYGIENE.md row #40.

on:
schedule:
# Weekly Mondays 14:17 UTC — off the hour to avoid the GHA
# cron thundering-herd.
- cron: "17 14 * * 1"
workflow_dispatch: {}
# Also run on any change to the expected snapshot or the detector
# itself, so a PR that updates expected gets an immediate green
# signal that the snapshot matches reality at merge time.
pull_request:
paths:
- "tools/hygiene/github-settings.expected.json"
- "tools/hygiene/snapshot-github-settings.sh"
- "tools/hygiene/check-github-settings-drift.sh"
- ".github/workflows/github-settings-drift.yml"
Comment on lines +27 to +35
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This workflow runs on pull_request, but the drift check needs administration: read endpoints (rulesets, branch protection, hooks, deploy keys). On fork-based PRs (which the repo explicitly allows), GITHUB_TOKEN won't have those permissions and the job will fail noisily. Consider skipping the job for fork PRs (e.g., if: github.event.pull_request.head.repo.full_name == github.repository) and keeping the check for same-repo PRs + scheduled runs.

Copilot uses AI. Check for mistakes.

permissions:
# Rulesets, branch protection, security_and_analysis,
# secrets counts, deploy keys, webhooks require
# `administration: read`. `actions: read` covers workflow
# reads + Actions variables + environments. `contents: read`
# is the baseline for `actions/checkout`.
contents: read
actions: read
administration: read

concurrency:
group: github-settings-drift
cancel-in-progress: false

jobs:
check:
name: check drift
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: check drift
env:
# Both values below are first-party trusted context.
# No user-authored input is used anywhere in this workflow.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
tools/hygiene/check-github-settings-drift.sh --repo "$GH_REPO"
1 change: 1 addition & 0 deletions docs/FACTORY-HYGIENE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ is never destructive; retiring one requires an ADR in
| 37 | WIP-limit discipline (Kanban) | Round open + per-persona session-open (always-on, not triggered) | All agents (self-administered) + Architect for cross-persona visibility | factory | Per-persona in-flight work cap (suggested 3: proposals / findings / drafts not yet landed); cross-persona cap (suggested 7 under architect-bottleneck review queue per GOVERNANCE §11). Over-cap flags to HUMAN-BACKLOG as `wip-pressure` rows. Always-on discipline per `docs/FACTORY-METHODOLOGIES.md` pull-vs-always-on criterion (this row is always-on, therefore not a skill). Cap numbers are *suggestions*; tune after 5-10 rounds of observation per Six Sigma Measure → Improve loop. | Inline self-report + HUMAN-BACKLOG row if over-cap + Architect notebook tally column | `user_kanban_six_sigma_process_preference.md` + `docs/FACTORY-METHODOLOGIES.md` + `docs/research/kanban-six-sigma-factory-process.md` + GOVERNANCE.md §11 |
| 38 | Harness-surface cadenced audit | Every 5-10 rounds per populated harness (same cadence as skill-tune-up and agent-QOL). Stubs don't tick. | Per populated harness: Claude owned by Architect (Kenji) **interim** until a dedicated harness-guide role is decided; plugin-provided `claude-code-guide` agent (Anthropic official plugin cache, not a local `.claude/agents/` file) is a reference resource consulted during audit, not the audit runner. Codex / Cursor / GitHub Copilot / Antigravity / Amazon Q / Kiro — TBD when populated (either dedicated guide per harness or a shared multi-harness guide). | factory | Audit each populated harness's platform surfaces for new features, cut features, behavioural changes. For Claude: model / Code CLI / Desktop app / Agent SDK / API. For Codex / Cursor / Copilot / Antigravity / Amazon Q / Kiro: per-harness equivalents inventoried at first-populated audit. Primary feature-comparison axis per harness is skill-authoring + eval-driven feedback loop (the Claude-Code feature that made it Aaron's primary choice — `memory/user_skill_creator_killer_feature_feedback_loop.md`). Update `docs/HARNESS-SURFACES.md` living inventory with adoption statuses (adopted / watched / untested / rejected / stub). When audit surfaces drift, either adopt (ADR if Tier-3), retire the workaround the new feature obsoletes, or record explicit rejection in `docs/WONT-DO.md`. Integration-point tests per harness are owned by a *different* harness per the capability-boundary rule (a harness cannot honestly self-verify its own factory integration from within itself). Triggering incidents: 2026-04-20 AutoMemory miss (Anthropic's Q1-2026 feature mis-attributed as factory-native) + 2026-04-20 multi-harness expansion (Aaron: factory supports multiple harnesses; each tests the others'). | `docs/HARNESS-SURFACES.md` audit log row per cycle per populated harness; ADRs under `docs/DECISIONS/` for Tier-3 adoptions; `docs/research/meta-wins-log.md` entry when a pre-existing factory assumption is found to have been wrong | `feedback_claude_surface_cadence_research.md` + `feedback_multi_harness_support_each_tests_own_integration.md` + `user_skill_creator_killer_feature_feedback_loop.md` + `reference_automemory_anthropic_feature.md` + `reference_autodream_feature.md` |
| 39 | Hot-file-path detector | Round-cadence (every round close) or every 5-10 rounds — whichever catches churn drift before the next merge-tangle. Proposed 2026-04-21. | TBD — candidate capability skill `hot-file-detector` (queued in BACKLOG P1); Architect runs inline until the skill lands. | factory (ships to adopters as a command-line recipe) | `git log --since="60 days ago" --name-only --pretty=format: \| grep -v '^$' \| sort \| uniq -c \| sort -rn \| head -25` — git history *is* the index. Heuristic threshold: >20 changes in 60d on a single monolithic doc = investigate; >30 = refactor candidate (tune after 5-10 rounds). Per-file decision is one of four: `refactor-split` (per-row, per-round, per-section), `consolidate-reduce` (merge with a sibling), `accept-as-append-only` (legitimately append-only → split into per-round files rather than trimming), or `observe`. Pair with merge-tangle fingerprints from `docs/research/parallel-worktree-safety-2026-04-22.md` §9 — a hot file is worse if also in a recent conflict list. Triggering incident: PR #31 5-file merge-tangle (2026-04-21) where `docs/ROUND-HISTORY.md` at 33 changes / 60d was the #1 conflict source, and `docs/BACKLOG.md` at 26 already has an in-flight split ADR. | Audit doc per cycle listing top-N hot paths + per-file decision; BACKLOG rows for refactor-split candidates; ADRs for structural changes. | `feedback_hot_file_path_detector_hygiene.md` + `docs/DECISIONS/2026-04-22-backlog-per-row-file-restructure.md` (same pattern) |
| 40 | GitHub-settings drift detector | Weekly (cron `17 14 * * 1`) + on any change to `tools/hygiene/github-settings.expected.json` / detector script / workflow file. Added 2026-04-21 after `AceHack/Zeta` → `Lucent-Financial-Group/Zeta` org-transfer silently flipped `secret_scanning` and `secret_scanning_push_protection` from enabled to disabled. | Automated (`.github/workflows/github-settings-drift.yml`); human resolves on drift | factory (ships to adopters as a template; repo-specific expected snapshot per adopter) | Live `gh api` snapshot vs. checked-in `tools/hygiene/github-settings.expected.json`: repo-level toggles (merge methods, security-and-analysis), rulesets + rule contents, classic branch protection on default branch, Actions permissions + variables + counts of secrets, environments + protection-rule types, Pages config, CodeQL default-setup state, webhook / deploy-key / secret counts. Script at `tools/hygiene/check-github-settings-drift.sh` exits 1 on drift and prints `diff -u` output. Resolution: intentional → re-snapshot + commit new expected with rationale; unintentional → revert in GitHub + rerun detector. | `docs/GITHUB-SETTINGS.md` + `tools/hygiene/github-settings.expected.json` + workflow run log + optional `memory/reference_github_*.md` entry if drift source is non-obvious | `feedback_github_settings_as_code_declarative_checked_in_file.md` + `feedback_blast_radius_pricing_standing_rule_alignment_signal.md` + `project_zeta_org_migration_to_lucent_financial_group.md` |

## Ships to project-under-construction

Expand Down
254 changes: 254 additions & 0 deletions docs/GITHUB-SETTINGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
# GitHub repo settings — declared state

This doc is the **declarative source of truth** for every GitHub
repo setting that GitHub does not itself expose as a checked-in
config file. Workflow YAML, CODEOWNERS, Dependabot config, and
pre-commit hooks are already declarative in-tree — not tracked
here. What *is* tracked here: click-ops toggles that live inside
GitHub's UI or require API calls to change.

The machine-readable companion is
[`tools/hygiene/github-settings.expected.json`](../tools/hygiene/github-settings.expected.json).
That JSON file is **authoritative** — if this markdown ever
disagrees with it, the JSON wins and this file gets updated.

Motivation (human maintainer, 2026-04-21):

> "its nice having the expected settings declarative defined"
>
> "i hate things in GitHub where I can't check in the
> declarative settgins so we will save a back[up]"

The same day we transferred `AceHack/Zeta` →
`Lucent-Financial-Group/Zeta` and discovered that GitHub's
org-transfer code path silently flipped `secret_scanning` and
`secret_scanning_push_protection` from `enabled` to `disabled`.
That silent drift is exactly what this system detects.

## How this works

1. **Expected state** is recorded in
`tools/hygiene/github-settings.expected.json` — normalized
output of `tools/hygiene/snapshot-github-settings.sh`.
2. **Drift detector** is `tools/hygiene/check-github-settings-drift.sh`.
It re-runs the snapshot against the live repo and diffs
against the expected JSON. Exit 0 on match, 1 on drift.
3. **Cadence** is enforced by
`.github/workflows/github-settings-drift.yml` — weekly cron
+ `workflow_dispatch` for manual runs. Drift blocks the
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: In the numbered list, this line starting with + will render as a nested unordered list item (not as “cron plus workflow_dispatch” text). If the intent is prose, rewrite without a leading list marker (e.g., “and workflow_dispatch …”).

Suggested change
+ `workflow_dispatch` for manual runs. Drift blocks the
and `workflow_dispatch` for manual runs. Drift blocks the

Copilot uses AI. Check for mistakes.
weekly run (visible in Actions tab as a failing job);
resolve by either reverting the unexpected change or
re-snapshotting and committing the new expected.
4. **On any settings change** (ruleset edit, new required
check, flipped security toggle, new environment, ...) the
same-commit obligation is: re-run
`snapshot-github-settings.sh`, commit the new expected
JSON alongside whatever configuration caused the drift,
with a message explaining *why* the setting changed.

See `docs/FACTORY-HYGIENE.md` row #40 for the full cadence /
owner / scope specification.

## What's captured

### Repo-level toggles

- Merge methods: squash on; merge commit and rebase off.
(`merge_commit_message` + `merge_commit_title` are still
captured even though merge-commit is off — the defaults
determine format if we ever flip the toggle on.)
- Auto-merge enabled; update branch button enabled;
auto-delete branch on merge enabled;
`use_squash_pr_title_as_default` off (explicit PR-title-or-
commit-title selection still applies).
- `allow_forking` on — required for the fork-based PR
workflow (contributors develop on personal forks and submit
PRs back to this repo; keeps the base repo's cost surface
thin).
- Squash commit title: PR title (falls back to commit title
for single-commit PRs); squash commit message: concatenated
commit messages.
- Web commit signoff not required (public repo, pre-v1).
- Visibility: public. `archived: false`, `disabled: false`,
`is_template: false`.
- Description: "F# implementation of DBSP for .NET 10".
Homepage: `https://lucent-financial-group.github.io/Zeta/`.
- Topics: empty (no classification tags yet).
- Features enabled: issues, discussions, projects, wiki,
downloads, pull-requests, pages.
- `pull_request_creation_policy: all` — anyone with push
access (collaborators + org members per team perms) can
open PRs.
- `custom_properties`: empty object — no org-level custom
repo properties set yet.
- Security-and-analysis: Dependabot security updates enabled;
secret scanning enabled; secret scanning push-protection
enabled; non-provider-pattern scanning, AI detection,
delegated-alert-dismissal, delegated-bypass, and validity
checks all disabled (higher false-positive / seat-cost
profile; revisit post-v1).

### Repo security extras

- Vulnerability alerts (Dependabot alerts) enabled.
- Automated security fixes (Dependabot auto-PRs) enabled,
not paused.
- Private vulnerability reporting enabled — external
researchers can open confidential advisories via the
Security tab.
- Interaction limits: none (would be `interaction_limits:
{limit, origin, expires_at}` when active — used to rate-
limit comment/issue activity during incident response).
- Autolinks: none — no external issue-tracker linking.
- Topics: empty — no discovery tags.

### Rulesets

Single ruleset named `Default` (id 15256879), enforcement
`active`, target `branch`, condition
`ref_name.include = ["~DEFAULT_BRANCH"]`. Six rules:

1. **Deletion** — block default-branch deletion.
2. **Non-fast-forward** — block non-fast-forward pushes.
3. **Copilot code review** — review draft PRs + review on
push.
4. **Code quality** — severity all.
5. **Pull request** — squash-only merge method;
`required_review_thread_resolution: true`;
`required_approving_review_count: 0` (agent-authored
repo — human review not required, AI review is via the
copilot-code-review and code-quality rules above).
6. **Required linear history**.

Note on the **`code_scanning` rule**: we toggled it OFF
2026-04-21 because it binds to CodeQL *default-setup*
configurations and Zeta uses *advanced-setup*
(`.github/workflows/codeql.yml` with `build-mode: manual`
for csharp + per-language SARIF upload). The rule returned
NEUTRAL / "1 configuration not found" and blocked PR #42
despite all advanced-setup sub-jobs passing. Diagnostic:
`gh api /repos/<owner>/<repo>/code-scanning/default-setup
--jq .state` returns `not-configured` on advanced-only
setups; the rule requires this state to be `configured`.
Re-enabling requires either (a) enabling default-setup
alongside advanced — unverified coexistence, duplicate
compute, or (b) discovering whether the rule can bind to
advanced-setup (untested).

### Classic branch protection (on `main`)

Overlaps with the ruleset; kept as defence-in-depth. Six
required status checks (strict mode):

- `build-and-test (ubuntu-22.04)`
- `build-and-test (macos-14)`
- `lint (semgrep)`
- `lint (shellcheck)`
- `lint (actionlint)`
- `lint (markdownlint)`

Other protections: dismiss stale reviews on; required linear
history; required conversation resolution; force pushes and
deletions blocked; enforce_admins off.

### Actions

- Actions enabled; `allowed_actions: all`.
- Variables (2):
- `COPILOT_AGENT_FIREWALL_ENABLED = "true"`
- `COPILOT_AGENT_FIREWALL_ALLOW_LIST_ADDITIONS = " "` (space —
no additions beyond the Copilot firewall defaults).
- Secrets (0): no Actions secrets. Any future secret addition
must be accompanied by a rationale in this doc.

### Workflows (5 active)

Static (checked-in):

- `.github/workflows/codeql.yml` (CodeQL)
- `.github/workflows/gate.yml` (gate matrix: build, test,
lint, semgrep)

Dynamic (GitHub-managed):

- Copilot code review
- Dependabot Updates
- Automatic Dependency Submission (NuGet)

### Environments

- `github-pages` environment with one `branch_policy`
protection rule — deployments only from `main`.

### GitHub Pages

- Build type: `workflow`.
- Source: branch `main`, path `/`.
- HTTPS enforced: yes.
- URL:
`https://lucent-financial-group.github.io/Zeta/` (transferred
from `https://acehack.github.io/Zeta/` on 2026-04-21).

### CodeQL default-setup

- State: `not-configured` (intentional — we use advanced
setup via `.github/workflows/codeql.yml`).

### Webhooks + deploy keys + secrets

- Webhooks: 0.
- Deploy keys: 0.
- Actions secrets: 0.
- Dependabot secrets: 0.

## What's NOT captured

- **Workflow YAML** — already declarative in
`.github/workflows/`.
- **CODEOWNERS** — already declarative in `.github/CODEOWNERS`
if/when we add one.
- **Dependabot config** — already declarative in
`.github/dependabot.yml`.
- **Pre-commit hooks** — already declarative in
`.pre-commit-config.yaml` if/when we add one.
- **`.github/copilot-instructions.md`** — already declarative;
audited under FACTORY-HYGIENE row 14.
- **Secret values** — the counts are captured; the values
would be a security hole. Never write secret values here.
- **Individual user/team permissions on the org** — org-level,
out of scope for per-repo declaration. If this ever grows,
consider a sibling `docs/ORG-SETTINGS.md` with the same
pattern applied to the org.
- **Transient statuses** — `security_and_analysis.*.url`,
timestamps, etc. The snapshot script strips these.

## How to update

```bash
# After making an intentional settings change in GitHub
# UI or via API, re-snapshot and commit:
tools/hygiene/snapshot-github-settings.sh \
--repo Lucent-Financial-Group/Zeta \
> tools/hygiene/github-settings.expected.json
git add tools/hygiene/github-settings.expected.json
# If the human-readable narrative also needs updating,
# edit docs/GITHUB-SETTINGS.md to match.
git commit -m "chore(settings): <what changed + why>"
```

Unintentional drift (detected by the weekly drift workflow or
a manual run) is fixed in the opposite direction: revert the
setting in GitHub, rerun the detector to confirm match, and
record the drift source in the PR body (or an ADR under
`docs/DECISIONS/` if the diagnosis is non-trivial and worth
preserving for future maintainers).

## Related

- `tools/hygiene/snapshot-github-settings.sh` — generates the
normalized JSON.
- `tools/hygiene/check-github-settings-drift.sh` — the drift
detector.
- `.github/workflows/github-settings-drift.yml` — cadence
workflow.
- `docs/FACTORY-HYGIENE.md` row #40 — the hygiene row.
Loading
Loading