diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 41699b6..cf9f27e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -9,6 +9,10 @@ body: value: | Please report what you **observed**, not what you inferred. A command and its actual output is worth more than a description of the problem. + + All fields below use the same plain text area and are **optional**: + answer what is relevant, skip what is not. The only hard gate is the + attestation pair at the end. - type: textarea id: what-happened attributes: @@ -17,29 +21,28 @@ body: placeholder: | $ just verify error: ... - render: shell validations: - required: true + required: false - type: textarea id: expected attributes: label: What you expected instead validations: - required: true + required: false - type: textarea id: repro attributes: label: Minimal reproduction description: The shortest sequence that reproduces it from a clean checkout. validations: - required: true + required: false - type: input id: version attributes: label: Version / commit description: Output of `git rev-parse --short HEAD`. validations: - required: true + required: false - type: textarea id: environment attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 068c5c2..3a5910d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -4,19 +4,24 @@ name: Feature request description: Propose a capability this project does not yet have. labels: ["enhancement", "needs-triage"] body: + - type: markdown + attributes: + value: | + All fields below are **optional** — answer what is relevant, skip the + rest. A short, concrete problem statement beats a long speculative one. - type: textarea id: problem attributes: label: The problem description: What are you unable to do today? Describe the situation, not the solution. validations: - required: true + required: false - type: textarea id: proposal attributes: label: Proposed change validations: - required: true + required: false - type: textarea id: alternatives attributes: @@ -34,4 +39,4 @@ body: - Template-wide — would propagate to minted repos - Not sure validations: - required: true + required: false diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..7d1d3d4 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,22 @@ + +# Support + +* **Bug reports and feature requests** — open an + [issue](https://github.com/hyperpolymath/aerie/issues/new/choose). All + form fields are optional; the only hard gate is the attestation pair. +* **Questions and how-do-I** — use + [Discussions](https://github.com/hyperpolymath/aerie/discussions) + (Q&A category). Questions as issues are triaged there. +* **Security vulnerabilities** — do **not** open a public issue. Report + privately via a + [security advisory](https://github.com/hyperpolymath/aerie/security/advisories/new); + see [SECURITY.md](../SECURITY.md) for scope and response times. +* **Setup trouble** — `./setup.sh` is the zero-prerequisite path (needs + only git + curl); `just doctor` diagnoses the toolchain. Docs: + [docs/SETUP.adoc](../docs/SETUP.adoc). + +Response times are best-effort; see +[GOVERNANCE.md](../GOVERNANCE.md) for maintainer responsibilities. diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 53195cf..201a99f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -30,6 +30,12 @@ https://github.com/hyperpolymath/aerie/pulls[hyperpolymath/aerie]. * `tests/idris2/` — proven-tests-format Idris2 suite scaffold. * `docs/REPO-SETTINGS.adoc`, `docs/wiki-plan.adoc`, `docs/reports/quality/REMEDIATION-PLAN.md`, `docs/SETUP.adoc`. +* `docs/ESTATE-PROPAGATION.adoc` — owner runbook: rolling the fixed + issue forms and check-suite health verification across the + hyperpolymath and metadatastician estates (layers: estate `.github` + defaults, per-repo PRs, template-source fix, startup-failure sweep). +* `scripts/propagate-github-templates.sh` — gh-driven per-repo PR + rollout of the canonical issue forms across both estates. * Conventional dotfiles: `.envrc`, `.mailmap`, `.gitleaksignore`, `.cicd-hygiene-allow`, `.hypatia-ignore`, `.tool-versions`. @@ -43,6 +49,20 @@ https://github.com/hyperpolymath/aerie/pulls[hyperpolymath/aerie]. * `Justfile` — canonical verbs added: `setup`, `build`, `test`, `bench`, `format`, `clean`, `release`, `install`, `guix-shell`, `container-build`, `man`, `cookbook`. +* `setup.sh` — rewritten as the guaranteed zero-prerequisite path + (issue #91): with only bash + curl it installs `just` and the pinned + Zig toolchain (checksum-verified from the ziglang.org release index) + into a user-local dir — no sudo — then runs `just doctor`. Safe to + re-run; cargo/brew/mise remain fallbacks, not requirements. +* `Justfile` — `setup` no longer gates `heal` behind a passing `doctor` + (a failing doctor would abort before the repair could run); both + `setup` and `heal` delegate to `./setup.sh`; `doctor` now requires + Zig 0.15.2 (matching `build.zig`/mise pins; was 0.13) and lists podman + as an optional warning. +* `README.md`, `QUICKSTART-DEV.adoc`, `QUICKSTART-USER.adoc`, + `docs/SETUP.adoc` — the zero-prerequisite route (Approach 3) is now + named explicitly and recommended first; the AI-assisted prompt starts + from `./setup.sh` instead of assuming `just` is already present. * `CHANGELOG.md` → `CHANGELOG.adoc` (estate documentation format). * `ARCHITECTURE.md` — rewritten from scaffold filler to the actual architecture law and layout. @@ -57,6 +77,13 @@ https://github.com/hyperpolymath/aerie/pulls[hyperpolymath/aerie]. (`src/api/v/`), not "zig"; the comment contradicted the law it quoted. * `.claude/CLAUDE.md` — "Never Zig, Rust, or C" self-contradiction (Zig IS the API language) and the "Zig (src/api/v/)" mislabel corrected. +* `.github/ISSUE_TEMPLATE/bug_report.yml` (issue #92) — the + "What happened" field no longer renders differently from its + neighbours (`render: shell` removed; all free-text fields are the same + plain textarea), and no content field is required any more — only the + two attestation checkboxes still gate submission. + `feature_request.yml` relaxed the same way, so irrelevant fields can + simply be left blank on both forms. === Added diff --git a/Justfile b/Justfile index a1820b8..d404cb9 100644 --- a/Justfile +++ b/Justfile @@ -6,9 +6,12 @@ import? "contractile.just" # CANONICAL VERBS (estate Justfile specification) # ═══════════════════════════════════════════════════════════════════ -# First-time setup: toolchain check + repair -setup: doctor - just heal +# First-time setup: install anything missing, then verify the toolchain. +# Delegates to the zero-prerequisite bootstrap (needs only bash + curl); +# deliberately NOT 'doctor then heal' — a failing doctor would abort the +# recipe before heal ever ran. +setup: + @./setup.sh # Build the gateway (debug) build: @@ -170,9 +173,9 @@ doctor: FAIL=$((FAIL + 1)) fi } - check "just" just "1.25" - check "git" git "2.40" - check "Zig" zig "0.13" + check "just" just "1.25" + check "git" git "2.40" + check "Zig" zig "0.15.2" # Optional tools if command -v panic-attack >/dev/null 2>&1; then echo " [OK] panic-attack — available" @@ -181,6 +184,13 @@ doctor: echo " [WARN] panic-attack — not found (pre-commit scanner)" WARN=$((WARN + 1)) fi + if command -v podman >/dev/null 2>&1; then + echo " [OK] podman — available (containerised stack)" + PASS=$((PASS + 1)) + else + echo " [WARN] podman — not found (only needed for 'podman compose up')" + WARN=$((WARN + 1)) + fi echo "" echo " Result: $PASS passed, $FAIL failed, $WARN warnings" if [ "$FAIL" -gt 0 ]; then @@ -190,18 +200,13 @@ doctor: echo " All required tools present." # Attempt to automatically install missing tools +# Delegates to the same zero-prerequisite bootstrap as first-time setup: +# curl-only downloads of `just` (via setup.sh) and the pinned Zig toolchain, +# followed by `just doctor` to verify. (Note: heal can only run when just +# is already present — fresh clones should run ./setup.sh directly.) heal: - #!/usr/bin/env bash - echo "═══════════════════════════════════════════════════" - echo " Aerie Heal — Automatic Tool Installation" - echo "═══════════════════════════════════════════════════" - echo "" - if ! command -v just >/dev/null 2>&1; then - echo "Installing just..." - cargo install just 2>/dev/null || echo "Install just from https://just.systems" - fi - echo "" - echo "Heal complete. Run 'just doctor' to verify." + @echo "=== Heal === (delegating to ./setup.sh — the zero-prerequisite bootstrap)" + @./setup.sh # Guided tour of the project structure and key concepts tour: diff --git a/QUICKSTART-DEV.adoc b/QUICKSTART-DEV.adoc index 8a321f9..133a94a 100644 --- a/QUICKSTART-DEV.adoc +++ b/QUICKSTART-DEV.adoc @@ -5,9 +5,8 @@ Clone, build, test, contribute. == Prerequisites -* Git 2.40+ -* just (command runner) -* See `just doctor` output for language-specific requirements +* Git 2.40+ and curl — nothing else; `./setup.sh` installs the rest +* `just doctor` output tells you the language-specific state afterwards == Setup @@ -15,8 +14,10 @@ Clone, build, test, contribute. ---- git clone https://github.com/hyperpolymath/aerie cd aerie -just doctor # verify toolchain -just heal # auto-install missing tools +./setup.sh # zero-prerequisite: installs just + pinned Zig (curl only), + # then runs just doctor. Safe to re-run. +just doctor # re-verify the toolchain any time +just heal # re-attempt automatic repair of anything doctor flags ---- == Development Workflow diff --git a/QUICKSTART-USER.adoc b/QUICKSTART-USER.adoc index 6b8ecce..2f84fe7 100644 --- a/QUICKSTART-USER.adoc +++ b/QUICKSTART-USER.adoc @@ -5,8 +5,8 @@ Get up and running in 60 seconds. == Prerequisites -* Git 2.40+ -* just (command runner) — https://just.systems +* Git 2.40+ and curl — nothing else; `./setup.sh` installs `just` and + the rest of the toolchain for you (no sudo). == Install @@ -14,8 +14,9 @@ Get up and running in 60 seconds. ---- git clone https://github.com/hyperpolymath/aerie cd aerie -just doctor # check toolchain -just heal # auto-install missing tools +./setup.sh # installs just + pinned Zig if missing, then checks the + # toolchain with just doctor +just heal # re-attempt automatic repair of anything doctor flagged ---- == First Run diff --git a/README.md b/README.md index c746063..fde56bd 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,10 @@ Four ways in — full detail in [`docs/SETUP.adoc`](docs/SETUP.adoc): repository. Use the Justfile as the single entry point.` 2. **Raw** — `git clone` → install Zig 0.15.2+, just, and Podman → `zig build -Doptimize=ReleaseSafe` → `podman compose -f compose.yml up`. -3. **Just** — `./setup.sh` (installs just if missing), then - `just doctor && just build && just test`. +3. **Just** (zero-prerequisite — start here) — `./setup.sh` needs only + git + curl: it installs `just` and the pinned Zig toolchain itself if + missing, then verifies with `just doctor`. Continue with + `just build && just test`. 4. **Launcher** — `./aerie-launcher.sh` (standards-compliant, cross-platform, launch-scaffolder generated). diff --git a/docs/ESTATE-PROPAGATION.adoc b/docs/ESTATE-PROPAGATION.adoc new file mode 100644 index 0000000..5ac9885 --- /dev/null +++ b/docs/ESTATE-PROPAGATION.adoc @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Propagating repo checks & issue templates across the estates +:toc: macro +:toclevels: 2 + +_Owner runbook: how to take what was fixed on `hyperpolymath/aerie` +(issues #91 and #92 — optional, consistently-typed issue forms and a +guaranteed zero-prerequisite setup path) and roll it out across the +**hyperpolymath** (user account) and **metadatastician** (organisation) +estates — and how to confirm the check suites on those repos actually +run rather than dying at startup._ + +Measured and written 2026-09-25 against the live GitHub state. + +toc::[] + +== 0. What "healthy" means (the target state) + +A repo in either estate is healthy when: + +. It carries the canonical issue forms from `.github/ISSUE_TEMPLATE/` + in this repo (post-#92): all free-text fields are the same plain + `textarea` type (no `render:` split) and **no content field is + required**; only the two attestation checkboxes on the bug form gate + submission. Repos without their own templates may instead inherit + the estate defaults (layer A). +. It has a zero-prerequisite setup path: `./setup.sh` bootstraps `just` + (and the pinned toolchain) using only bash + curl, then runs + `just doctor` (post-#91 pattern in this repo). +. Its workflows **schedule** when they should. The known estate failure + mode is workflows dying with `startup_failure` / zero jobs — green in + no list, red in the run list, and **not enforcing anything** (see + <>). +. The branch gate is active: ruleset `Optimus-Branch` enabled with + `required_status_checks` naming the GATE workflow set (per + `docs/REPO-SETTINGS.adoc` §Delta-1 and `hyperpolymath/standards` + `config/settings/repo.json`). + +== 1. Layer A — estate default health files (`.github` repos) + +Both estates already own the special `.github` repo +(`hyperpolymath/.github`, `metadatastician/.github`; verified +2026-09-25). Default community-health files placed there apply to every +repo in the estate that does **not** define its own. + +Copy the fixed forms from this repo into each estate's `.github` repo, +replacing template mint-placeholders with the estate's real values: + +[source,sh] +---- +for EST in hyperpolymath metadatastician; do + gh repo view "$EST/.github" >/dev/null # exists for both today + git clone --depth 1 "https://github.com/$EST/.github" "/tmp/$EST.github" + mkdir -p "/tmp/$EST.github/ISSUE_TEMPLATE" + for f in bug_report.yml feature_request.yml config.yml; do + sed -e "s/{{FORGE}}/github.com/g" \ + -e "s/{{OWNER}}/$EST/g" \ + .github/ISSUE_TEMPLATE/"$f" > "/tmp/$EST.github/ISSUE_TEMPLATE/$f" + done + # config.yml keeps {{REPO}} intentionally? NO — for estate defaults, + # relative admonition headers suffice; replace the security-advisory + # and discussions links with literal estate URLs, e.g.: + # https://github.com//security/advisories/new + # GitHub expands contact_links relative to the *viewed* repo for + # enterprise templates only; for personal/org .github repos, leave a + # generic SECURITY.md pointer instead of a {{REPO}} URL. + ( cd "/tmp/$EST.github" && git add -A && \ + git commit -m "chore(github): estate default issue forms (aerie #92 class)" && \ + git push ) +done +---- + +Notes: + +* Estate defaults are a **fallback**, not an override: any repo with its + own `.github/ISSUE_TEMPLATE` ignores them → those repos need layer B. +* Review `contact_links` in `config.yml` before pushing — mint + placeholders (`{{FORGE}}/{{OWNER}}/{{REPO}}`) must not ship to the + estate defaults. + +== 2. Layer B — per-repo PRs for repos with their own templates + +`scripts/propagate-github-templates.sh` (in this repo) finds every +non-fork, non-archived repo in both estates, checks for an existing +`.github/ISSUE_TEMPLATE`, and raises a per-repo PR syncing the canonical +forms (placeholders rendered per repo, conventional-commit subject, +`conformance` label best-effort). + +[source,sh] +---- +# 1. survey (read-only; full estates take several minutes — ~1 API call/repo) +./scripts/propagate-github-templates.sh --dry-run +./scripts/propagate-github-templates.sh --dry-run --only # spot-check + +# 2. apply — one PR per repo that already carries its own templates +./scripts/propagate-github-templates.sh --apply + +# 3. optional: also PR repos that would otherwise inherit estate defaults +./scripts/propagate-github-templates.sh --apply --all +---- + +Then, per repo: review diff (should be templates-only), confirm the +GATE workflows on the PR **scheduled** (they must appear in the Checks +tab — an empty/absent check list is the failure signature, see +<>), squash-merge, delete branch. + +== 3. Layer C — fix the template source (future mints) + +So the next minted repo is born correct rather than needing layer B: + +* Update `rsr-template-repo` (the estate template the `mise.toml` + discipline comment and `docs/REPO-SETTINGS.adoc` canon point at) with + the same `.github/ISSUE_TEMPLATE/*`, the `./setup.sh` zero-prerequisite + bootstrap, and the Justfile `setup`/`heal` wiring from this repo. +* Refresh the `launch-scaffolder` spec inputs (`aerie.launcher.a2ml` + pattern) where they capture onboarding text, so generated launchers + reference `./setup.sh` first. +* Where `instant-sync.yml`/the standards sync can push template + refreshes to already-minted repos, cut a sync release; everything it + misses is covered by layer B. + +== 4. Known live gaps on *this* repo (do these first) + +Measured 2026-09-25 (see also `docs/REPO-SETTINGS.adoc` §Delta-9, same +signature on 2026-09-23): + +* **Label Triage `startup_failure`** on the `issues` event — it fired + (and died instantly, zero jobs) when #91 and #92 were opened. The + estate gate requires the workflow path to be listed in the repo's + `.github/workflows/actions.lock`; re-lock per + `hyperpolymath/standards` docs, or re-check the Actions allow-list + (next bullet). Diagnosis requires owner token — the audit bot gets + 403 on the Actions permissions endpoints. +* **Red on main**: `Governance` (24s fail), `Mirror to Git Forges`, + `Secret Scanner` runs failing on the last main push. Re-run/read with: + `gh run list --branch main --limit 10`. +* **Owner-only settings** (`docs/REPO-SETTINGS.adoc` delta list): enable + `Optimus-Branch` ruleset + populate its `required_status_checks` + contexts; set `allow_merge_commit=false` (squash-only canon); verify + Actions `allowed_actions=selected` with non-empty `patterns_allowed` + per `config/settings/actions-allowlist.json`. + +== 5. [[s5]]The silent failure mode: checks that never schedule + +The single most important estate-wide check: an **empty Actions +allow-list** (`allowed_actions: all` is fine; `selected` with empty +`patterns_allowed` is fatal) makes *every* workflow — including the PR +gates — die at startup with `jobs.total_count == 0` and no check run. +Gates look quiet while nothing enforces. Audit both estates: + +[source,sh] +---- +for EST in hyperpolymath metadatastician; do + KIND=$(gh api users/$EST --jq .type) + [ "$KIND" = Organization ] && REL="orgs/$EST" || REL="users/$EST" + gh api --paginate "$REL/repos?per_page=100" --jq '.[].full_name' | + while read -r R; do + P=$(gh api "repos/$R/actions/permissions" 2>/dev/null \ + --jq '[.enabled, .allowed_actions] | @tsv') || P="unreadable" + N=$(gh api "repos/$R/actions/permissions/selected-actions" \ + --jq '.patterns_allowed | length' 2>/dev/null || echo "-") + printf '%-55s perms=%-18s patterns=%s\n' "$R" "${P:-unreadable}" "$N" + done +done +---- + +* `patterns=0` ⇒ fix: re-apply the estate allow-list + (`gh api -X PUT repos/$R/actions/permissions ... -f allowed_actions=selected`, + then PUT `selected-actions` with the canon patterns; commands in + `docs/REPO-SETTINGS.adoc` §3). +* `unreadable` ⇒ the token used lacks admin on that repo; run with the + estate owner token. +* Follow with: last-3-runs green check + (`gh run list -R "$R" --branch main --limit 3`), and a test issue to + watch Label Triage complete. + +== 6. Optional: a weekly estate sweep so this never decays + +Add this to **each** estate's `.github` repo +(e.g. `.github/workflows/estate-health-sweep.yml`); it re-audits the +allow-list + last-run health and files/updates a `conformance` tracking +issue in the estate governance repo on regression: + +[source,yaml] +---- +name: Estate health sweep +on: + schedule: [{ cron: "0 6 * * 1" }] + workflow_dispatch: +permissions: { contents: read, issues: write } +jobs: + sweep: + runs-on: ubuntu-latest + steps: + - name: Audit every repo and file a conformance issue on regression + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + EST="${{ github.repository_owner }}" + gh api users/$EST --jq .type | grep -q Organization \ + && REL="orgs/$EST" || REL="users/$EST" + BAD="" + gh api --paginate "$REL/repos?per_page=100" \ + --jq '.[] | select(.archived|not) | .full_name' | + while read -r R; do + N=$(gh api "repos/$R/actions/permissions/selected-actions" \ + --jq '.patterns_allowed | length' 2>/dev/null || echo "-") + [ "$N" = "0" ] && BAD="$BAD- \`$R\`: empty actions allow-list (checks cannot schedule)\n" + gh api "repos/$R/contents/.github/ISSUE_TEMPLATE" >/dev/null 2>&1 \ + || BAD="$BAD- \`$R\`: no own issue templates (inherits estate defaults — confirm intended)\n" + done + if [ -n "$BAD" ]; then + printf '## Estate health sweep %s\n\n%b' "$(date -I)" "$BAD" \ + | gh issue create --title "Estate health sweep: attention needed" \ + --label conformance --body-file - || true + fi +---- + +(GITHUB_TOKEN in the `.github` repo can only see that repo — for a real +cross-estate sweep, run it from the governance repos +(`metadatastician-governance`, and the hyperpolymath equivalent) with a +fine-grained PAT or GitHub App token as `ESTATE_TOKEN`.) + +== 7. Rollout order & acceptance checklist + +. [ ] Fix known gaps on aerie (section 4). +. [ ] Layer A: estate default forms into `hyperpolymath/.github` and + `metadatastician/.github`. +. [ ] Layer B: `--dry-run` survey, then `--apply`; merge PRs on the + high-traffic repos first (aerie pattern as canonical diff). +. [ ] Layer C: update `rsr-template-repo` + launch-scaffolder so future + mints carry the fixed forms and `./setup.sh` bootstrap. +. [ ] Section 5 sweep: zero `patterns=0` repos; GATE workflows schedule + on a test PR per estate. +. [ ] Section 6 sweep workflow live in both governance repos. +. [ ] Re-run the sweep a week later; expect zero new findings. + +== References + +* `docs/REPO-SETTINGS.adoc` — aerie settings vs. estate canon, owner + command blocks (rulesets, allow-list, topics, environments). +* `hyperpolymath/standards` — `config/settings/repo.json`, + `config/settings/actions-allowlist.json`, `config/rulesets/base.json`. +* Aerie issues #91 (zero-prerequisite setup) and #92 (issue-form + fields): fixed in this repo; this document is the estate-wide follow-on. diff --git a/docs/README.md b/docs/README.md index 7f0a7e0..d280b5f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,10 +6,11 @@ Setup (four approaches), man pages, reports (quality/security), the wiki plan, a | Entry | Purpose | |-------|---------| -| `SETUP.adoc` | four-approach setup | +| `SETUP.adoc` | four-approach setup (Approach 3 = zero-prerequisite) | | `man/` | groff man pages | | `reports/` | audit and remediation reports | | `wiki-plan.adoc` | wiki development plan (berrywiki pattern) | | `REPO-SETTINGS.adoc` | measured vs canonical GitHub settings + apply commands | +| `ESTATE-PROPAGATION.adoc` | owner runbook: propagate issue forms + check-suite health across the hyperpolymath and metadatastician estates | See [`aerie_chora.deed`](../aerie_chora.deed) for the canonical machine-readable description of this layer. diff --git a/docs/SETUP.adoc b/docs/SETUP.adoc index 172b589..2a777bc 100644 --- a/docs/SETUP.adoc +++ b/docs/SETUP.adoc @@ -7,12 +7,28 @@ Everything you need to build, test, and run Aerie, four ways. The Justfile is the single source of truth for commands; the other three approaches are routes into it. -== Prerequisites (all approaches) +[IMPORTANT] +==== +**Approach 3 (`./setup.sh`) is the zero-prerequisite path.** It works +immediately after `git clone` on a machine with only core OS tools +(bash, curl, git): it installs `just` and the pinned Zig toolchain +itself, into a user-local directory, then runs `just doctor`. The other +approaches list their prerequisites below — if you are not sure which to +take, take Approach 3. +==== + +== Prerequisites (per approach) + +These are installed for you by `./setup.sh` / `just heal` when missing; +list them here only matters if you take the raw manual route: * **Zig 0.15.2+** — the gateway and FFI are Zig (estate law: API = Zig, - FFI = Zig, ABI = Idris2). -* **just** — the estate task runner (no Makefiles). -* **Podman** (or any OCI runtime) — for the containerised stack. + FFI = Zig, ABI = Idris2). Pinned in `.tool-versions`; `./setup.sh` + installs that exact version. +* **just** — the estate task runner (no Makefiles). Installed by + `./setup.sh` when missing. +* **Podman** (or any OCI runtime) — for the containerised stack only + (not needed for build/test). * Optional: Rust (tracked-drift crate only — do not extend), Idris2 (ABI + proven-tests suite), Guix (reproducible environment). @@ -25,10 +41,12 @@ Give your agent exactly this prompt: ---- Read aerie_chora.deed, .claude/CLAUDE.md, and docs/SETUP.adoc in this -repository, then set it up: install missing tools with 'just heal', -verify the toolchain with 'just doctor', build with 'just build', and -run the test suite with 'just test'. Do not build, extend, or migrate -to src/api/rust (tracked drift). Report any gate that fails. +repository, then set it up: run ./setup.sh first (it installs just and +the pinned Zig toolchain using only curl), verify the toolchain with +'just doctor', heal whatever it reports with 'just heal', build with +'just build', and run the test suite with 'just test'. Do not build, +extend, or migrate to src/api/rust (tracked drift). Report any gate +that fails. ---- The deed (`aerie_chora.deed`) is the universal AI entry point: it names @@ -55,12 +73,16 @@ podman compose -f compose.yml up # 5. Verify: gateway answers on :4000 (REST+GraphQL) and :4001 (gRPC) ---- -== Approach 3 — Just +== Approach 3 — Just (zero-prerequisite: only bash, curl, git needed) ---- -./setup.sh # platform detection + installs just if missing +./setup.sh # platform detection; installs just + pinned Zig if + # missing (curl-only download, no sudo), then runs + # just doctor. Safe to re-run: present tools are + # left untouched. just doctor # toolchain health check (fails with a repair hint) just heal # attempts automatic repair of missing tools + # (same bootstrap as ./setup.sh, then re-verifies) just build # zig build (debug) just build-release # zig build -Doptimize=ReleaseSafe just test # in-tree suites (zig, idris2, rust) + submodule suites diff --git a/scripts/propagate-github-templates.sh b/scripts/propagate-github-templates.sh new file mode 100755 index 0000000..55e87ca --- /dev/null +++ b/scripts/propagate-github-templates.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# propagate-github-templates.sh — roll the canonical GitHub issue forms +# (and optionally the repo check-suite files) out across an estate as +# per-repo pull requests. +# +# Default owners: hyperpolymath (user account) and metadatastician (org). +# +# ./scripts/propagate-github-templates.sh --dry-run # survey only +# ./scripts/propagate-github-templates.sh --only aerie # single repo +# ./scripts/propagate-github-templates.sh --apply # raise the PRs +# +# Repos that do NOT carry their own .github/ISSUE_TEMPLATE are skipped by +# default: they inherit the estate defaults from the owner's .github repo +# (see docs/ESTATE-PROPAGATION.adoc, layer A). Pass --all to also raise +# PRs on those (making the forms explicit + frozen in-repo). +# +# Template placeholders {{FORGE}}, {{OWNER}}, {{REPO}} found in the copied +# files are substituted per target repo (FORGE defaults to github.com). +# +# Prereqs: gh (authenticated), git. Safe to re-run: an existing open PR +# branch is force-refreshed rather than duplicated. + +set -euo pipefail + +OWNERS="${ESTATE_OWNERS:-hyperpolymath metadatastician}" +TEMPLATE_DIR="${TEMPLATE_DIR:-$(cd "$(dirname "$0")/.." && pwd)/.github/ISSUE_TEMPLATE}" +SUPPORT_FILE="${SUPPORT_FILE:-}" # optional .github/SUPPORT.md source +FORGE="${FORGE:-github.com}" +BRANCH="chore/sync-issue-forms" +APPLY=0; DRYRUN=0; ALL=0 +ONLY="" +SKIP_FORKS=1; SKIP_ARCHIVED=1 + +usage() { sed -n '2,28p' "$0"; exit "${1:-0}"; } + +while [ $# -gt 0 ]; do + case "$1" in + --owners) OWNERS="$2"; shift 2 ;; + --templates) TEMPLATE_DIR="$2"; shift 2 ;; + --support-file) SUPPORT_FILE="$2"; shift 2 ;; + --forge) FORGE="$2"; shift 2 ;; + --only) ONLY="$2"; shift 2 ;; + --apply) APPLY=1; shift ;; + --dry-run) DRYRUN=1; shift ;; + --all) ALL=1; shift ;; + --include-forks) SKIP_FORKS=0; shift ;; + --include-archived) SKIP_ARCHIVED=0; shift ;; + -h|--help) usage 0 ;; + *) echo "unknown flag: $1" >&2; usage 1 ;; + esac +done + +command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI required"; exit 1; } +command -v git >/dev/null 2>&1 || { echo "ERROR: git required"; exit 1; } +[ -d "$TEMPLATE_DIR" ] || { echo "ERROR: template dir not found: $TEMPLATE_DIR"; exit 1; } +if [ "$APPLY" -eq 0 ] && [ "$DRYRUN" -eq 0 ]; then + echo "(neither --apply nor --dry-run: defaulting to --dry-run)" + DRYRUN=1 +fi + +TMPROOT="$(mktemp -d)" +trap 'rm -rf "$TMPROOT"' EXIT + +list_repos() { # owner -> tab-separated full_name, archived, fork + local owner="$1" kind + kind="$(gh api "users/$owner" --jq '.type' 2>/dev/null || echo User)" + if [ "$kind" = "Organization" ]; then + gh api --paginate "orgs/$owner/repos?per_page=100" \ + --jq '.[] | [.full_name, .archived, .fork] | @tsv' + else + gh api --paginate "users/$owner/repos?per_page=100" \ + --jq '.[] | [.full_name, .archived, .fork] | @tsv' + fi +} + +has_own_templates() { # owner/repo -> 0/1 via HTTP code of contents lookup + gh api "repos/$1/contents/.github/ISSUE_TEMPLATE" >/dev/null 2>&1 +} + +render_into() { # src_file dest_file owner repo + sed -e "s/{{FORGE}}/$FORGE/g" -e "s/{{OWNER}}/$3/g" -e "s/{{REPO}}/$4/g" "$1" > "$2" +} + +row() { printf '%-55s %s\n' "$1" "$2"; } + +echo "═══════════════════════════════════════════════════" +echo " Estate template propagation" +echo " owners: $OWNERS" +echo " source: $TEMPLATE_DIR" +echo " mode: $([ "$APPLY" -eq 1 ] && echo APPLY || echo DRY-RUN)$([ "$ALL" -eq 1 ] && echo ' (all repos)')" +echo "═══════════════════════════════════════════════════" + +for OWNER in $OWNERS; do + echo "" + echo "## $OWNER" + while IFS=$'\t' read -r FULL ARCHIVED FORK; do + NAME="${FULL#*/}" + [ -n "$ONLY" ] && [ "$NAME" != "$ONLY" ] && continue + if [ "$SKIP_ARCHIVED" -eq 1 ] && [ "$ARCHIVED" = "true" ]; then row "$FULL" "skip (archived)"; continue; fi + if [ "$SKIP_FORKS" -eq 1 ] && [ "$FORK" = "true" ]; then row "$FULL" "skip (fork)"; continue; fi + if [ "$NAME" = ".github" ]; then + row "$FULL" "skip (.github repo — handled by layer A of docs/ESTATE-PROPAGATION.adoc)"; continue + fi + + if has_own_templates "$FULL"; then + NEED="has own templates — PR" + else + if [ "$ALL" -eq 1 ]; then NEED="no own templates — PR (--all)"; else row "$FULL" "ok (inherits estate defaults)"; continue; fi + fi + + if [ "$APPLY" -eq 0 ]; then row "$FULL" "would PR: $NEED"; continue; fi + + WT="$TMPROOT/${OWNER}-${NAME}" + if ! git clone --depth 1 "https://github.com/$FULL" "$WT" >/dev/null 2>&1; then + row "$FULL" "ERROR: clone failed (permissions?)"; continue + fi + ( + cd "$WT" + git checkout -B "$BRANCH" >/dev/null 2>&1 + mkdir -p .github/ISSUE_TEMPLATE + for f in "$TEMPLATE_DIR"/*; do + render_into "$f" ".github/ISSUE_TEMPLATE/$(basename "$f")" "$OWNER" "$NAME" + done + if [ -n "$SUPPORT_FILE" ] && [ -f "$SUPPORT_FILE" ]; then + render_into "$SUPPORT_FILE" ".github/SUPPORT.md" "$OWNER" "$NAME" + fi + git add .github + if git diff --cached --quiet; then + echo "$FULL — templates already identical" + exit 3 + fi + git -c user.email="${GIT_AUTHOR_EMAIL:-arena-bot@users.noreply.github.com}" \ + -c user.name="${GIT_AUTHOR_NAME:-estate-bot}" \ + commit -q -m "chore(github): sync issue forms (optional, consistent fields) + +Source: hyperpolymath/aerie .github/ISSUE_TEMPLATE (issues #91/#92 class: +fields were inconsistently typed/required). Placeholders rendered for +$FULL. See docs/ESTATE-PROPAGATION.adoc." -m "Signed-off-by: ${GIT_AUTHOR_NAME:-estate-bot} <${GIT_AUTHOR_EMAIL:-arena-bot@users.noreply.github.com}>" + git push -q --force-with-lease -u origin "$BRANCH" + gh pr create --repo "$FULL" --title "chore(github): sync issue forms (optional, consistent fields)" \ + --base "$(gh repo view "$FULL" --json defaultBranchRef --jq .defaultBranchRef.name)" \ + --body $'## What\n\nSyncs the canonical issue forms from `hyperpolymath/aerie`:\n\n- All free-text fields are the same entry type (plain `textarea`; the `render: shell` split is gone).\n- No content field is mandatory — the only required items are the two attestation checkboxes on the bug form.\n- `{{FORGE}}`/`{{OWNER}}`/`{{REPO}}` placeholders were rendered for this repo.\n\n## Why\n\nTemplate-wide fix class (aerie issues #91/#92): reporters could not skip irrelevant fields, and one field rendered differently from its neighbours. See `docs/ESTATE-PROPAGATION.adoc` in aerie for the estate rollout plan.\n\nGenerated by `scripts/propagate-github-templates.sh`.' \ + 2>&1 | tail -1 + for lab in conformance; do + gh pr edit --repo "$FULL" --add-label "$lab" >/dev/null 2>&1 || true + done + ) || { + rc=$? + if [ "$rc" -eq 3 ]; then row "$FULL" "up to date"; else row "$FULL" "ERROR: PR step failed ($rc)"; fi + continue + } + row "$FULL" "PR raised" + done < <(list_repos "$OWNER") +done + +echo "" +echo "Done. Next: verify checks are SCHEDULING (not startup-failing) on each PR —" +echo "see the verification sweep in docs/ESTATE-PROPAGATION.adoc §5." diff --git a/setup.sh b/setup.sh index 2960a33..f225e16 100755 --- a/setup.sh +++ b/setup.sh @@ -2,63 +2,229 @@ # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# Aerie — Universal Setup Script -# Detects platform and shell, installs just, then hands off to Justfile. +# Aerie — Universal Setup Script (the zero-prerequisite path). +# +# Guaranteed to work straight after `git clone` with only core OS tools: +# bash and curl (plus tar/xz for the Zig archive). It detects the platform, +# installs whatever the Justfile needs that is missing — the estate task +# runner `just` and the Zig toolchain pinned in .tool-versions — into a +# user-local bin directory (no sudo), then hands off to the Justfile via +# `just doctor`. +# +# Safe to re-run: tools already present are left untouched. set -euo pipefail +TMPD="$(mktemp -d)" +trap 'rm -rf "$TMPD"' EXIT + +die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } +have() { command -v "$1" >/dev/null 2>&1; } + echo "═══════════════════════════════════════════════════" -echo " Aerie — Setup" +echo " Aerie — Setup (zero-prerequisite bootstrap)" echo "═══════════════════════════════════════════════════" echo "" -# Platform detection OS="$(uname -s)" ARCH="$(uname -m)" echo "Platform: $OS $ARCH" - -# Shell detection -CURRENT_SHELL="$(basename "$SHELL" 2>/dev/null || echo "unknown")" +CURRENT_SHELL="$(basename "${SHELL:-unknown}" 2>/dev/null || echo "unknown")" echo "Shell: $CURRENT_SHELL" echo "" -# Check for just -if ! command -v just >/dev/null 2>&1; then - echo "just (command runner) is required but not installed." - echo "" - case "$OS" in - Linux) - if command -v cargo >/dev/null 2>&1; then - echo "Installing just via cargo..." - cargo install just - elif command -v brew >/dev/null 2>&1; then - echo "Installing just via Homebrew..." - brew install just - else - echo "Install just from: https://just.systems/man/en/installation.html" - exit 1 - fi - ;; - Darwin) - if command -v brew >/dev/null 2>&1; then - echo "Installing just via Homebrew..." - brew install just - else - echo "Install Homebrew first: https://brew.sh" - echo "Then: brew install just" - exit 1 - fi - ;; - *) - echo "Install just from: https://just.systems/man/en/installation.html" - exit 1 - ;; - esac - echo "" +# Normalise the CPU architecture to the naming used by both tool vendors. +case "$ARCH" in + x86_64 | amd64) ARCH=x86_64 ;; + arm64 | aarch64) ARCH=aarch64 ;; + *) die "Unsupported CPU architecture: $ARCH (need x86_64 or aarch64)" ;; +esac + +# User-local install prefix. Everything this script installs lands here; +# nothing touches /usr/local, nothing needs sudo. +BIN_DIR="${AERIE_BIN_DIR:-${XDG_BIN_HOME:-$HOME/.local/bin}}" +DATA_DIR="${AERIE_DATA_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/aerie}" +mkdir -p "$BIN_DIR" "$DATA_DIR" +PATH_PREPENDED="" +case ":$PATH:" in + *":$BIN_DIR:"*) : ;; + *) + PATH="$BIN_DIR:$PATH" + export PATH + PATH_PREPENDED=1 + ;; +esac + +have curl || die "curl is required (it is the only tool setup.sh cannot bootstrap). Install it with your OS package manager." + +download() { # url dest + curl -fSL --proto '=https' --tlsv1.2 --retry 3 -o "$2" "$1" +} + +# ──────────────────────────────────────────────────────────────── just + +latest_release_tag() { # owner/repo -> tag, or empty + local url + url="$(curl -fsSI -o /dev/null -w '%{redirect_url}' \ + "https://github.com/$1/releases/latest" 2>/dev/null)" || return 0 + [ -n "$url" ] && printf '%s\n' "${url##*/}" || return 0 +} + +install_just() { + if have just; then + echo "just: already present ($(just --version 2>/dev/null | head -1))" + return 0 + fi + echo "just: not found — installing into $BIN_DIR" + + local target ext + case "$OS" in + Linux) target="$ARCH-unknown-linux-musl" ext=tar.gz ;; + Darwin) target="$ARCH-apple-darwin" ext=tar.gz ;; + *) target="" ;; + esac + + local ver + ver="${JUST_VERSION:-$(latest_release_tag casey/just)}" + ver="${ver:-1.40.0}" # last-resort pin if the releases redirect is unreachable + + if [ -n "$target" ] && download "https://github.com/casey/just/releases/download/$ver/just-$ver-$target.$ext" "$TMPD/just.$ext"; then + tar -xzf "$TMPD/just.$ext" -C "$TMPD" just + install -m 755 "$TMPD/just" "$BIN_DIR/just" + elif have cargo; then + echo "just: binary download failed — falling back to cargo install just" + cargo install just + elif have brew; then + echo "just: binary download failed — falling back to brew install just" + brew install just + else + die "could not install just. Install it manually: https://just.systems/man/en/installation.html" + fi + + have just || die "just is still not on PATH after the install attempt ($BIN_DIR must be on PATH)." + echo "just: installed ($(just --version 2>/dev/null | head -1))" +} + +# ──────────────────────────────────────────────────────────────── zig + +pinned_zig_version() { + if [ -f .tool-versions ]; then + awk '$1 == "zig" { print $2; exit }' .tool-versions + fi +} + +# Extract the tarball URL + shasum for - out of the ziglang.org +# release index without needing jq. +zig_index_field() { # version arch-os field(tarball|shasum) -> value, or empty + curl -fsSL "https://ziglang.org/download/index.json" 2>/dev/null | awk -v v="\"$1\"" -v k="\"$2\"" -v f="\"$3\"" ' + $0 ~ "^ " v ": {" { inver = 1; next } + inver && $0 ~ "^ }" { exit } + inver && $0 ~ "^ " k ": {" { inkey = 1; next } + inkey && $0 ~ "^ }" { exit } + inkey && $0 ~ f { + sub(/^[^"]*"[^"]*"[^"]*"/, "") + sub(/".*/, "") + print + exit + } + ' +} + +install_zig() { + if have zig; then + echo "zig: already present ($(zig version 2>/dev/null | head -1))" + return 0 + fi + + local ver os_tag + ver="${ZIG_VERSION:-$(pinned_zig_version)}" + ver="${ver:-0.15.2}" # last-resort pin; keep in step with build.zig / mise.toml + case "$OS" in + Linux) os_tag=linux ;; + Darwin) os_tag=macos ;; + *) os_tag="" ;; + esac + + echo "zig: not found — installing $ver into $DATA_DIR" + + local url sha="" key="${ARCH}-${os_tag}" + if [ -n "$os_tag" ]; then + url="$(zig_index_field "$ver" "$key" tarball)" + sha="$(zig_index_field "$ver" "$key" shasum)" + # Fallback when the index is unreachable: 0.14+ asset naming. + url="${url:-https://ziglang.org/download/$ver/zig-$ARCH-$os_tag-$ver.tar.xz}" + fi + + if [ -n "${url:-}" ] && download "$url" "$TMPD/zig.tar.xz"; then + if [ -n "$sha" ]; then + local actual="" + if have sha256sum; then + actual="$(sha256sum "$TMPD/zig.tar.xz" | awk '{print $1}')" + elif have shasum; then + actual="$(shasum -a 256 "$TMPD/zig.tar.xz" | awk '{print $1}')" + fi + if [ -n "$actual" ]; then + [ "$actual" = "$sha" ] || die "zig archive checksum mismatch — aborting." + echo "zig: checksum verified" + else + echo "zig: WARNING: no sha256 tool available — skipping checksum verification" + fi + fi + tar -xJf "$TMPD/zig.tar.xz" -C "$TMPD" 2>/dev/null \ + || die "could not extract the Zig archive — install xz (e.g. apt/dnf/brew install xz) and re-run." + local dest="$DATA_DIR/zig-$ver" + rm -rf "$dest" + mv "$TMPD"/zig-* "$dest" + ln -sfn "$dest/zig" "$BIN_DIR/zig" + elif have mise; then + echo "zig: download failed — falling back to mise (mise.toml pins $ver)" + mise install "zig@$ver" + elif have brew; then + echo "zig: download failed — falling back to brew install zig" + brew install zig + else + echo "zig: could not install automatically." + echo " Get Zig $ver from https://ziglang.org/download/ and put it on PATH." + return 1 + fi + + if have zig; then + echo "zig: installed ($(zig version 2>/dev/null | head -1))" + else + echo "zig: still not on PATH after the install attempt." + return 1 + fi +} + +# ──────────────────────────────────────────────────────────────── run + +install_just +ZIG_RC=0 +install_zig || ZIG_RC=1 + +if [ -n "$PATH_PREPENDED" ]; then + echo "" + echo "NOTE: installed tools live in $BIN_DIR, which is not yet on your PATH." + echo " It was added for this session only. Make it permanent with:" + echo "" + echo " echo 'export PATH=\"$BIN_DIR:\$PATH\"' >> ~/.${CURRENT_SHELL}rc" + echo "" fi +echo "" echo "Running diagnostics..." -just doctor +echo "" +if ! just doctor; then + echo "" + echo "Some checks failed above. Re-running ./setup.sh will retry the automatic" + echo "repair; anything it cannot fix is listed with a manual install hint." + exit 1 +fi echo "" -echo "Setup complete. Run 'just help-me' for common workflows." +if [ "$ZIG_RC" -eq 0 ]; then + echo "Setup complete. Next: just build && just test ('just help-me' for workflows)." +else + echo "Setup mostly complete, but Zig still needs a manual install (see above);" + echo "the docs-only and container paths work without it." +fi