diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 64a2183d..a58a8fe7 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -109,7 +109,7 @@ workflows: - 'dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8' - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' '.github/workflows/mirror.yml': - - 'hyperpolymath/standards@d55304a01b5260b0511aae0d14ed8b8770b00293' + - 'hyperpolymath/standards@70019048f862250c04346f27e6ca196ff7aa187a' '.github/workflows/no-js-scan.yml': - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' '.github/workflows/pages-archive.yml': @@ -264,6 +264,15 @@ dependencies: commit: 'sha1-0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d' owner_id: 75048950 repo_id: 623796603 + 'hyperpolymath/standards@70019048f862250c04346f27e6ca196ff7aa187a': + ref: '70019048f862250c04346f27e6ca196ff7aa187a' + commit: 'sha1-70019048f862250c04346f27e6ca196ff7aa187a' + owner_id: 6759885 + repo_id: 1116521501 + uses: + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8' + - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' 'hyperpolymath/standards@d55304a01b5260b0511aae0d14ed8b8770b00293': ref: 'd55304a01b5260b0511aae0d14ed8b8770b00293' commit: 'sha1-d55304a01b5260b0511aae0d14ed8b8770b00293' @@ -274,7 +283,6 @@ dependencies: - 'dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8' - 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406' - 'swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6' - - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' 'ocaml/setup-ocaml@93303b622b2522e4411e295f9e77411a24912ac7': ref: 'v3.9.0' commit: 'sha1-93303b622b2522e4411e295f9e77411a24912ac7' diff --git a/.github/workflows/mirror-reusable.yml b/.github/workflows/mirror-reusable.yml index a8569176..249daed5 100644 --- a/.github/workflows/mirror-reusable.yml +++ b/.github/workflows/mirror-reusable.yml @@ -14,13 +14,14 @@ # `vars._MIRROR_ENABLED == 'true'`, so forge selection is # configured per-repo via Actions vars — no per-call inputs required. # -# Callers pass the per-forge SSH keys EXPLICITLY, by the names declared in -# `workflow_call.secrets` below (GITLAB_SSH_KEY, BITBUCKET_SSH_KEY, -# CODEBERG_SSH_KEY, SOURCEHUT_SSH_KEY, DISROOT_SSH_KEY, GITEA_SSH_KEY, -# RADICLE_KEY). Do not use `secrets: inherit`: it forwards every repo/org -# secret the caller can see, not just these seven (standards#1108 §6b). -# Without a passed secret, `${{ secrets.X }}` inside the reusable -# evaluates to empty and that forge's job no-ops. +# Callers pass the per-forge SSH keys and optional R2 backup secrets +# EXPLICITLY, by the names declared in `workflow_call.secrets` below +# (GITLAB_SSH_KEY, BITBUCKET_SSH_KEY, CODEBERG_SSH_KEY, SOURCEHUT_SSH_KEY, +# DISROOT_SSH_KEY, GITEA_SSH_KEY, RADICLE_KEY, R2_ACCESS_KEY_ID, +# R2_SECRET_ACCESS_KEY, R2_AGE_RECIPIENT). Do not use `secrets: inherit`: +# it forwards every repo/org secret the caller can see, not just these ten +# (standards#1108 §6b). Without a passed secret, `${{ secrets.X }}` inside +# the reusable evaluates to empty and that forge's job no-ops. # # Caller example (wrapper): # # SPDX-License-Identifier: MPL-2.0 @@ -42,6 +43,9 @@ # DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }} # GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }} # RADICLE_KEY: ${{ secrets.RADICLE_KEY }} +# R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} +# R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} +# R2_AGE_RECIPIENT: ${{ secrets.R2_AGE_RECIPIENT }} name: Mirror to Git Forges (reusable) @@ -453,6 +457,7 @@ jobs: mirror-r2-bundle: # D303/D304: Cloudflare R2 git-bundle backup with mandatory client-side # age encryption before any bytes leave the runner. + name: Mirror encrypted bundle to Cloudflare R2 timeout-minutes: 20 runs-on: ${{ inputs.runs-on }} if: vars.R2_BUNDLE_MIRROR_ENABLED == 'true' @@ -474,9 +479,9 @@ jobs: run: | set -euo pipefail if [[ ! "$R2_ENDPOINT" =~ ^https://[a-zA-Z0-9-]+(\.eu|\.fedramp|\.us)?\.r2\.cloudflarestorage\.com$ ]] || - [[ ! "$R2_BUCKET" =~ ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$ ]] || + [[ ! "$R2_BUCKET" =~ ^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$ ]] || [[ ! "$AGE_RECIPIENT" =~ ^(age1[a-z0-9]{58}|ssh-ed25519[[:space:]]+AAAA[A-Za-z0-9+/=]+)$ ]]; then - echo "::error::Configure valid R2_ENDPOINT_URL (*.r2.cloudflarestorage.com), R2_BUCKET, and R2_AGE_RECIPIENT (age1... or ssh-ed25519)" + echo "::error::Configure valid R2_ENDPOINT_URL (*.r2.cloudflarestorage.com), R2_BUCKET (lowercase alphanumeric and hyphens), and R2_AGE_RECIPIENT (age1... or ssh-ed25519)" exit 1 fi - name: Create, age-encrypt, and upload git bundle to Cloudflare R2 @@ -490,8 +495,13 @@ jobs: AGE_RECIPIENT: ${{ env.R2_RECIPIENT }} MIRROR_ORG: ${{ vars.R2_ORG || vars.MIRROR_ORG || github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} + COMMIT_SHA: ${{ github.sha }} run: | set -euo pipefail + if [[ ! "$COMMIT_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then + echo "::error::Invalid COMMIT_SHA" + exit 1 + fi if ! command -v age >/dev/null 2>&1; then sudo apt-get update -qq && sudo apt-get install -y -qq age fi @@ -503,7 +513,11 @@ jobs: age -r "$AGE_RECIPIENT" -o "$ENC_BUNDLE" "$RAW_BUNDLE" rm -f "$RAW_BUNDLE" test -s "$ENC_BUNDLE" - aws s3 cp "$ENC_BUNDLE" "s3://${R2_BUCKET}/${MIRROR_ORG}/${REPO_NAME}/latest.bundle.age" --endpoint-url "$R2_ENDPOINT" + BUNDLE_TS="$(date -u +%Y%m%dT%H%M%SZ)-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + SNAPSHOT_KEY="s3://${R2_BUCKET}/${MIRROR_ORG}/${REPO_NAME}/snapshots/${BUNDLE_TS}-${COMMIT_SHA}.bundle.age" + LATEST_KEY="s3://${R2_BUCKET}/${MIRROR_ORG}/${REPO_NAME}/latest.bundle.age" + aws s3 cp "$ENC_BUNDLE" "$SNAPSHOT_KEY" --endpoint-url "$R2_ENDPOINT" + aws s3 cp "$ENC_BUNDLE" "$LATEST_KEY" --endpoint-url "$R2_ENDPOINT" - name: Skipped (R2 credentials or age recipient not configured) if: ${{ env.R2_KEY_ID == '' || env.R2_SECRET_KEY == '' || env.R2_RECIPIENT == '' }} run: | diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index a80c824e..5573342c 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -14,8 +14,8 @@ permissions: jobs: mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@d55304a01b5260b0511aae0d14ed8b8770b00293 - # Exactly the seven optional secrets the callee declares at the + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@70019048f862250c04346f27e6ca196ff7aa187a + # Exactly the ten optional secrets the callee declares at the # pinned SHA (verified declared ⊇ referenced). Was `secrets: # inherit`, which forwarded every repo/org secret (standards#1108 # §6b; the old convention is tracked in #943). @@ -27,3 +27,6 @@ jobs: DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }} GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }} RADICLE_KEY: ${{ secrets.RADICLE_KEY }} + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + R2_AGE_RECIPIENT: ${{ secrets.R2_AGE_RECIPIENT }} diff --git a/docs/MIRROR-HOST-VERIFICATION.adoc b/docs/MIRROR-HOST-VERIFICATION.adoc index f241e881..6ac111d7 100644 --- a/docs/MIRROR-HOST-VERIFICATION.adoc +++ b/docs/MIRROR-HOST-VERIFICATION.adoc @@ -40,11 +40,30 @@ Per owner rulings D303 and D304 (`standards#1234`), `mirror-reusable.yml` also provides an optional `mirror-r2-bundle` job gated on `vars.R2_BUNDLE_MIRROR_ENABLED == 'true'`. When enabled and provisioned with `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_ENDPOINT_URL` -(`https://.r2.cloudflarestorage.com`), `R2_BUCKET`, and -`R2_AGE_RECIPIENT` (`age1...` or `ssh-ed25519`), the runner creates a full +(`https://[.eu|.fedramp|.us].r2.cloudflarestorage.com`), `R2_BUCKET`, +and `R2_AGE_RECIPIENT` (`age1...` or `ssh-ed25519`), the runner creates a full `git bundle`, verifies it, encrypts it client-side with `age -r`, removes the plaintext bundle, and uploads only the `.bundle.age` ciphertext to Cloudflare -R2. +R2 under both an immutable `snapshots/-.bundle.age` object key +and `latest.bundle.age`. + +Callers must pass the R2 credentials explicitly in the `secrets:` block because +GitHub Actions does not forward caller secrets to a reusable workflow +automatically. Without these mappings, `mirror-r2-bundle` skips the upload and +emits a notice. Map `R2_AGE_RECIPIENT` under `secrets:` when the recipient is +stored as a secret; when stored as an Actions variable (`vars.R2_AGE_RECIPIENT`), +the reusable workflow reads it directly from `vars` without a secret mapping: + +[source,yaml] +---- +jobs: + mirror: + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@ + secrets: + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + R2_AGE_RECIPIENT: ${{ secrets.R2_AGE_RECIPIENT }} +---- `scripts/tests/science-ci-security-test.sh` launches the assertions in `scripts/tests/science-ci-security-test.rb`, which exercise every actual SSH diff --git a/docs/reorg/2026-10-14-tidy/ISSUE-1234-CLOSEOUT-REPORT.adoc b/docs/reorg/2026-10-14-tidy/ISSUE-1234-CLOSEOUT-REPORT.adoc index 422efc57..a26aa430 100644 --- a/docs/reorg/2026-10-14-tidy/ISSUE-1234-CLOSEOUT-REPORT.adoc +++ b/docs/reorg/2026-10-14-tidy/ISSUE-1234-CLOSEOUT-REPORT.adoc @@ -28,7 +28,7 @@ | *B / D332* | `.github/settings.yml` vs `check-settings-drift.sh` (`2026-10-14` comment 2) | *Ruled & Scripted (`95` active repos)* -| Ruled: *"Uninstall the Probot Settings app, then delete `.github/settings.yml` across the estate."* Full 95-repo census (64 `hyperpolymath`, 31 `metadatastician`) recorded below and automated in `scripts/triage-2026-10-23-issue-1234-apply.sh` (checks that app `1561` installation `145157322` is uninstalled before deleting `.github/settings.yml`). +| Ruled: *"Uninstall the Probot Settings app, then delete `.github/settings.yml` across the estate."* Full 95-repo census (65 `hyperpolymath`, 30 `metadatastician`) recorded below and automated in `scripts/triage-2026-10-23-issue-1234-apply.sh` (checks that app `1561` installation `145157322` is uninstalled before opening `.github/settings.yml` deletion PRs across all 95 repositories). | *A1 (D16)* | Remove ruleset bypass for apps `1561`, `946600`, `85455` @@ -694,14 +694,14 @@ All 24 repositories carrying `.github/workflows/cflite_*.yml` or `.github/workfl | *TRANSFER the 42 `*.jl` package repos to `metadatastician` in one wave* + 1 atomic PR to `julia-professional-registry`; *KEEP `julia-professional-registry` in `hyperpolymath`* | *Tier 4: Multi-Ecosystem / Registry-Published Flagships (`crates.io` / `hex.pm` / `npmjs.com` / Submodule Hubs)* -| `22` -| `affinescript`, `ambientops`, `anamnesis`, `blocky-writer`, `boj-server`, `boj-server-mk2`, `bunsenite`, `chapeliser`, `deed-core`, `developer-ecosystem`, `echidna`, `excel-economic-numbers-tool`, `git-scripts`, `gitbot-fleet`, `iseriser`, `jtv-lang`, `k9-ecosystem`, `nextgen-databases`, `odds-and-sods-package-manager`, `palimpsest-license`, `patallm-gallery`, `proven`, `repo-guardian`, `rrecord-verity`, `volumod` (note: `standards` is in Tier 2; `22` distinct non-Tier-2 repos) +| `25` +| `affinescript`, `ambientops`, `anamnesis`, `blocky-writer`, `boj-server`, `boj-server-mk2`, `bunsenite`, `chapeliser`, `deed-core`, `developer-ecosystem`, `echidna`, `excel-economic-numbers-tool`, `git-scripts`, `gitbot-fleet`, `iseriser`, `jtv-lang`, `k9-ecosystem`, `nextgen-databases`, `odds-and-sods-package-manager`, `palimpsest-license`, `patallm-gallery`, `proven`, `repo-guardian`, `rrecord-verity`, `volumod` (`25` distinct non-Tier-2 repos) | *Medium package-metadata cost:* `12` repos reference `crates.io/crates/*`, `7` reference `hex.pm/packages/*`, `8` reference `npmjs.com/package/*`, `affinescript` is referenced by `affinescript-verify.yml`, and `jtv-lang` hosts `ViperPkg` (`subdir = "viper-pkg"` in `julia-professional-registry`). GitHub maintains permanent HTTP/Git redirects on repo transfer so existing `cargo`/`hex`/`npm` installs continue working as long as the old `hyperpolymath/` name is never reused, and `Cargo.toml`/`mix.exs`/`package.json` `repository` URLs update on the next patch release. | *DEFER to Wave 2* (transfer per-flagship alongside next release bump, updating `Cargo.toml`/`mix.exs`/`package.json` and `affinescript-verify.yml`) | *Tier 5: Standalone Active Repositories (Zero External Registry URLs, Zero Estate CI Callers)* -| `274` -| All remaining 274 active public `hyperpolymath` repositories (0 archived, 0 forks, 0 `julia-professional-registry` entries, 0 `crates.io`/`hex.pm`/`npmjs.com` publications, 0 incoming `uses:` or `repository:` references from `standards` workflows) +| `271` +| All remaining 271 active public `hyperpolymath` repositories (0 archived, 0 forks, 0 `julia-professional-registry` entries, 0 `crates.io`/`hex.pm`/`npmjs.com` publications, 0 incoming `uses:` or `repository:` references from `standards` workflows) | *Low cost:* GitHub automatically redirects Git remotes and web URLs; org-level rulesets on `metadatastician` (`Branch-Floor`, `Tag-Floor`, `Require-Signed-Commits`, `Require-Linear-History`, `Secret-Scan-Floor`, `Lock-Floor`, `EstateBranching`, `EstateTagging`) immediately cover transferred repos without per-repo ruleset maintenance. Only per-repo cost is updating `README.adoc` / `CITATION.cff` / `.well-known` self-links via a post-transfer script (`docs/compliance/repo-transfer-to-enterprise-playbook.adoc`). | *READY TO TRANSFER in batched waves* (after A5 / D298 reverts `metadatastician` `Branch-Floor` and `Tag-Floor` to canon) |=== @@ -714,7 +714,7 @@ All 24 repositories carrying `.github/workflows/cflite_*.yml` or `.github/workfl | *D355* (A4 / D97 follow-up) | Repository transfer wave plan (`hyperpolymath` -> `metadatastician`) -| **(1) Keep Tier 0 (3 identity repos), Tier 1 (`cargo-zigbuild`, `MetaManifold-WebUI`; delete `explore` per D329), Tier 2 (12 CI/CD spine repos + `julia-professional-registry`), and the 3 private vaults/farm in `hyperpolymath`; (2) Transfer Tier 3 (42 `*.jl` package repos) to `metadatastician` with one atomic `Package.toml` URL PR in `julia-professional-registry` (per ADR-0003); (3) Transfer Tier 5 (274 zero-consumer active repos) in batches of 25 after D298 is applied; (4) Hold Tier 4 (22 registry-published flagships) for per-release transfer.** +| **(1) Keep Tier 0 (3 identity repos), Tier 1 (`cargo-zigbuild`, `MetaManifold-WebUI`; delete `explore` per D329), Tier 2 (12 CI/CD spine repos + `julia-professional-registry`), and the 3 private vaults/farm in `hyperpolymath`; (2) Transfer Tier 3 (42 `*.jl` package repos) to `metadatastician` with one atomic `Package.toml` URL PR in `julia-professional-registry` (per ADR-0003); (3) Transfer Tier 5 (271 zero-consumer active repos) in batches of 25 after D298 is applied; (4) Hold Tier 4 (25 registry-published flagships) for per-release transfer.** | Transfer Tier 3 (`42` `*.jl` repos) only for now / Transfer Tier 3 + Tier 4 + Tier 5 / Freeze all transfers |=== @@ -923,10 +923,10 @@ ____ *Ruling D332 (`2026-10-14` comment 2 on `#1234`):* *"Uninstall the Probot Settings app, then delete `.github/settings.yml` across the estate."* * **Owner Prerequisite:** Uninstall GitHub App `settings` (`app_id: 1561`, installation `145157322` at `https://github.com/settings/installations/145157322`). -* **Estate Census (95 active repositories carrying `.github/settings.yml`):** -** **`hyperpolymath` (64 repos):** `007`, `ambientops`, `apiserver`, `arthropodologist`, `asdf-overlay-manager`, `audit-my-cloud`, `awesome-lifecycle`, `betlang`, `bunsenite`, `bust`, `cable-audit`, `chapeliser`, `chrono-mcp`, `cicd-hyper-a`, `civic-connect`, `claude-config-vault`, `clippy-cornucopia`, `conative-gating`, `conflow`, `con-sensual`, `CryptographicAudit.jl`, `developer-ecosystem`, `dicti0nary-attack`, `docmatrix`, `echidnabot`, `ensaid-concatenator`, `ensaid- fleet`, `EntryPoint.jl`, `ephapax`, `error-catalog-mcp`, `exotic-programming-ideas`, `ExplicitlyTyped.jl`, `family-ops`, `feedback-o-tron`, `FireFlag.jl`, `flat-mate`, `formatrix-docs`, `git-private-farm-dashboard`, `git-reticulator`, `grim-repo`, `guix-overlay-pack`, `heterogenous-mobile-computing`, `hypatia`, `i-human`, `ipv6-only`, `iseriser`, `januskey`, `jtv-lang`, `julia-ecosystem`, `line-em-up`, `lithoglyph`, `local-coord-mcp`, `MarkovChains.jl`, `meta-repo-manager`, `minix-sdk`, `model-check-mcp`, `mustfile`, `my-lang`, `nesy-solver`, `network-ambulance`, `neurophone`, `nexia-list`, `nextgen-databases`, `nextgen-languages`. -** **`metadatastician` (31 repos):** `_pathroot`, `affinescript-libraries`, `apate`, `arango-arango`, `burble`, `chora`, `data-prep`, `deliberation`, `design-practice`, `epistemic-logic`, `ergo`, `eval-harness`, `evidence-graph`, `floating-point-arithmetic`, `formal-methods`, `git-forensics`, `governance-audit`, `graph-analytics`, `information-retrieval`, `kcq`, `knot-knot`, `knowledge-representation`, `language-design`, `marid`, `meta-analysis`, `metadatastician-governance`, `methodology`, `model-checking`, `odraz`, `ontology-engineering`, `probabilistic-programming`. -* Automated deletion (gated on verifying app `1561` is uninstalled) is in `scripts/triage-2026-10-23-issue-1234-apply.sh` (Section D332). +* **Estate Census (95 active repositories in `hyperpolymath` and `metadatastician` carrying `.github/settings.yml` on `HEAD`):** +** **`hyperpolymath` (65 repos):** `.github`, `affinescript-vite`, `anytype`, `Apodeixis.jl`, `atoma`, `autoformal-fidelity`, `bactrian`, `bofj-kitt`, `boj-server`, `cafescripto`, `cargo-zigbuild`, `cicd-squabbler`, `contractiles`, `CoprocessorRuntime.jl`, `czech-file-knife`, `dictask`, `echidna`, `ephapax`, `EpistemicTypes.jl`, `fraying-model-computational-testbed`, `haec`, `hermeneia`, `heron-polyfeed-reader`, `ideas-to-alphas`, `jaffascript`, `kategoria`, `kitchenspeak`, `krl`, `lithoglyph`, `llm-grace`, `lucidscript`, `natsci-studio`, `nesy-solver`, `nextgen-typing`, `occupancy-types`, `panoply`, `patch-bridge`, `proof-burrower`, `Protoctist.jl`, `proven`, `pseudoscript`, `quintessence`, `rattlescript`, `reasonably-good-token-vault`, `recon-silly-ation`, `ResidualEvidenceTypes.jl`, `resource-algebra`, `rsr-template-repo`, `scaffoldia`, `secret-types`, `sholes`, `snifs`, `somethings-fishy`, `stateful-artefacts`, `system-et`, `trigger`, `trope-checker`, `typed-wasm`, `unified-api-adapter`, `vocarium`, `xmlpull-zig`, `zig-boringtls`, `zig-dnssec`, `zig-icu-collation`, `zig-unorm`. +** **`metadatastician` (30 repos):** `_pathroot`, `affinescript-libraries`, `berrywiki`, `bowtie-workbench`, `burble`, `cadastra`, `cerro-torre`, `cleave`, `consent-aware-web`, `first-post`, `gossamer`, `groove`, `hvn-dehallucinator`, `IDApTIK`, `knot-knot`, `kyq`, `lintel`, `marid`, `metadatastician-governance`, `odraz`, `paint-type`, `rokur`, `selur`, `sim-public-relations`, `spline`, `stapeln`, `svalinn`, `vordr`, `ZenodoDeposits.jl`, `ZeroInflatedCounts.jl`. +* Automated deletion PR creation across all 95 repositories (gated on verifying app `1561` is uninstalled) is in `scripts/triage-2026-10-23-issue-1234-apply.sh` (Section D332). ''' @@ -1040,6 +1040,6 @@ Per `docs/reorg/2026-10-14-tidy/README.adoc` and `#1234` **A4 (D97)**, the conso | *D355* | Repository transfer wave plan (A4 / D97 follow-up) -| **(1) Keep Tier 0 (3 identity repos), Tier 1 (forks; delete `explore` per D329), Tier 2 (12 CI/CD spine repos + `julia-professional-registry`), and 3 private vaults/farm in `hyperpolymath`; (2) Transfer Tier 3 (42 `*.jl` package repos) to `metadatastician` with 1 atomic `Package.toml` PR in `julia-professional-registry`; (3) Transfer Tier 5 (274 zero-consumer active repos) in batches of 25 after D298 is applied; (4) Hold Tier 4 (22 registry-published flagships) for per-release transfer.** +| **(1) Keep Tier 0 (3 identity repos), Tier 1 (forks; delete `explore` per D329), Tier 2 (12 CI/CD spine repos + `julia-professional-registry`), and 3 private vaults/farm in `hyperpolymath`; (2) Transfer Tier 3 (42 `*.jl` package repos) to `metadatastician` with 1 atomic `Package.toml` PR in `julia-professional-registry`; (3) Transfer Tier 5 (271 zero-consumer active repos) in batches of 25 after D298 is applied; (4) Hold Tier 4 (25 registry-published flagships) for per-release transfer.** | Transfer Tier 3 (`42` `*.jl` repos) only / Freeze all transfers |=== diff --git a/docs/reorg/2026-10-14-tidy/issue-triage-1049-1234.adoc b/docs/reorg/2026-10-14-tidy/issue-triage-1049-1234.adoc index 45ccf9a0..cef7bd17 100644 --- a/docs/reorg/2026-10-14-tidy/issue-triage-1049-1234.adoc +++ b/docs/reorg/2026-10-14-tidy/issue-triage-1049-1234.adoc @@ -760,5 +760,5 @@ Keep a legacy `|…a2ml` alternative only during the kyX overlap. (`1-formats/de ** *A6, D67 (rev.):* the 9 Marid Tier-1 packages also go to General with the same UUIDs (UUIDv8 profile T, D322), after a local RegistryCI AutoMerge guideline run. Note that D224's proposed General date was *2026-10-14* (today), gated on the bunx launcher PR, which landed as #1100. ** *A7, D330:* Jonathan checks GitLab for a private canonical-ums copy. ** *Comment (D333/D334):* kcq → kyq and kcX → kyX, executed (standards #1237, template #234, 7 derived CLAUDE.md copies, boj-server #364, QuantumCircuit.jl #42). Owner follow-up: rename the GitLab and Bitbucket mirror repos `metadatastician/kcq` → `kyq`. -. *DISPOSITION:* *COMPLETED & SCRIPTED FOR CLOSE-OUT* — see `docs/reorg/2026-10-14-tidy/ISSUE-1234-CLOSEOUT-REPORT.adoc` and `scripts/triage-2026-10-23-issue-1234-apply.sh` (`scripts/tests/triage-2026-10-23-issue-1234-apply-test.sh`). D304, D330 (A7), and D332 were ruled by Jonathan in comment 2 (`IC_kwDOQozEHc8AAAABb41I1g`); D304 is implemented in `.github/workflows/mirror-reusable.yml` (`mirror-r2-bundle`); A1–A6 and D332 censuses, AutoMerge guideline verification, and convergence actions are complete. +. *DISPOSITION:* *REPORTED & SCRIPTED (`ISSUE-1234-CLOSEOUT-REPORT.adoc`); CROSS-REPO WRITES PENDING OWNER-TOKEN CONVERGENCE* — see `docs/reorg/2026-10-14-tidy/ISSUE-1234-CLOSEOUT-REPORT.adoc` and `scripts/triage-2026-10-23-issue-1234-apply.sh` (`scripts/tests/triage-2026-10-23-issue-1234-apply-test.sh`). D304, D330 (A7), and D332 were ruled by Jonathan in comment 2 (`IC_kwDOQozEHc8AAAABb41I1g`); D304 is implemented in `.github/workflows/mirror-reusable.yml` (`mirror-r2-bundle`) and wired in `.github/workflows/mirror.yml`; A1–A6 and D332 censuses and local `RegistryCI.jl` AutoMerge guideline verification are complete in-tree, while A1/A5 ruleset PUTs, A2/A3/D332 cross-repo PR merges + `knot-knot` Pages publication, A6 General registration (blocked on 4 documented items), and D332 Probot Settings uninstallation remain gated on verified execution via `scripts/triage-2026-10-23-issue-1234-apply.sh --apply`. . *RISK:* A1/A5 are org/repo ruleset writes (`--dry-run` first). A2/A3/D332 delete workflows/settings files across repos. diff --git a/scripts/tests/science-ci-security-test.rb b/scripts/tests/science-ci-security-test.rb index 778de479..c49307f3 100644 --- a/scripts/tests/science-ci-security-test.rb +++ b/scripts/tests/science-ci-security-test.rb @@ -72,11 +72,61 @@ def workflow(name) assert(!r2_verify['continue-on-error'], 'mirror-r2-bundle verification failure is ignored') assert(r2_upload['run'].include?('age -r "$AGE_RECIPIENT" -o "$ENC_BUNDLE" "$RAW_BUNDLE"'), 'mirror-r2-bundle must encrypt with age before upload') assert(r2_upload['run'].include?('rm -f "$RAW_BUNDLE"'), 'mirror-r2-bundle must remove plaintext bundle before upload') +r2_script = r2_upload['run'] +# Match command lines so the earlier EXIT trap does not count as plaintext removal. +r2_commands = r2_script.lines.map(&:strip) +encrypt_at = r2_commands.index('age -r "$AGE_RECIPIENT" -o "$ENC_BUNDLE" "$RAW_BUNDLE"') +remove_at = r2_commands.index('rm -f "$RAW_BUNDLE"') +upload_at = r2_commands.index { |command| command.start_with?('aws s3 cp "$ENC_BUNDLE"') } +assert(encrypt_at && remove_at && upload_at && encrypt_at < remove_at && remove_at < upload_at, + 'mirror-r2-bundle must encrypt and remove plaintext before uploading ciphertext') +assert(r2_script.include?('/snapshots/${BUNDLE_TS}-${COMMIT_SHA}.bundle.age'), + 'mirror-r2-bundle must write an immutable timestamp+SHA snapshot bundle') +assert(r2_script.include?('/latest.bundle.age'), + 'mirror-r2-bundle must update latest.bundle.age') +Dir.mktmpdir('r2-snapshots-') do |tmp| + stubs = { + 'date' => 'printf "%s\n" 20261023T120000Z', + 'git' => 'if [ "$2" = create ]; then printf plaintext > "$3"; else test -s "$3"; fi', + 'age' => 'printf ciphertext > "$4"', + 'aws' => <<~'SH' + test ! -e "$RUNNER_TEMP/test-repo.bundle" || exit 1 + test "$(cat "$3")" = ciphertext || exit 1 + printf '%s\n' "$4" >> "$TEST_UPLOADS" + SH + } + stubs.each do |name, script| + path = File.join(tmp, name) + File.write(path, "#!/bin/sh\nset -eu\n#{script}\n") + File.chmod(0o755, path) + end + uploads = File.join(tmp, 'uploads') + sha = 'a' * 40 + env = { 'PATH' => "#{tmp}:#{ENV.fetch('PATH')}", 'RUNNER_TEMP' => tmp, + 'REPO_NAME' => 'test-repo', 'COMMIT_SHA' => sha, 'AGE_RECIPIENT' => 'test-recipient', + 'R2_BUCKET' => 'test-bucket', 'MIRROR_ORG' => 'test-owner', + 'R2_ENDPOINT' => 'https://test.r2.cloudflarestorage.com', 'TEST_UPLOADS' => uploads } + [[100, 1], [100, 2], [101, 1]].each do |run_id, attempt| + run!(env.merge('GITHUB_RUN_ID' => run_id.to_s, 'GITHUB_RUN_ATTEMPT' => attempt.to_s), + 'bash', '-c', r2_script) + assert(!File.exist?(File.join(tmp, 'test-repo.bundle.age')), 'R2 upload left ciphertext behind') + end + keys = File.readlines(uploads, chomp: true) + snapshots = keys.each_slice(2).map(&:first) + assert(snapshots.uniq.length == 3, 'R2 snapshot uploads collide across runs or attempts') + assert(snapshots.first == "s3://test-bucket/test-owner/test-repo/snapshots/20261023T120000Z-100-1-#{sha}.bundle.age", + 'R2 snapshot path or bundle filename format changed') + assert(keys.each_slice(2).all? { |_, latest| latest == 's3://test-bucket/test-owner/test-repo/latest.bundle.age' }, + 'R2 latest bundle path changed') +end +puts 'PASS: R2 snapshots differ across runs and attempts at the same timestamp and SHA; only ciphertext is uploaded' [ ['https://0123456789abcdef0123456789abcdef.r2.cloudflarestorage.com', 'estate-mirrors', 'age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p', true], + ['https://0123456789abcdef0123456789abcdef.us.r2.cloudflarestorage.com', 'estate-mirrors', 'age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p', true], ['http://0123456789abcdef0123456789abcdef.r2.cloudflarestorage.com', 'estate-mirrors', 'age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p', false], ['https://evil.example.com', 'estate-mirrors', 'age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p', false], ['https://0123456789abcdef0123456789abcdef.r2.cloudflarestorage.com', 'INVALID_BUCKET', 'age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p', false], + ['https://0123456789abcdef0123456789abcdef.r2.cloudflarestorage.com', 'estate.mirrors', 'age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p', false], ['https://0123456789abcdef0123456789abcdef.r2.cloudflarestorage.com', 'estate-mirrors', '', false] ].each do |endpoint, bucket, recipient, succeeds| out, err, status = Open3.capture3( diff --git a/scripts/tests/triage-2026-10-23-issue-1234-apply-test.sh b/scripts/tests/triage-2026-10-23-issue-1234-apply-test.sh index 25727301..6acea17b 100755 --- a/scripts/tests/triage-2026-10-23-issue-1234-apply-test.sh +++ b/scripts/tests/triage-2026-10-23-issue-1234-apply-test.sh @@ -28,13 +28,22 @@ for id in 15402978 18110196 18874804 18875919 14968573 14968592 14968613 1496872 done echo "PASS: all 10 A1 (D16) ruleset IDs are covered" -# 3. Verify close-out report covers A1-A7, D304, D332, and D335-D355. -for marker in "A1 (D16)" "A2 (D14)" "A3 (D24)" "A4 (D97)" "A5 (D298)" "A6 (D67" "A7 (D330)" "B / D304" "B / D332" "D355"; do +# 3. Verify A2 covers echidna stub fuzz_input.rs, A3 updates knot-knot pages.yml, and D332 covers 95 repos. +grep -q 'fuzz/fuzz_targets/fuzz_input.rs' "$SUT" +grep -q 'www/public' "$SUT" +grep -q 'ls-remote --exit-code --heads origin' "$SUT" +grep -q 'orgs/metadatastician/installations' "$SUT" +grep -q 'user/installations' "$SUT" +echo "PASS: A2 echidna stub, A3 knot-knot pages.yml, remote-branch resumability, and both owner installation endpoints are present" + +# 4. Verify close-out report covers A1-A7, D304, D332, D335-D355, and reconciled Tier 4 (25) / Tier 5 (271) counts. +for marker in "A1 (D16)" "A2 (D14)" "A3 (D24)" "A4 (D97)" "A5 (D298)" "A6 (D67" "A7 (D330)" "B / D304" "B / D332" "D355" "All remaining 271 active public"; do grep -q "$marker" "$REPORT" || { echo "FAIL: missing '$marker' in $REPORT" >&2; exit 1; } done -echo "PASS: close-out report contains all #1234 sections and ruling row D355" +echo "PASS: close-out report contains all #1234 sections, ruling row D355, and reconciled 357-repo tier counts" -# 4. --dry-run must execute cleanly against a stub `gh` that refuses any write verb. +# 5. --dry-run must execute cleanly against a stub `gh` that refuses any write verb, +# and must keep #306 and #1234 open while items are pending. WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT cat > "$WORK/gh" <<'SHIM' @@ -42,6 +51,13 @@ cat > "$WORK/gh" <<'SHIM' set -euo pipefail case "${1:-}" in api) + if [ "${2:-}" = "--include" ] && [ "${3:-}" = repos/hyperpolymath/explore ]; then + case "${TEST_EXPLORE:-absent}" in + present) printf 'HTTP/2.0 200 OK\n\n{}\n'; exit 0 ;; + absent) printf 'HTTP/2.0 404 Not Found\n\n{}\n'; exit 1 ;; + *) printf 'HTTP/2.0 403 Forbidden\n\n{}\n'; exit 1 ;; + esac + fi if [ "${2:-}" = "-X" ]; then echo "FAIL: unexpected write call under --dry-run: gh $*" >&2 exit 99 @@ -56,17 +72,32 @@ case "${1:-}" in repos/metadatastician/knot-knot) printf 'false\n' ;; - user/installations) - printf '1\n' + orgs/metadatastician/installations|user/installations) + [ "${TEST_INSTALLATION_ERROR:-}" != "$2" ] || exit 1 + [ "$3" = --paginate ] && [ "$4" = --slurp ] && [ "$5" = --jq ] + printf '%s\n' "${TEST_INSTALLATIONS:-[]}" | jq -r "$6" ;; repos/hyperpolymath/standards/issues/*) printf 'open\n' ;; + repos/hyperpolymath/standards/contents/.github/workflows) + [ "${TEST_WORKFLOWS:-}" != error ] || exit 1 + printf '%s\n' "${TEST_WORKFLOWS:-[]}" | jq -r "$4" + ;; *) printf '{}\n' ;; esac ;; + issue) + [ -n "${TEST_CLOSE_LOG:-}" ] || exit 99 + printf '%s\n' "$3" >> "$TEST_CLOSE_LOG" + ;; + repo) + [ "$2" = clone ] && [ -n "${TEST_CLONE_LOG:-}" ] || exit 99 + printf '%s\n' "$3" >> "$TEST_CLONE_LOG" + mkdir -p "$4" + ;; *) echo "FAIL: unexpected write call under --dry-run: gh $*" >&2 exit 99 @@ -75,5 +106,92 @@ esac SHIM chmod +x "$WORK/gh" -PATH="$WORK:$PATH" bash "$SUT" --dry-run >/dev/null -echo "PASS: --dry-run performs zero gh write calls" +dry_out="$(PATH="$WORK:$PATH" bash "$SUT" --dry-run)" +printf '%s\n' "$dry_out" | grep -q 'D332 .github/settings.yml targets: 95' +printf '%s\n' "$dry_out" | grep -q '#306 kept open' +printf '%s\n' "$dry_out" | grep -q '#1234 kept open' +echo "PASS: --dry-run performs zero gh write calls, enumerates 95 D332 repos, and gates issue closure on convergence" + +# 6. Exercise the actual close-out section with all earlier work converged. +# Only the gh shim can close issues; no network or repository writes occur. +{ + printf 'set -euo pipefail\nDRY_RUN=0\nREPO=hyperpolymath/standards\nREPORT_REL=report.adoc\n' + sed -n '/^run_gh()/,/^}/p' "$SUT" + sed -n '/^A1_PENDING=0/,/^D332_PENDING=0/p' "$SUT" + printf 'A3_PENDING=${TEST_A3_PENDING:-0}\n' + sed -n '/^echo "== Section 6:/,$p' "$SUT" +} > "$WORK/closeout.sh" + +close_log="$WORK/closed" +PATH="$WORK:$PATH" TEST_CLOSE_LOG="$close_log" bash "$WORK/closeout.sh" > "$WORK/closeout.out" +[ "$(cat "$close_log")" = 306 ] || { echo 'FAIL: A6 must hold #1234 open even when A3 is converged' >&2; exit 1; } +grep -q 'A6=1' "$WORK/closeout.out" +grep -q 'All 48 repositories' "$SUT" +echo "PASS: converged A3 closes #306 while unresolved A6 holds #1234 open" + +for scenario in explore-present explore-error casket-pages.yml pages-archive.yml pages.yml existing-a3; do + explore=absent workflows='[]' earlier_pending=0 + case "$scenario" in + explore-present) explore=present ;; + explore-error) explore=error ;; + existing-a3) earlier_pending=1 ;; + *) workflows="[{\"name\":\"$scenario\"}]" ;; + esac + : > "$close_log" + PATH="$WORK:$PATH" TEST_CLOSE_LOG="$close_log" TEST_EXPLORE="$explore" \ + TEST_WORKFLOWS="$workflows" TEST_A3_PENDING="$earlier_pending" \ + bash "$WORK/closeout.sh" > "$WORK/closeout.out" + [ ! -s "$close_log" ] || { echo "FAIL: premature closure for $scenario" >&2; exit 1; } + grep -q '#306 kept open' "$WORK/closeout.out" + grep -q '#1234 kept open' "$WORK/closeout.out" +done +if PATH="$WORK:$PATH" TEST_CLOSE_LOG="$close_log" TEST_WORKFLOWS=error \ + bash "$WORK/closeout.sh" > "$WORK/closeout.out"; then + echo 'FAIL: D341 query failure must stop closure' >&2 + exit 1 +fi +[ ! -s "$close_log" ] +echo "PASS: D329, each D341 workflow, existing A3 work, and failed queries prevent premature closure" + +# 7. Execute Section 5 with independently installed/uninstalled owners. The +# gh clone stub creates empty local fixtures, representing converged repos. +{ + printf 'set -euo pipefail\nDRY_RUN=${TEST_DRY_RUN:-0}\nWORK_TMP=$TEST_WORK_TMP\nD332_PENDING=0\n' + sed -n '/^run_gh()/,/^}/p' "$SUT" + sed -n '/^check_probot_installed()/,/^}/p' "$SUT" + sed -n '/^echo "== Section 5 /,/^# Section 6 /p' "$SUT" + printf 'printf "D332_PENDING=%%s\\n" "$D332_PENDING"\n' +} > "$WORK/settings.sh" + +for hyper in 0 1; do + for meta in 0 1; do + installations="$(jq -nc --argjson hyper "$hyper" --argjson meta "$meta" ' + [{installations: [{app_id:1561, account:{login:"unrelated-owner"}}, + {app_id:9999, account:{login:"hyperpolymath"}}]}, + {installations: ([{app_id:1561, account:{login:"hyperpolymath"}} | select($hyper == 1)] + + [{app_id:1561, account:{login:"metadatastician"}} | select($meta == 1)])}]')" + clone_log="$WORK/cloned" + : > "$clone_log" + PATH="$WORK:$PATH" TEST_CLONE_LOG="$clone_log" TEST_WORK_TMP="$WORK" \ + TEST_INSTALLATIONS="$installations" bash "$WORK/settings.sh" > "$WORK/settings.out" + pending=$((65 * hyper + 30 * meta)) + [ "$(wc -l < "$clone_log")" -eq "$((95 - pending))" ] + grep -q "D332_PENDING=$pending" "$WORK/settings.out" + if [ "$hyper" -eq 1 ] && grep -q '^hyperpolymath/' "$clone_log"; then + echo 'FAIL: personal-owner cleanup began before uninstall' >&2; exit 1 + fi + if [ "$meta" -eq 1 ] && grep -q '^metadatastician/' "$clone_log"; then + echo 'FAIL: organisation cleanup began before uninstall' >&2; exit 1 + fi + done +done +for endpoint in user/installations orgs/metadatastician/installations; do + if PATH="$WORK:$PATH" TEST_CLONE_LOG="$clone_log" TEST_WORK_TMP="$WORK" \ + TEST_INSTALLATION_ERROR="$endpoint" bash "$WORK/settings.sh" > "$WORK/settings.out" 2>&1; then + echo "FAIL: live installation query failure must stop cleanup: $endpoint" >&2; exit 1 + fi + PATH="$WORK:$PATH" TEST_CLONE_LOG="$clone_log" TEST_WORK_TMP="$WORK" TEST_DRY_RUN=1 \ + TEST_INSTALLATION_ERROR="$endpoint" bash "$WORK/settings.sh" > "$WORK/settings.out" + grep -q 'D332_PENDING=95' "$WORK/settings.out" +done +echo "PASS: each owner gates cleanup on its own uninstall, filters app/account across pages, and fails closed on query errors" diff --git a/scripts/triage-2026-10-23-issue-1234-apply.sh b/scripts/triage-2026-10-23-issue-1234-apply.sh index 458541f6..d5b5a22a 100755 --- a/scripts/triage-2026-10-23-issue-1234-apply.sh +++ b/scripts/triage-2026-10-23-issue-1234-apply.sh @@ -17,7 +17,7 @@ # bash scripts/triage-2026-10-23-issue-1234-apply.sh --dry-run # validate & print actions (default safe check) # bash scripts/triage-2026-10-23-issue-1234-apply.sh # execute live with owner token # -# Idempotent: safe to re-run; skips rulesets/repos/issues already converged. +# Idempotent: safe to re-run; skips rulesets/repos/branches/issues already converged. set -euo pipefail @@ -43,9 +43,60 @@ run_gh() { fi } +# ensure_open_pr — Ensure an open pull request exists for a pushed branch on a repository. +# Takes owner/repo, head branch, title, and body; creates the PR only when no +# open PR for that head branch exists yet, making interrupted runs resumable. +# Does not check DRY_RUN; callers must guard creation. Failures from `gh pr list` +# or `gh pr create` stop the script under `set -e`. +ensure_open_pr() { + local repo="$1" branch="$2" title="$3" body="$4" + local open_count + open_count="$(gh pr list --repo "$repo" --head "$branch" --state open --json number --jq 'length')" + if [ "$open_count" -gt 0 ]; then + echo " $repo: open PR already exists for branch $branch — skipping PR create" + else + gh pr create --repo "$repo" --head "$branch" --title "$title" --body "$body" + fi +} + +# check_probot_installed — Check Probot Settings (app_id 1561) for one owner. +# The personal account uses user/installations, filtered by account login; +# the organisation uses orgs/{owner}/installations. Paginate both endpoints. +# Prints the matching installation count and returns 0. +# If the query fails or returns invalid output, DRY_RUN=1 prints 1 and returns 0 +# so callers treat the app as installed. In live mode, reports the query failure +# on stderr and returns 1. +check_probot_installed() { + local owner="$1" endpoint count="" + case "$owner" in + hyperpolymath) endpoint=user/installations ;; + metadatastician) endpoint=orgs/metadatastician/installations ;; + *) echo "ERROR: unsupported installation owner: $owner" >&2; return 1 ;; + esac + if count="$(gh api "$endpoint" --paginate --slurp --jq "[.[].installations[]? | select(.app_id == 1561 and .account.login == \"$owner\")] | length" 2>/dev/null)" && [[ "$count" =~ ^[0-9]+$ ]]; then + printf '%s\n' "$count" + return 0 + fi + if [ "$DRY_RUN" -eq 1 ]; then + printf '1\n' + return 0 + fi + echo "ERROR: unable to query $endpoint for $owner (requires owner access)" >&2 + return 1 +} + WORK_TMP="$(mktemp -d)" trap 'rm -rf "$WORK_TMP"' EXIT +A1_PENDING=0 +A5_PENDING=0 +A2_PENDING=0 +A3_PENDING=0 +# General registrations remain blocked (A6/D67); this script does not resolve +# them. Clear this pending item only after their resolution is verified. +A6_PENDING=1 +D332_PENDING=0 + # --------------------------------------------------------------------------- # Section 1 — A1 (D16): Strip apps 1561, 85455, 946600 from bypass_actors on # the 10 hyperpolymath repository rulesets (metadatastician org/repo = 0). @@ -85,6 +136,9 @@ for item in "${A1_TARGETS[@]}"; do }' > "$payload" echo " $r ruleset $id: removing $bad_count bypass actor(s)" run_gh api -X PUT "repos/$r/rulesets/$id" --input "$payload" >/dev/null + if [ "$DRY_RUN" -eq 1 ]; then + A1_PENDING=$((A1_PENDING + 1)) + fi done # --------------------------------------------------------------------------- @@ -126,15 +180,17 @@ for id in 23868655 23868851; do exit 1 } echo " orgs/metadatastician/rulesets/$id: verified GET -> $after_csv" + else + A5_PENDING=$((A5_PENDING + 1)) fi done # --------------------------------------------------------------------------- # Section 3 — A2 (D14): Retire stub/broken ClusterFuzzLite workflows across -# the 14 DELETE repos (and stub fuzz_input.rs on echidna). +# the 14 DELETE repos, and remove the leftover stub fuzz_input.rs on echidna. # --------------------------------------------------------------------------- echo "" -echo "== Section 3 (A2 / D14): ClusterFuzzLite stub workflow retirement (14 DELETE repos) ==" +echo "== Section 3 (A2 / D14): ClusterFuzzLite stub workflow retirement (14 DELETE repos + echidna stub) ==" A2_DELETE_REPOS=( "hyperpolymath/absolute-zero:.github/workflows/cflite_pr.yml" @@ -156,43 +212,139 @@ A2_DELETE_REPOS=( for entry in "${A2_DELETE_REPOS[@]}"; do r="${entry%%:*}" wf="${entry##*:}" + branch="chore/d14-retire-cflite-stub" + pr_title="chore(ci): retire stub ClusterFuzzLite workflow (D14, standards#1234 A2)" + pr_body="Retires the template-stub ClusterFuzzLite workflow and prunes \`actions.lock\` per owner ruling **D14** (\`hyperpolymath/standards#1234\` item **A2**)." if [ "$DRY_RUN" -eq 1 ]; then echo " [dry-run] A2 retire stub CFLite workflow: $r ($wf + .clusterfuzzlite/ + fuzz/ + prune-stale.sh)" + A2_PENDING=$((A2_PENDING + 1)) else - echo " A2 retiring stub CFLite workflow on $r ($wf)..." + echo " A2 checking $r ($wf)..." repo_dir="$WORK_TMP/a2-${r//\//_}" gh repo clone "$r" "$repo_dir" -- --depth=1 + if [ ! -e "$repo_dir/$wf" ] && [ ! -d "$repo_dir/.clusterfuzzlite" ] && [ ! -d "$repo_dir/fuzz" ]; then + echo " $r: already converged on default branch" + continue + fi + A2_PENDING=$((A2_PENDING + 1)) + if git -C "$repo_dir" ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then + echo " $r: remote branch $branch already exists" + ensure_open_pr "$r" "$branch" "$pr_title" "$pr_body" + continue + fi ( cd "$repo_dir" - git checkout -b chore/d14-retire-cflite-stub + git checkout -b "$branch" git rm -rf --ignore-unmatch "$wf" .clusterfuzzlite fuzz if [ -f .github/workflows/actions.lock ]; then bash "$ROOT/scripts/prune-stale.sh" .github/workflows git add .github/workflows/actions.lock fi if ! git diff --cached --quiet; then - git commit -S -m "chore(ci): retire stub ClusterFuzzLite workflow (D14, standards#1234 A2)" - git push -u origin chore/d14-retire-cflite-stub - gh pr create --repo "$r" \ - --title "chore(ci): retire stub ClusterFuzzLite workflow (D14, standards#1234 A2)" \ - --body "Retires the template-stub ClusterFuzzLite workflow and prunes \`actions.lock\` per owner ruling **D14** (\`hyperpolymath/standards#1234\` item **A2**)." + git commit -S -m "$pr_title" + git push -u origin "$branch" + ensure_open_pr "$r" "$branch" "$pr_title" "$pr_body" fi ) fi done +ECHIDNA_REPO="hyperpolymath/echidna" +ECHIDNA_STUB="fuzz/fuzz_targets/fuzz_input.rs" +ECHIDNA_BRANCH="chore/d14-retire-echidna-fuzz-stub" +ECHIDNA_TITLE="chore(fuzz): remove leftover template stub fuzz_input.rs (D14, standards#1234 A2)" +ECHIDNA_BODY="Removes the leftover template stub \`fuzz/fuzz_targets/fuzz_input.rs\` while retaining the 3 real fuzz targets (\`fuzz_aspect_tagging.rs\`, \`fuzz_concept_Arena.rs\`, \`fuzz_dispatch.rs\`) per owner ruling **D14** (\`hyperpolymath/standards#1234\` item **A2**)." + +if [ "$DRY_RUN" -eq 1 ]; then + echo " [dry-run] A2 remove leftover stub target on $ECHIDNA_REPO ($ECHIDNA_STUB)" + A2_PENDING=$((A2_PENDING + 1)) +else + echo " A2 checking $ECHIDNA_REPO ($ECHIDNA_STUB)..." + echidna_dir="$WORK_TMP/a2-echidna" + gh repo clone "$ECHIDNA_REPO" "$echidna_dir" -- --depth=1 + if [ ! -e "$echidna_dir/$ECHIDNA_STUB" ]; then + echo " $ECHIDNA_REPO: $ECHIDNA_STUB already absent on default branch" + else + A2_PENDING=$((A2_PENDING + 1)) + if git -C "$echidna_dir" ls-remote --exit-code --heads origin "$ECHIDNA_BRANCH" >/dev/null 2>&1; then + echo " $ECHIDNA_REPO: remote branch $ECHIDNA_BRANCH already exists" + ensure_open_pr "$ECHIDNA_REPO" "$ECHIDNA_BRANCH" "$ECHIDNA_TITLE" "$ECHIDNA_BODY" + else + ( + cd "$echidna_dir" + git checkout -b "$ECHIDNA_BRANCH" + git rm -f "$ECHIDNA_STUB" + git commit -S -m "$ECHIDNA_TITLE" + git push -u origin "$ECHIDNA_BRANCH" + ensure_open_pr "$ECHIDNA_REPO" "$ECHIDNA_BRANCH" "$ECHIDNA_TITLE" "$ECHIDNA_BODY" + ) + fi + fi +fi + # --------------------------------------------------------------------------- -# Section 4 — A3 (D24): Enable GitHub Pages on metadatastician/knot-knot -# (the 1 real interactive website in the #306 cohort) and retire the 33 -# docs-only Pages workflows + 3 banned jekyll*.yml workflows. +# Section 4 — A3 (D24): Update metadatastician/knot-knot pages.yml to publish +# www/public/, enable GitHub Pages on metadatastician/knot-knot, and retire +# the 33 docs-only Pages workflows + 3 banned jekyll*.yml workflows. # --------------------------------------------------------------------------- echo "" -echo "== Section 4 (A3 / D24): Enabling Pages on metadatastician/knot-knot & retiring 33 docs-only Pages workflows ==" +echo "== Section 4 (A3 / D24): Publishing metadatastician/knot-knot 3D knot lab & retiring 33 docs-only Pages workflows ==" + +KNOT_REPO="metadatastician/knot-knot" +KNOT_BRANCH="chore/d24-publish-knot-lab-pages" +KNOT_TITLE="chore(pages): publish www/public 3D knot laboratory in pages.yml (D24, standards#1234 A3)" +KNOT_BODY="Updates \`.github/workflows/pages.yml\` to overlay \`www/public/\` (\`knot-lab/index.html\`) and \`www/.well-known/\` into \`_site/\` with \`include-hidden-files: true\` per owner ruling **D24** (\`hyperpolymath/standards#1234\` item **A3**, \`hyperpolymath/standards#306\`)." + +if [ "$DRY_RUN" -eq 1 ]; then + echo " [dry-run] A3 update $KNOT_REPO .github/workflows/pages.yml to publish www/public/ and www/.well-known/" + A3_PENDING=$((A3_PENDING + 1)) +else + knot_dir="$WORK_TMP/a3-knot-knot" + gh repo clone "$KNOT_REPO" "$knot_dir" -- --depth=1 + if grep -q 'www/public' "$knot_dir/.github/workflows/pages.yml" 2>/dev/null; then + echo " $KNOT_REPO: .github/workflows/pages.yml already publishes www/public/" + else + A3_PENDING=$((A3_PENDING + 1)) + if git -C "$knot_dir" ls-remote --exit-code --heads origin "$KNOT_BRANCH" >/dev/null 2>&1; then + echo " $KNOT_REPO: remote branch $KNOT_BRANCH already exists" + ensure_open_pr "$KNOT_REPO" "$KNOT_BRANCH" "$KNOT_TITLE" "$KNOT_BODY" + else + ( + cd "$knot_dir" + git checkout -b "$KNOT_BRANCH" + awk ' + /cabal run casket-ssg -- build \.\.\/site \.\.\/_site/ { + print + print " if [ -d ../www/public ]; then cp -a ../www/public/. ../_site/; fi" + print " if [ -d ../www/.well-known ]; then cp -a ../www/.well-known ../_site/.well-known; fi" + next + } + /path: \x27_site\x27/ { + print + print " include-hidden-files: true" + next + } + { print } + ' .github/workflows/pages.yml > .github/workflows/pages.yml.new + mv .github/workflows/pages.yml.new .github/workflows/pages.yml + git add .github/workflows/pages.yml + if ! git diff --cached --quiet; then + git commit -S -m "$KNOT_TITLE" + git push -u origin "$KNOT_BRANCH" + ensure_open_pr "$KNOT_REPO" "$KNOT_BRANCH" "$KNOT_TITLE" "$KNOT_BODY" + fi + ) + fi + fi +fi knot_pages="$(gh api repos/metadatastician/knot-knot --jq '.has_pages')" if [ "$knot_pages" = "false" ]; then echo " metadatastician/knot-knot: enabling GitHub Pages (build_type=workflow)..." run_gh api -X POST "repos/metadatastician/knot-knot/pages" -f "build_type=workflow" >/dev/null + if [ "$DRY_RUN" -eq 1 ]; then + A3_PENDING=$((A3_PENDING + 1)) + fi else echo " metadatastician/knot-knot: GitHub Pages already enabled" fi @@ -240,15 +392,28 @@ echo " A3 docs-only / banned-Jekyll workflow targets: ${#A3_DELETE_REPOS[@]}" for entry in "${A3_DELETE_REPOS[@]}"; do r="${entry%%:*}" wf="${entry##*:}" + branch="chore/d24-retire-$(basename "$wf" .yml)" + pr_title="chore(ci): retire $(basename "$wf") per D24 (standards#1234 A3)" + pr_body="Retires \`$wf\` (docs-only Pages stub or banned Jekyll workflow; documentation goes to BerryWiki per **D295**) and prunes \`actions.lock\` per owner ruling **D24** (\`hyperpolymath/standards#1234\` item **A3**, \`hyperpolymath/standards#306\`)." if [ "$DRY_RUN" -eq 1 ]; then echo " [dry-run] A3 retire workflow: $r ($wf + prune-stale.sh)" + A3_PENDING=$((A3_PENDING + 1)) else - echo " A3 retiring $wf on $r..." + echo " A3 checking $wf on $r..." repo_dir="$WORK_TMP/a3-${r//\//_}-$(basename "$wf" .yml)" gh repo clone "$r" "$repo_dir" -- --depth=1 + if [ ! -e "$repo_dir/$wf" ]; then + echo " $r: $wf already absent on default branch" + continue + fi + A3_PENDING=$((A3_PENDING + 1)) + if git -C "$repo_dir" ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then + echo " $r: remote branch $branch already exists" + ensure_open_pr "$r" "$branch" "$pr_title" "$pr_body" + continue + fi ( cd "$repo_dir" - branch="chore/d24-retire-$(basename "$wf" .yml)" git checkout -b "$branch" git rm -f --ignore-unmatch "$wf" if [ -f .github/workflows/actions.lock ]; then @@ -258,9 +423,7 @@ for entry in "${A3_DELETE_REPOS[@]}"; do if ! git diff --cached --quiet; then git commit -S -m "chore(ci): retire $(basename "$wf") per D24 (standards#1234 A3, closes #306)" git push -u origin "$branch" - gh pr create --repo "$r" \ - --title "chore(ci): retire $(basename "$wf") per D24 (standards#1234 A3)" \ - --body "Retires \`$wf\` (docs-only Pages stub or banned Jekyll workflow; documentation goes to BerryWiki per **D295**) and prunes \`actions.lock\` per owner ruling **D24** (\`hyperpolymath/standards#1234\` item **A3**, \`hyperpolymath/standards#306\`)." + ensure_open_pr "$r" "$branch" "$pr_title" "$pr_body" fi ) fi @@ -271,22 +434,156 @@ done # before deleting .github/settings.yml across the 95 active repos. # --------------------------------------------------------------------------- echo "" -echo "== Section 5 (B / D332): Checking Probot Settings (app_id 1561) installation status ==" +echo "== Section 5 (B / D332): Checking Probot Settings (app_id 1561) installation & retiring .github/settings.yml (95 repos) ==" -probot_installed="$(gh api user/installations --jq '[.installations[]? | select(.app_id == 1561)] | length')" -if [ "$probot_installed" -gt 0 ]; then - echo " NOTE: Probot Settings (app_id 1561, installation 145157322) is still installed ($probot_installed installation)." - echo " Per D332 ('Uninstall the Probot Settings app, then delete .github/settings.yml across the estate')," - echo " uninstall https://github.com/settings/installations/145157322 first, then re-run this script." -else - echo " Probot Settings (app_id 1561) confirmed uninstalled." -fi +D332_SETTINGS_REPOS=( + "hyperpolymath/.github" + "hyperpolymath/affinescript-vite" + "hyperpolymath/anytype" + "hyperpolymath/Apodeixis.jl" + "hyperpolymath/atoma" + "hyperpolymath/autoformal-fidelity" + "hyperpolymath/bactrian" + "hyperpolymath/bofj-kitt" + "hyperpolymath/boj-server" + "hyperpolymath/cafescripto" + "hyperpolymath/cargo-zigbuild" + "hyperpolymath/cicd-squabbler" + "hyperpolymath/contractiles" + "hyperpolymath/CoprocessorRuntime.jl" + "hyperpolymath/czech-file-knife" + "hyperpolymath/dictask" + "hyperpolymath/echidna" + "hyperpolymath/ephapax" + "hyperpolymath/EpistemicTypes.jl" + "hyperpolymath/fraying-model-computational-testbed" + "hyperpolymath/haec" + "hyperpolymath/hermeneia" + "hyperpolymath/heron-polyfeed-reader" + "hyperpolymath/ideas-to-alphas" + "hyperpolymath/jaffascript" + "hyperpolymath/kategoria" + "hyperpolymath/kitchenspeak" + "hyperpolymath/krl" + "hyperpolymath/lithoglyph" + "hyperpolymath/llm-grace" + "hyperpolymath/lucidscript" + "hyperpolymath/natsci-studio" + "hyperpolymath/nesy-solver" + "hyperpolymath/nextgen-typing" + "hyperpolymath/occupancy-types" + "hyperpolymath/panoply" + "hyperpolymath/patch-bridge" + "hyperpolymath/proof-burrower" + "hyperpolymath/Protoctist.jl" + "hyperpolymath/proven" + "hyperpolymath/pseudoscript" + "hyperpolymath/quintessence" + "hyperpolymath/rattlescript" + "hyperpolymath/reasonably-good-token-vault" + "hyperpolymath/recon-silly-ation" + "hyperpolymath/ResidualEvidenceTypes.jl" + "hyperpolymath/resource-algebra" + "hyperpolymath/rsr-template-repo" + "hyperpolymath/scaffoldia" + "hyperpolymath/secret-types" + "hyperpolymath/sholes" + "hyperpolymath/snifs" + "hyperpolymath/somethings-fishy" + "hyperpolymath/stateful-artefacts" + "hyperpolymath/system-et" + "hyperpolymath/trigger" + "hyperpolymath/trope-checker" + "hyperpolymath/typed-wasm" + "hyperpolymath/unified-api-adapter" + "hyperpolymath/vocarium" + "hyperpolymath/xmlpull-zig" + "hyperpolymath/zig-boringtls" + "hyperpolymath/zig-dnssec" + "hyperpolymath/zig-icu-collation" + "hyperpolymath/zig-unorm" + "metadatastician/_pathroot" + "metadatastician/affinescript-libraries" + "metadatastician/berrywiki" + "metadatastician/bowtie-workbench" + "metadatastician/burble" + "metadatastician/cadastra" + "metadatastician/cerro-torre" + "metadatastician/cleave" + "metadatastician/consent-aware-web" + "metadatastician/first-post" + "metadatastician/gossamer" + "metadatastician/groove" + "metadatastician/hvn-dehallucinator" + "metadatastician/IDApTIK" + "metadatastician/knot-knot" + "metadatastician/kyq" + "metadatastician/lintel" + "metadatastician/marid" + "metadatastician/metadatastician-governance" + "metadatastician/odraz" + "metadatastician/paint-type" + "metadatastician/rokur" + "metadatastician/selur" + "metadatastician/sim-public-relations" + "metadatastician/spline" + "metadatastician/stapeln" + "metadatastician/svalinn" + "metadatastician/vordr" + "metadatastician/ZenodoDeposits.jl" + "metadatastician/ZeroInflatedCounts.jl" +) + +echo " D332 .github/settings.yml targets: ${#D332_SETTINGS_REPOS[@]}" +d332_branch="chore/d332-retire-settings-yml" +d332_title="chore(governance): delete .github/settings.yml after Probot Settings uninstall (D332, standards#1234)" +d332_body="Deletes \`.github/settings.yml\` now that the Probot Settings GitHub App (\`1561\`) is uninstalled; repository and organisation rulesets are the sole settings authority per owner ruling **D332** (\`hyperpolymath/standards#1234\`)." +for owner in hyperpolymath metadatastician; do + probot_installed="$(check_probot_installed "$owner")" + if [ "$probot_installed" -gt 0 ]; then + echo " $owner: Probot Settings (app_id 1561) is still installed or unverified; uninstall it before settings cleanup (D332)." + else + echo " $owner: Probot Settings (app_id 1561) confirmed uninstalled — retiring .github/settings.yml..." + fi + for r in "${D332_SETTINGS_REPOS[@]}"; do + [ "${r%%/*}" = "$owner" ] || continue + if [ "$probot_installed" -gt 0 ]; then + D332_PENDING=$((D332_PENDING + 1)) + continue + fi + if [ "$DRY_RUN" -eq 1 ]; then + echo " [dry-run] D332 retire .github/settings.yml: $r" + D332_PENDING=$((D332_PENDING + 1)) + else + repo_dir="$WORK_TMP/d332-${r//\//_}" + gh repo clone "$r" "$repo_dir" -- --depth=1 + if [ ! -e "$repo_dir/.github/settings.yml" ]; then + echo " $r: .github/settings.yml already absent on default branch" + continue + fi + D332_PENDING=$((D332_PENDING + 1)) + if git -C "$repo_dir" ls-remote --exit-code --heads origin "$d332_branch" >/dev/null 2>&1; then + echo " $r: remote branch $d332_branch already exists" + ensure_open_pr "$r" "$d332_branch" "$d332_title" "$d332_body" + continue + fi + ( + cd "$repo_dir" + git checkout -b "$d332_branch" + git rm -f .github/settings.yml + git commit -S -m "$d332_title" + git push -u origin "$d332_branch" + ensure_open_pr "$r" "$d332_branch" "$d332_title" "$d332_body" + ) + fi + done +done # --------------------------------------------------------------------------- -# Section 6 — Close-out comments on #306 and #1234 +# Section 6 — Gated close-out comments on #306 and #1234 # --------------------------------------------------------------------------- echo "" -echo "== Section 6: Closing #306 (absorbed into D24/A3) and #1234 ==" +echo "== Section 6: Verifying convergence before closing #306 (absorbed into D24/A3) and #1234 ==" # close_if_open — Close a GitHub issue on $REPO with an evidence comment if it is still open. # Takes an issue number and comment text. DRY_RUN=1 still fetches the issue state, @@ -304,23 +601,53 @@ close_if_open() { fi } -close_if_open 306 "Resolved under **D24** (**#1234 A3**). All 48 repositories in the #306 cohort have been classified in \`$REPORT_REL\`: -- **1 real interactive website (\`KEEP\` — enable Pages):** \`metadatastician/knot-knot\` (\`www/public/knot-lab/index.html\`, 31,990-byte Three.js 3D knot laboratory). -- **33 docs-only Pages workflows on Pages-disabled repos (\`DELETE\` workflow; docs go to BerryWiki per D295).** +# D329 requires deletion of the explore fork, not just its Pages workflow. +# Only an explicit HTTP 404 counts as absence; query failures stay pending. +if explore_response="$(gh api --include repos/hyperpolymath/explore 2>/dev/null)"; then + echo " D329: hyperpolymath/explore still exists." + A3_PENDING=$((A3_PENDING + 1)) +elif printf '%s\n' "$explore_response" | grep -Eq '^HTTP/[0-9.]+ 404([[:space:]]|$)'; then + echo " D329: hyperpolymath/explore is absent." +else + echo " D329: unable to verify deletion of hyperpolymath/explore." + A3_PENDING=$((A3_PENDING + 1)) +fi + +# D341 requires all three standards Pages workflows to be absent on the +# default branch. A failed contents query stops closure under set -e. +d341_pending="$(gh api "repos/$REPO/contents/.github/workflows" --jq '[.[] | select(.name == "casket-pages.yml" or .name == "pages-archive.yml" or .name == "pages.yml")] | length')" +A3_PENDING=$((A3_PENDING + d341_pending)) +echo " D341: $d341_pending standards Pages workflow(s) remain." + +TOTAL_PENDING=$((A1_PENDING + A5_PENDING + A2_PENDING + A3_PENDING + A6_PENDING + D332_PENDING)) +echo " Pending convergence items: total=$TOTAL_PENDING (A1=$A1_PENDING, A5=$A5_PENDING, A2=$A2_PENDING, A3=$A3_PENDING, A6=$A6_PENDING, D332=$D332_PENDING)" + +if [ "$A3_PENDING" -eq 0 ]; then + close_if_open 306 "Resolved under **D24** (**#1234 A3**). All 48 repositories in the #306 cohort have been classified in \`$REPORT_REL\` and converged: +- **1 real interactive website (\`KEEP\` — Pages enabled & \`www/public/\` published):** \`metadatastician/knot-knot\` (\`www/public/knot-lab/index.html\`, 31,990-byte Three.js 3D knot laboratory). +- **33 docs-only Pages workflows on Pages-disabled repos (\`DELETE\` workflow merged; docs go to BerryWiki per D295).** - **3 repositories carrying banned \`jekyll*.yml\` workflows (\`DELETE\` workflow / fork).** -- **1 repository (\`hyperpolymath/standards\`)** tracked under D341. +- **D329:** \`hyperpolymath/explore\` confirmed absent. +- **D341:** All 3 Pages workflows (\`casket-pages.yml\`, \`pages-archive.yml\`, \`pages.yml\`) confirmed absent from \`hyperpolymath/standards\`. - **10 repositories** already retired in the 2026-05-30 sweep and \`jtv-lang#100\`." +else + echo " #306 kept open until all A3 workflow deletions, knot-knot Pages publication, D329, and D341 converge ($A3_PENDING pending)." +fi -close_if_open 1234 "All items on **#1234** (**B / D304**, **B / D332**, and execution queue **A1–A7**) are completed and recorded in \`$REPORT_REL\` and \`scripts/triage-2026-10-23-issue-1234-apply.sh\`: -- **B / D304:** Ruled in comment 2 (Cloudflare R2 + client-side \`age\` encryption); implemented in \`.github/workflows/mirror-reusable.yml\` (\`mirror-r2-bundle\`) and \`docs/MIRROR-HOST-VERIFICATION.adoc\`. -- **B / D332:** Ruled in comment 2 (uninstall Probot Settings \`1561\`, then delete \`.github/settings.yml\` across the 95 active repos). -- **A1 (D16):** Full ruleset \`bypass_actors\` census complete (\`0\` in \`metadatastician\`, \`10\` in \`hyperpolymath\`: 4 active, 6 disabled); convergence scripted in Section 1. -- **A2 (D14):** Full 24-repo ClusterFuzzLite census complete (\`10\` KEEP real Rust/Zig targets, \`14\` DELETE stubs). -- **A3 (D24):** Full 48-repo Pages classification complete (\`1\` KEEP real site \`metadatastician/knot-knot\`, \`33\` DELETE docs-only workflows, \`3\` banned \`jekyll*.yml\`, \`1\` \`standards\`, \`10\` already retired). +if [ "$TOTAL_PENDING" -eq 0 ]; then + close_if_open 1234 "Execution results for **#1234** (**B / D304**, **B / D332**, and execution queue **A1–A7**) are recorded in \`$REPORT_REL\` and \`scripts/triage-2026-10-23-issue-1234-apply.sh\`. A6 General registrations remain blocked; the local check passed: +- **B / D304:** Ruled in comment 2 (Cloudflare R2 + client-side \`age\` encryption); implemented in \`.github/workflows/mirror-reusable.yml\` (\`mirror-r2-bundle\`), wired in \`.github/workflows/mirror.yml\`, and documented in \`docs/MIRROR-HOST-VERIFICATION.adoc\`. +- **B / D332:** Ruled in comment 2 (Probot Settings \`1561\` confirmed uninstalled and \`.github/settings.yml\` deleted across the 95 active repos). +- **A1 (D16):** Full ruleset \`bypass_actors\` census complete (\`0\` in \`metadatastician\`, \`10\` in \`hyperpolymath\`: 4 active, 6 disabled) and converged in Section 1. +- **A2 (D14):** Full 24-repo ClusterFuzzLite census complete (\`10\` KEEP real Rust/Zig targets, \`14\` DELETE stubs + \`echidna\` stub target removed). +- **A3 (D24):** Full 48-repo Pages classification complete and converged (\`1\` KEEP real site \`metadatastician/knot-knot\`, \`33\` DELETE docs-only workflows, \`3\` banned \`jekyll*.yml\`, \`1\` \`standards\`, \`10\` already retired). - **A4 (D97):** Full 357-repo transfer-candidate cost census across 6 tiers + follow-up ruling row **D355** posted in \`$REPORT_REL\`. -- **A5 (D298):** Precondition blast radius verified (\`0\` canon failures across 29 bypassed pushes; \`7\` secret-scanning failures on repos without secret scanning); \`?dry-run=true\` + live \`PUT\` reverting \`23868655\` and \`23868851\` to canon (\`deletion\` + \`non_fast_forward\`, \`bypass_actors: []\`) scripted in Section 2. -- **A6 (D67):** Local \`RegistryCI.jl\` \`AutoMerge\` check complete for all 9 Marid Tier-1 packages (\`16/16\` pass at tag \`fd139f763a306a7e2eca60d96ea1a1a9f77c1e71\`), with all 4 General registration blockers and verbatim guideline texts recorded in \`$REPORT_REL\`. +- **A5 (D298):** Precondition blast radius verified (\`0\` canon failures across 29 bypassed pushes; \`7\` secret-scanning failures on repos without secret scanning); \`?dry-run=true\` + live \`PUT\` reverting \`23868655\` and \`23868851\` to canon (\`deletion\` + \`non_fast_forward\`, \`bypass_actors: []\`) verified in Section 2. +- **A6 (D67) — pending:** Local \`RegistryCI.jl\` \`AutoMerge\` check passed for all 9 Marid Tier-1 packages (\`16/16\` at tag \`fd139f763a306a7e2eca60d96ea1a1a9f77c1e71\`); General registrations remain blocked, with all 4 blockers and verbatim guideline texts recorded in \`$REPORT_REL\`. - **A7 (D330):** Closed in comment 2 (no GitLab copy of \`canonical-ums\` exists)." +else + echo " #1234 kept open until all pending convergence items finish (total=$TOTAL_PENDING: A1=$A1_PENDING, A5=$A5_PENDING, A2=$A2_PENDING, A3=$A3_PENDING, A6=$A6_PENDING, D332=$D332_PENDING)." +fi echo "" echo "Done. (DRY_RUN=$DRY_RUN)"