From ea6d880c5e24a783ebe37b3cf94099b161c6f790 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Sun, 27 Sep 2026 12:36:41 +0100 Subject: [PATCH] feat(acceptance): validate the public entry points against live infrastructure Adds the repeatable acceptance run and records what the family publish proved. scripts/acceptance-public-release.py is read-only and reports pass, fail or not-executed per entry point, so an environment limitation is never counted as success. It runs the documented installer against a scratch directory and checks the installed binary reports the channel version, verifies every advertised archive against the manifest size and SHA-256, resolves the Homebrew formula, and reports the crates.io versions a cargo install would serve. The plans under docs/plans/ gain the measured result of the family publish. A dry run of the full client crate family on main (run 36314289075) reaches terraphim_config 1.20.4 and fails to compile: unresolved import terraphim_automata::parse_markdown_directives_dir the item is gated behind the `fs-traversal` feature crates.io's terraphim_automata is 1.21.1, which gates that symbol, while crates.io's terraphim_config is 1.20.4, which does not enable the feature, and the client crates require config 1.20.2. No client crate is publishable until that is repaired, and the repair lives in terraphim-core and terraphim-config-persistence, not here. Run 36314289075 also confirmed that publish-crates.yml refuses terraphim_agent before mutating any manifest (#95), and that terraphim_update 1.20.2 and terraphim_command_runtime 0.1.0 already exist on crates.io. Verified: acceptance script compiles; record manifest check passes (3 binaries at 1.21.16, 20 archives byte-verified); the failing dry run is cited by id. --- .../design-crates-io-parity-2026-09-27.md | 210 ++++++++++++ ...-homebrew-documented-command-2026-09-27.md | 214 ++++++++++++ ...sign-macos-homebrew-evidence-2026-09-27.md | 211 ++++++++++++ ...-native-channels-deb-rpm-aur-2026-09-27.md | 229 +++++++++++++ ...gn-public-release-acceptance-2026-09-27.md | 239 +++++++++++++ .../plans/design-site-installer-2026-09-27.md | 288 ++++++++++++++++ .../research-crates-io-parity-2026-09-27.md | 287 ++++++++++++++++ ...-homebrew-documented-command-2026-09-27.md | 203 +++++++++++ ...arch-macos-homebrew-evidence-2026-09-27.md | 189 +++++++++++ ...-native-channels-deb-rpm-aur-2026-09-27.md | 195 +++++++++++ ...ch-public-release-acceptance-2026-09-27.md | 201 +++++++++++ ...site-installer-stale-release-2026-09-27.md | 231 +++++++++++++ scripts/acceptance-public-release.py | 319 ++++++++++++++++++ 13 files changed, 3016 insertions(+) create mode 100644 docs/plans/design-crates-io-parity-2026-09-27.md create mode 100644 docs/plans/design-homebrew-documented-command-2026-09-27.md create mode 100644 docs/plans/design-macos-homebrew-evidence-2026-09-27.md create mode 100644 docs/plans/design-native-channels-deb-rpm-aur-2026-09-27.md create mode 100644 docs/plans/design-public-release-acceptance-2026-09-27.md create mode 100644 docs/plans/design-site-installer-2026-09-27.md create mode 100644 docs/plans/research-crates-io-parity-2026-09-27.md create mode 100644 docs/plans/research-homebrew-documented-command-2026-09-27.md create mode 100644 docs/plans/research-macos-homebrew-evidence-2026-09-27.md create mode 100644 docs/plans/research-native-channels-deb-rpm-aur-2026-09-27.md create mode 100644 docs/plans/research-public-release-acceptance-2026-09-27.md create mode 100644 docs/plans/research-site-installer-stale-release-2026-09-27.md create mode 100755 scripts/acceptance-public-release.py diff --git a/docs/plans/design-crates-io-parity-2026-09-27.md b/docs/plans/design-crates-io-parity-2026-09-27.md new file mode 100644 index 00000000..f7936ad5 --- /dev/null +++ b/docs/plans/design-crates-io-parity-2026-09-27.md @@ -0,0 +1,210 @@ +# Implementation Plan: Truthful crates.io channel statement and installer steering + +**Status**: Draft +**Research Doc**: `docs/plans/research-crates-io-parity-2026-09-27.md` +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Phase**: 2 (disciplined-design) +**Estimated Effort**: 4 hours + +## Overview + +### Summary + +Make every documented install path either deliver 1.21.16 or state plainly what it delivers. The cargo path keeps crates.io as-is (publishing the family is a separate programme), and users are steered to the path that does deliver 1.21.16. + +### Approach + +Documentation and steering only. No crate is published, nothing is yanked, and no dependency pin changes. The site's cargo block gains an accurate version note and a pointer to the installer; the crate READMEs carry the same note so it survives being read on crates.io itself. + +### Scope + +**In Scope:** +- An accurate statement on the site and in crate READMEs of what `cargo install` delivers today. +- A pointer from the cargo instruction to the installer and the self-update path. +- A validation check that the documented versions match reality at release time. + +**Out of Scope:** +- Publishing any crate to crates.io, including the family. +- Changing `registry = "terraphim"` pins or `[patch.crates-io]`. +- Yanking or deleting existing versions. + +**Avoid At All Cost** (from 5/25 analysis): + +| Rejected | Why | +|---|---| +| Publishing the dependency family now | Irreversible, cross-repo, owned by `terraphim-core #71` | +| A one-off manual publish to "catch up" | Creates an unreproducible channel state | +| Removing registry pins to make publishing easy | Changes how the product builds; needs its own research | +| A caveat so vague it means nothing ("may lag") | The point is a precise, checkable statement | + +## Architecture + +### Component Diagram + +``` +crates.io (unchanged) installer (fixed separately) self-update (works) + | | | + +---------- site cargo block + crate READMEs ---------------+ + states true version and preferred path +``` + +### Data Flow + +Release -> crates.io newest_version (observed) and channel manifest version (observed) -> site/README note generated or hand-checked at release time -> published. + +### Key Design Decisions + +| Decision | Rationale | Alternatives Rejected | +|---|---|---| +| State the exact version, not a vague caveat | A precise claim is checkable and honest | "may be behind" phrasing | +| Note lives in crate READMEs as well as the site | The README is what a user sees on crates.io | Site-only | +| Validate the claim in CI | Prevents the note drifting from reality | Trust that someone will notice | +| Leave crates.io untouched | Avoids irreversible action owned elsewhere | Catch-up publish | + +### Eliminated Options (Essentialism) + +| Option Rejected | Why Rejected | Risk of Including | +|---|---|---| +| Publishing terraphim-grep alone as a proof | Its graph still needs the family; half-published state | Confusing version matrix | +| Auto-generating the README note from crates.io at build time | Adds network dependency to the build | Flaky builds | +| A separate "channels" documentation page | Out of vital few; the install section is where users are | Documentation sprawl | + +### Simplicity Check + +**What if this could be easy?** It is: one sentence per surface stating the true version and the preferred path, plus one CI assertion that the sentence is still true. Nothing else is needed to make the channel honest. + +**Senior Engineer Test**: A senior engineer would approve; the alternative is an irreversible cross-repo publish for a documentation problem. + +**Nothing Speculative Checklist**: +- [x] No features the user didn't request +- [x] No abstractions "in case we need them later" +- [x] No flexibility "just in case" +- [x] No error handling for scenarios that cannot occur +- [x] No premature optimization + +## File Changes + +### New Files + +| File | Purpose | +|------|---------| +| `scripts/check-documented-versions.py` | Asserts the documented crates.io version statement matches the live observation | + +### Modified Files + +| File | Changes | +|------|---------| +| Site install section | Accurate cargo note plus installer pointer | +| `crates/terraphim_agent/README.md` | Channel note | +| `crates/terraphim_cli/README.md` | Channel note | +| `crates/terraphim_grep/README.md` | Channel note | +| `.github/workflows/ci.yml` or a scheduled workflow | Run the checker | + +### Deleted Files + +| File | Reason | +|------|--------| +| none | - | + +## API Design + +### New Script Interface + +``` +check-documented-versions.py [--site-url URL] [--crates a,b,c] + exit 0 when every documented claim matches the live version + exit 1 with a diff-style report when a claim is stale +``` + +### Error Types + +``` +// exit 0 all claims accurate +// exit 1 one or more claims stale; prints expected vs observed +// exit 2 crates.io or the site unreachable (does not fail the claim, reports inconclusive) +``` + +## Test Strategy + +### Unit Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_parses_crate_version` | `scripts/check-documented-versions.py` self-test | Version extraction from the API payload | +| `test_detects_stale_claim` | same | A stale claim yields exit 1 | + +### Integration Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_documented_versions_match_live` | CI job invoking the checker | No drift at merge time | +| `test_readme_note_present` | same | Each crate README carries the note | + +### Property Tests + +Not applicable. + +## Implementation Steps + +### Step 1: Version checker + +**Files:** `scripts/check-documented-versions.py` +**Description:** Query crates.io with a descriptive User-Agent for each documented crate, extract the documented version claims from the site and READMEs, and compare. +**Tests:** unit tests above +**Estimated:** 1.5 hours + +### Step 2: Documentation + +**Files:** site install section, three crate READMEs +**Description:** Add the precise statement and the steer to the installer. +**Tests:** `test_readme_note_present` +**Dependencies:** Step 1 +**Estimated:** 1.5 hours + +### Step 3: CI wiring + +**Files:** workflow file +**Description:** Run the checker on merge and on a schedule; fail on drift. +**Dependencies:** Steps 1 and 2 +**Estimated:** 1 hour + +## Rollback Plan + +1. Revert the documentation commit; nothing irreversible was done. +2. The checker can be disabled by removing its workflow step. + +## Migration (if applicable) + +Not applicable. + +## Dependencies + +### New Dependencies + +| Dependency | Version | Justification | +|------------|---------|---------------| +| none | - | Python standard library only | + +### Dependency Updates + +| Dependency | From | To | Reason | +|------------|------|-----|--------| +| none | - | - | - | + +## Performance Considerations + +Not applicable; the checker runs once per merge. + +## Open Items + +| Item | Status | Owner | +|------|--------|-------| +| Confirm policy option (a) family publish, (b) document lag, or (c) document and steer | Pending | release owner | +| Decide whether the site footnote or the README is the primary statement | Pending | release owner | + +## Approval + +- [ ] Technical review complete +- [ ] Test strategy approved +- [ ] Human approval received \ No newline at end of file diff --git a/docs/plans/design-homebrew-documented-command-2026-09-27.md b/docs/plans/design-homebrew-documented-command-2026-09-27.md new file mode 100644 index 00000000..62a0105f --- /dev/null +++ b/docs/plans/design-homebrew-documented-command-2026-09-27.md @@ -0,0 +1,214 @@ +# Implementation Plan: Correct the documented Homebrew command + +**Status**: Draft +**Research Doc**: `docs/plans/research-homebrew-documented-command-2026-09-27.md` +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Phase**: 2 (disciplined-design) +**Estimated Effort**: 3 hours + +## Overview + +### Summary + +Change the documented Homebrew command to the real tap and real formula names, and decide explicitly whether terraphim-cli also ships through the tap. + +### Approach + +Copy correction plus, if the cli is in scope, one new formula following the existing pattern exactly. No change to how the existing formulae work. + +### Scope + +**In Scope:** +- Correct the tap and formula names in the documented command. +- Add a `terraphim-cli` formula if the site continues to advertise a cli install. +- Add the Homebrew command to the public-release acceptance run. + +**Out of Scope:** +- Renaming the tap or moving formulae to homebrew-core. +- Fixing `terraphim-server.rb`'s terraphim-ai v1.20.5 pin. +- macOS execution evidence, which is tracked separately. + +**Avoid At All Cost** (from 5/25 analysis): + +| Rejected | Why | +|---|---| +| Inventing a `terraphim-ai` formula to match the wrong copy | Creates a product-name formula nobody else uses | +| Renaming the tap to `homebrew-terraphim-ai` | Breaks existing users for a naming preference | +| Duplicating the formula pattern into a shared library | Three formulae do not justify abstraction | + +## Architecture + +### Component Diagram + +``` +site copy: brew tap terraphim/terraphim && brew install terraphim-agent + | | + | +-> tap repository terraphim/homebrew-terraphim + | Formula/terraphim-agent.rb (v1.21.16) + | Formula/terraphim-grep.rb (v1.21.16) + | Formula/terraphim-cli.rb (new, if in scope) + +----------------------------> downloads.terraphim.ai (primary) + GitHub release (mirror) +``` + +### Data Flow + +`brew install ` -> formula url resolved per OS/arch -> archive downloaded -> SHA-256 compared -> binary installed -> formula `test do` block executed by `brew test`. + +### Key Design Decisions + +| Decision | Rationale | Alternatives Rejected | +|---|---|---| +| Copy follows the tap, not the reverse | The tap has users and merge history; the copy is one line | Renaming the tap | +| New cli formula copies the existing pattern exactly | Proven shape, no new concepts | A shared helper | +| Acceptance run executes the documented command | Prevents copy drift | Manual review | + +### Eliminated Options (Essentialism) + +| Option Rejected | Why Rejected | Risk of Including | +|---|---|---| +| A meta-formula installing all binaries | Brew formulae install one thing well | Ownership and conflict complexity | +| Serving the tap from a separate repository | Adds a hop for no benefit | Synchronisation burden | +| Adding a `livecheck` block for every formula now | Not needed for correctness | Extra moving parts | + +### Simplicity Check + +**What if this could be easy?** It is: correct one line, and if the cli is in scope, add one formula that is a near-copy of an existing one with different names and hashes. + +**Senior Engineer Test**: Yes; anything more would be over-engineering a copy fix. + +**Nothing Speculative Checklist**: +- [x] No features the user didn't request +- [x] No abstractions "in case we need them later" +- [x] No flexibility "just in case" +- [x] No error handling for scenarios that cannot occur +- [x] No premature optimization + +## File Changes + +### New Files + +| File | Purpose | +|------|---------| +| `Formula/terraphim-cli.rb` (tap) | Install terraphim-cli 1.21.16, if in scope | + +### Modified Files + +| File | Changes | +|------|---------| +| Site install section | `brew tap terraphim/terraphim && brew install terraphim-agent` (or the agreed set) | +| `scripts/check-documented-versions.py` (from the crates item) | Also assert the Homebrew command's formula names exist | +| Release acceptance run | Add the Homebrew command | + +### Deleted Files + +| File | Reason | +|------|--------| +| none | - | + +## API Design + +No programmatic interface. The contract is the documented command string and the formula names. + +``` +$ brew tap terraphim/terraphim +$ brew install terraphim-agent +$ terraphim-agent --version +terraphim-agent 1.21.16 +``` + +## Test Strategy + +### Unit Tests + +| Test | Location | Purpose | +|------|----------|---------| +| Formula Ruby syntax | `ruby -c Formula/*.rb` | Formulae parse | +| `brew audit --strict --formula` | Linuxbrew | Catch formula smells | + +### Integration Tests + +| Test | Location | Purpose | +|------|----------|---------| +| Install agent from the tap on Linuxbrew | acceptance run | Real install, real checksum | +| Install grep from the tap on Linuxbrew | acceptance run | Real install, real checksum | +| Run the documented command verbatim | acceptance run | Copy and tap agree | +| Formula downloads match the manifest | acceptance run | No checksum drift | + +No mocks: Homebrew is exercised for real on a Linuxbrew host; macOS execution is a separately tracked item. + +### Property Tests + +Not applicable. + +## Implementation Steps + +### Step 1: Decide the installable set + +**Files:** none (decision) +**Description:** Confirm whether the documented Homebrew path installs agent only, agent plus grep, or agent plus cli. +**Estimated:** 15 minutes + +### Step 2: Copy correction + +**Files:** site install section +**Description:** Replace the tap and formula names with the real ones. +**Tests:** documented command run in the acceptance run +**Estimated:** 30 minutes + +### Step 3: Optional cli formula + +**Files:** `Formula/terraphim-cli.rb` +**Description:** Mirror `terraphim-agent.rb` with cli names, target selection and manifest checksums. +**Tests:** `ruby -c`, `brew audit`, install on Linuxbrew +**Dependencies:** Step 1 +**Estimated:** 1.5 hours + +### Step 4: Acceptance wiring + +**Files:** acceptance run definition +**Description:** Execute the documented command and compare the installed version with the manifest. +**Dependencies:** Steps 2 and 3 +**Estimated:** 1 hour + +## Rollback Plan + +1. Revert the copy commit; the previous (broken) command returns, which is no regression. +2. For a new formula, delete the file; no user depends on it yet. +3. Existing formulae are untouched. + +## Migration (if applicable) + +Not applicable. + +## Dependencies + +### New Dependencies + +| Dependency | Version | Justification | +|------------|---------|---------------| +| none | - | Formulae use existing channels | + +### Dependency Updates + +| Dependency | From | To | Reason | +|------------|------|-----|--------| +| none | - | - | - | + +## Performance Considerations + +Not applicable. + +## Open Items + +| Item | Status | Owner | +|------|--------|-------| +| Which binaries the Homebrew path advertises | Pending | release owner | +| Site source location | Pending | release owner | +| Whether `terraphim-cli` gets a formula | Pending | release owner | + +## Approval + +- [ ] Technical review complete +- [ ] Test strategy approved +- [ ] Human approval received \ No newline at end of file diff --git a/docs/plans/design-macos-homebrew-evidence-2026-09-27.md b/docs/plans/design-macos-homebrew-evidence-2026-09-27.md new file mode 100644 index 00000000..611b4612 --- /dev/null +++ b/docs/plans/design-macos-homebrew-evidence-2026-09-27.md @@ -0,0 +1,211 @@ +# Implementation Plan: macOS Homebrew evidence job + +**Status**: Draft +**Research Doc**: `docs/plans/research-macos-homebrew-evidence-2026-09-27.md` +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Phase**: 2 (disciplined-design) +**Estimated Effort**: 3 hours + +## Overview + +### Summary + +An automated job on a GitHub `macos-15` runner that installs the published formulae from the tap, asserts behaviour including the codesign check, and archives the transcript in the release record. + +### Approach + +Reuse the runner already used by the signing jobs. Tap the formula, install, print versions, run one real command, run `brew test`, and upload the transcript as an artefact referenced by the release record. + +### Scope + +**In Scope:** +- `brew tap` and `brew install` for terraphim-agent and terraphim-grep on macOS arm64. +- Version output, one real command, and `brew test` including the codesign assertion. +- Transcript artefact attached to the release record. +- Honest reporting if the thin Intel lane cannot run because Rosetta is unavailable. + +**Out of Scope:** +- Provisioning a persistent macOS host. +- Notarisation re-verification beyond the formula's codesign check. +- Any change to the formulae themselves, beyond what the evidence run proves necessary. + +**Avoid At All Cost** (from 5/25 analysis): + +| Rejected | Why | +|---|---| +| A permanent self-hosted Mac | Cost and maintenance for a short periodic job | +| Manual evidence collection | Manual is exactly why the gap exists | +| Treating a Rosetta-blocked lane as a pass | Violates the honesty rule used elsewhere in this plan set | + +## Architecture + +### Component Diagram + +``` +release workflow (or manual dispatch) + | + v +job: macos-brew-evidence (runs-on: macos-15) + brew tap terraphim/terraphim + brew install terraphim-agent terraphim-grep + terraphim-agent --version ; terraphim-grep --version + terraphim-agent session list (or equivalent real command) + brew test terraphim-agent + tee transcript -> upload-artifact +``` + +### Data Flow + +Tap -> formula -> R2 universal archive -> SHA-256 -> install -> assertions -> transcript -> release record. + +### Key Design Decisions + +| Decision | Rationale | Alternatives Rejected | +|---|---|---| +| GitHub `macos-15` runner | Already used and permitted; no new infrastructure | Self-hosted Mac | +| Transcript as an uploaded artefact | Verifiable evidence attached to the release | Console output only | +| Run `brew test` explicitly | It contains the macOS codesign assertion | Version check only | +| Report the Intel lane as not-executed when Rosetta is absent | Consistent honesty rule | Silent omission | + +### Eliminated Options (Essentialism) + +| Option Rejected | Why Rejected | Risk of Including | +|---|---|---| +| Testing every Homebrew version | Out of vital few | Long runtimes | +| Publishing to homebrew-core as evidence | Unrelated programme | Review burden | +| Screenshot-based UI evidence | The binaries are CLIs | Brittle artefacts | + +### Simplicity Check + +**What if this could be easy?** It is a short job: tap, install, assert, upload. The only judgement call is the Rosetta lane, handled by an explicit not-executed state. + +**Senior Engineer Test**: Yes. + +**Nothing Speculative Checklist**: +- [x] No features the user didn't request +- [x] No abstractions "in case we need them later" +- [x] No flexibility "just in case" +- [x] No error handling for scenarios that cannot occur +- [x] No premature optimization + +## File Changes + +### New Files + +| File | Purpose | +|------|---------| +| `.github/workflows/macos-brew-evidence.yml` | The evidence job | +| `scripts/probes/macos-evidence.sh` | Assertions and transcript assembly | + +### Modified Files + +| File | Changes | +|------|---------| +| `docs/release-operator-checklist.md` | Section 6 points at the job and its artefact | +| Release record for v1.21.16 | Attach the first transcript | + +### Deleted Files + +| File | Reason | +|------|--------| +| none | - | + +## API Design + +``` +scripts/probes/macos-evidence.sh --formulae "terraphim-agent terraphim-grep" --out transcript.txt + exit 0 all assertions passed + exit 1 an assertion failed (transcript still written) + exit 2 lane not executable (for example Rosetta unavailable), reported as not-executed +``` + +## Test Strategy + +### Unit Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_transcript_contains_versions` | `scripts/tests/test-macos-evidence.sh` | Required lines present | +| `test_lane_skip_is_exit_2` | same | Skip is not a pass | + +### Integration Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_install_from_tap_macos_arm64` | macOS runner | Real install | +| `test_codesign_assertion_runs` | macOS runner | The macOS-only test executes | +| `test_transcript_uploaded` | workflow | Artefact present and non-empty | + +No mocks: real Homebrew, real tap, real signed bytes. + +### Property Tests + +Not applicable. + +## Implementation Steps + +### Step 1: Evidence script + +**Files:** `scripts/probes/macos-evidence.sh` +**Description:** Tap, install, versions, real command, `brew test`, transcript, tristate exit codes. +**Tests:** unit tests +**Estimated:** 1 hour + +### Step 2: Workflow job + +**Files:** `.github/workflows/macos-brew-evidence.yml` +**Description:** `macos-15` job, manual dispatch plus invocation from the release process, artefact upload. +**Tests:** a real run +**Dependencies:** Step 1 +**Estimated:** 1 hour + +### Step 3: First evidence run and archival + +**Files:** release record +**Description:** Run against 1.21.16 and archive the transcript. +**Dependencies:** Step 2 +**Estimated:** 1 hour + +## Rollback Plan + +1. The job is additive and changes no published artefact. +2. If the job proves flaky, disable the workflow and record the reason; the checklist reverts to manual. + +## Migration (if applicable) + +Not applicable. + +## Dependencies + +### New Dependencies + +| Dependency | Version | Justification | +|------------|---------|---------------| +| none | - | Uses Homebrew already present on macOS runners | + +### Dependency Updates + +| Dependency | From | To | Reason | +|------------|------|-----|--------| +| none | - | - | - | + +## Performance Considerations + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Job runtime | < 10 minutes | runner log | +| Runner minutes per release | minimal | run count | + +## Open Items + +| Item | Status | Owner | +|------|--------|-------| +| Whether GitHub macOS runners satisfy the checklist's evidence requirement | Pending | release owner | +| Whether the thin Intel lane is required this release | Pending | release owner | + +## Approval + +- [ ] Technical review complete +- [ ] Test strategy approved +- [ ] Human approval received \ No newline at end of file diff --git a/docs/plans/design-native-channels-deb-rpm-aur-2026-09-27.md b/docs/plans/design-native-channels-deb-rpm-aur-2026-09-27.md new file mode 100644 index 00000000..a60f9106 --- /dev/null +++ b/docs/plans/design-native-channels-deb-rpm-aur-2026-09-27.md @@ -0,0 +1,229 @@ +# Implementation Plan: Native channels - DEB/RPM packaging and AUR submission + +**Status**: Draft +**Research Doc**: `docs/plans/research-native-channels-deb-rpm-aur-2026-09-27.md` +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Phase**: 2 (disciplined-design) +**Estimated Effort**: 2 days of dry-run work; publication blocked externally + +## Overview + +### Summary + +Produce and dry-run-validate native DEB/RPM packages and an AUR split PKGBUILD from the sealed stage, without publishing, until central signing and AUR credentials are available. + +### Approach + +Build both from the same sealed archives and receipts, so payload hashes are provably identical to the released bytes. Publication is a separate, explicitly blocked step. + +### Scope + +**In Scope:** +- nFPM packaging for terraphim-agent and terraphim-grep on x86_64 and aarch64 MUSL. +- Split PKGBUILD `terraphim-clients-bin` producing `terraphim-agent-bin` and `terraphim-grep-bin`. +- Receipts under `/usr/share/terraphim/package-manager.d/`. +- Dry-run validation: contents, ownership, lint, reproducibility, payload hashes. + +**Out of Scope:** +- Repository publication, which waits on central signing. +- AUR submission, which waits on credentials. +- Omarchy package generation, which consumes the AUR output. + +**Avoid At All Cost** (from 5/25 analysis): + +| Rejected | Why | +|---|---| +| Publishing before the signing contract lands | Produces unsigned packages that later need replacement | +| A hand-maintained package definition separate from the sealed stage | Guarantees hash drift | +| Building an APT/YUM repository service now | Infrastructure beyond the release | +| Bundling both binaries into one package | Violates the ownership contract | + +## Architecture + +### Component Diagram + +``` +sealed stage (archives + SHA256SUMS + receipts) + | + +--> nFPM --> terraphim-agent_1.21.16_amd64.deb / .rpm + | terraphim-grep_1.21.16_amd64.deb / .rpm + | (x86_64 and aarch64 MUSL payloads) + | | + | publication BLOCKED on central signing + | + +--> PKGBUILD terraphim-clients-bin --> terraphim-agent-bin, terraphim-grep-bin + --> .SRCINFO + --> submission BLOCKED on AUR credentials +``` + +### Data Flow + +Archive -> package payload -> package -> install in a clean matrix -> receipt written -> upgrade -> remove/purge -> receipt removed. + +### Key Design Decisions + +| Decision | Rationale | Alternatives Rejected | +|---|---|---| +| Package payloads are the sealed archives verbatim | Guarantees payload-hash equality | Rebuilding from source at package time | +| One package per binary | Ownership contract, no conflicts | A bundle package | +| Receipts under a fixed documented path | Machine-readable channel provenance | Documentation only | +| Dry-run until blockers clear | Avoids publishing artefacts that need replacement | Publish then fix | + +### Eliminated Options (Essentialism) + +| Option Rejected | Why Rejected | Risk of Including | +|---|---|---| +| AppStream metadata and desktop entries | The binaries are CLIs | Unnecessary surface | +| systemd units | No service is packaged here | Wrong scope | +| Multi-distro repository signing keys managed here | Owned by the central signing contract | Duplicated trust roots | + +### Simplicity Check + +**What if this could be easy?** Use the pinned packager, feed it the sealed archives, and assert the payload hash equals the archive hash. Everything else is a clean-matrix install test. + +**Senior Engineer Test**: Yes; the temptation to build repository infrastructure is explicitly rejected. + +**Nothing Speculative Checklist**: +- [x] No features the user didn't request +- [x] No abstractions "in case we need them later" +- [x] No flexibility "just in case" +- [x] No error handling for scenarios that cannot occur +- [x] No premature optimization + +## File Changes + +### New Files + +| File | Purpose | +|------|---------| +| `scripts/build-native-packages.sh` | nFPM packaging from the sealed stage | +| `packaging/terraphim-clients-bin/PKGBUILD` | AUR split package | +| `scripts/validate-native-packages.sh` | Dry-run matrix validation | + +### Modified Files + +| File | Changes | +|------|---------| +| `docs/release-operator-checklist.md` | Packaging section links the validation script and records the blockers | + +### Deleted Files + +| File | Reason | +|------|--------| +| none | - | + +## API Design + +``` +scripts/build-native-packages.sh --stage DIR --out DIR [--arch amd64|arm64] +scripts/validate-native-packages.sh --packages DIR + exit 0 all matrix checks passed + exit 1 a check failed + exit 2 a matrix entry could not execute (reported) +``` + +Package contract: + +``` +/usr/bin/terraphim-agent +/usr/share/doc/terraphim-agent/license +/usr/share/terraphim/package-manager.d/terraphim-agent (content: dpkg | rpm) +``` + +## Test Strategy + +### Unit Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_payload_hash_equals_archive` | `scripts/validate-native-packages.sh` | The packaged binary is the released binary | +| `test_no_glibc_dependency_musl` | same | MUSL packages declare no glibc or gcc dependency | + +### Integration Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_deb_matrix` | clean Debian/Ubuntu containers | Inspect, install, ownership, version, upgrade, remove, purge, receipt cleanup | +| `test_rpm_matrix` | clean Fedora/RHEL-compatible containers | Same checks | +| `test_arch_split_ownership` | clean Arch chroot | Both split outputs, ownership, `.SRCINFO` drift, `namcap` | +| `test_omarchy_sync` | Omarchy checkout | `bin/sync-upstream` validates both architectures | + +No mocks: real package managers in real clean containers or chroots. + +### Property Tests + +Not applicable. + +## Implementation Steps + +### Step 1: nFPM packaging + +**Files:** `scripts/build-native-packages.sh` +**Description:** Consume the sealed stage, produce per-binary DEB and RPM for both architectures with receipts. +**Tests:** payload hash equality +**Estimated:** 4 hours + +### Step 2: Package validation matrix + +**Files:** `scripts/validate-native-packages.sh` +**Description:** Clean-matrix install, upgrade, remove, purge, receipt cleanup, architecture metadata, reproducibility. +**Tests:** integration tests +**Dependencies:** Step 1 +**Estimated:** 6 hours + +### Step 3: AUR split PKGBUILD + +**Files:** `packaging/terraphim-clients-bin/PKGBUILD` +**Description:** Split outputs, architecture-specific source aliases, SHA-256 arrays, matching `provides` and `conflicts`, `.SRCINFO` generation. +**Tests:** chroot build, `namcap` +**Estimated:** 6 hours + +### Step 4: Record blockers and evidence + +**Files:** release record +**Description:** Record dry-run results and the named blockers with owners. +**Dependencies:** Steps 1 to 3 +**Estimated:** 1 hour + +## Rollback Plan + +1. Nothing is published by this plan, so rollback is deleting the scripts and the PKGBUILD. +2. If packaging is later published, rollback is yanking the release, which is why publication waits for signing. + +## Migration (if applicable) + +Existing curl-installed binaries at the same path are not migrated; the packages own `/usr/bin` and installation is documented as requiring the curl install to be removed first where both would write the same path. + +## Dependencies + +### New Dependencies + +| Dependency | Version | Justification | +|------------|---------|---------------| +| nFPM | pinned by digest | Reproducible package construction | +| namcap | distribution-provided | AUR lint | + +### Dependency Updates + +| Dependency | From | To | Reason | +|------------|------|-----|--------| +| none | - | - | - | + +## Performance Considerations + +Not applicable; packaging is a build-time concern. + +## Open Items + +| Item | Status | Owner | +|------|--------|-------| +| Central signing status (blocks DEB/RPM publication) | Blocked | terraphim-ai maintainer | +| AUR account and SSH key (blocks submission) | Blocked | release owner | +| Whether Omarchy is in this release | Pending | release owner | + +## Approval + +- [ ] Technical review complete +- [ ] Test strategy approved +- [ ] Human approval received \ No newline at end of file diff --git a/docs/plans/design-public-release-acceptance-2026-09-27.md b/docs/plans/design-public-release-acceptance-2026-09-27.md new file mode 100644 index 00000000..5565379b --- /dev/null +++ b/docs/plans/design-public-release-acceptance-2026-09-27.md @@ -0,0 +1,239 @@ +# Implementation Plan: Public-release acceptance run + +**Status**: Draft +**Research Doc**: `docs/plans/research-public-release-acceptance-2026-09-27.md` +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Phase**: 2 (disciplined-design) +**Estimated Effort**: 1 day + +## Overview + +### Summary + +One script that executes every documented public entry point and fails when any of them disagrees with the published manifest, reporting unexecutable entry points honestly as not-executed. + +### Approach + +A single Bash orchestrator plus small per-entry-point probes. Each probe prints a structured line and a final verdict table. Runs locally before announcement and in CI after the release workflow. + +### Scope + +**In Scope:** +- Installer entry point in a clean container. +- Cargo entry point: observed crates.io version versus the channel version, with the documented claim checked. +- Homebrew entry point: formula existence and checksum parity, plus real install where a Linuxbrew host exists. +- Updater entry point: shipped binary reports current against the R2 channel. +- Honest not-executed reporting for macOS and Windows. + +**Out of Scope:** +- Re-validating sealed stages, signatures or promotion provenance. +- Building anything; the run consumes published artefacts only. +- macOS execution, tracked separately. + +**Avoid At All Cost** (from 5/25 analysis): + +| Rejected | Why | +|---|---| +| A test framework dependency | Bash plus a container is enough | +| Treating skipped checks as passes | This is the exact failure the item exists to prevent | +| Caching downloaded archives between runs | Hides a broken fetch | +| A machine-readable dashboard | The release record needs transcripts, not a service | + +## Architecture + +### Component Diagram + +``` +scripts/acceptance-public-release.sh (orchestrator, prints table, sets exit code) + | + +-- probe-installer -> docker run debian:bookworm-slim: documented curl command, assert version + +-- probe-cargo -> crates.io observed version vs documented claim + +-- probe-homebrew -> formula presence + manifest checksum parity (+ real install if host) + +-- probe-updater -> channel binary check-update against downloads.terraphim.ai + +-- probe-site -> every documented command string is accounted for +``` + +### Data Flow + +Documented entry point list -> probe per entry point -> observed facts -> comparison with `stable-v2.json` -> verdict table -> exit code. + +### Key Design Decisions + +| Decision | Rationale | Alternatives Rejected | +|---|---|---| +| Container for the installer probe | The host has masking artefacts in `~/.cargo/bin` | Running on the dev host | +| Manifest is the comparison standard | Generated truth, already validated | Hardcoded expected version | +| Three-state result: pass, fail, not-executed | Honesty about what was really run | Boolean pass/fail | +| Raw transcripts archived | Evidence for the release record | Summary only | + +### Eliminated Options (Essentialism) + +| Option Rejected | Why Rejected | Risk of Including | +|---|---|---| +| A nightly full matrix of every OS | Cost without a corresponding release | Infrastructure churn | +| Screenshot or UI checks of the site | Out of vital few | Brittle, unrelated | +| Auto-opening issues on failure | Not asked for; the release owner decides | Noise | + +### Simplicity Check + +**What if this could be easy?** It is: five probes, each a handful of lines, one table, one exit code. The only subtlety is refusing to call a skip a pass, which is one extra state. + +**Senior Engineer Test**: Yes; anything heavier would be a test framework for five checks. + +**Nothing Speculative Checklist**: +- [x] No features the user didn't request +- [x] No abstractions "in case we need them later" +- [x] No flexibility "just in case" +- [x] No error handling for scenarios that cannot occur +- [x] No premature optimization + +## File Changes + +### New Files + +| File | Purpose | +|------|---------| +| `scripts/acceptance-public-release.sh` | Orchestrator and verdict table | +| `scripts/probes/installer.sh` | Clean-container installer probe | +| `scripts/probes/cargo-claim.sh` | crates.io claim probe | +| `scripts/probes/homebrew.sh` | Formula and checksum probe | +| `scripts/probes/updater.sh` | Updater probe | + +### Modified Files + +| File | Changes | +|------|---------| +| `docs/release-operator-checklist.md` | Replace the manual entry-point section with a pointer to the run | +| `.github/workflows/` | Invoke the run after the release workflow and on a schedule | + +### Deleted Files + +| File | Reason | +|------|--------| +| none | - | + +## API Design + +``` +scripts/acceptance-public-release.sh [--version X.Y.Z] [--skip-network] [--json OUT] + exit 0 every executed probe passed + exit 1 at least one executed probe failed + exit 2 a probe could not execute (reported, does not silently pass) + exit 3 usage error +``` + +Structured output per probe: + +``` +PROBE installer PASS installed=1.21.16 manifest=1.21.16 sha=71cb8745... +PROBE cargo FAIL documented=1.21.16 observed=1.21.1 +PROBE homebrew FAIL formula=terraphim-ai missing (tap has terraphim-agent) +PROBE updater PASS check-update reports current +PROBE macos NOT-EXECUTED no macOS host +``` + +## Test Strategy + +### Unit Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_probe_result_tristate` | `scripts/tests/test-acceptance.sh` | Pass, fail and not-executed are distinct | +| `test_exit_codes` | same | Exit code reflects the worst probe outcome | + +### Integration Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_installer_probe_clean_container` | local and CI | Real install from the documented command | +| `test_all_documented_commands_covered` | local and CI | No documented command is unaccounted for | +| `test_run_against_live_channel` | pre-announcement run | End-to-end truth | + +No mocks: every probe contacts the live public channel; the container is stock and unfurnished. + +### Property Tests + +Not applicable. + +## Implementation Steps + +### Step 1: Orchestrator and result model + +**Files:** `scripts/acceptance-public-release.sh` +**Description:** Probe registry, tristate results, verdict table, exit codes, JSON output. +**Tests:** unit tests +**Estimated:** 2 hours + +### Step 2: Installer probe + +**Files:** `scripts/probes/installer.sh` +**Description:** `docker run --rm debian:bookworm-slim` with the documented one-liner; assert version equals manifest and the binary runs. +**Tests:** integration test +**Estimated:** 2 hours + +### Step 3: Cargo, homebrew and updater probes + +**Files:** `scripts/probes/cargo-claim.sh`, `homebrew.sh`, `updater.sh` +**Description:** crates.io claim check; formula presence and checksum parity with Linuxbrew execution where available; updater check against the channel. +**Tests:** integration tests +**Estimated:** 3 hours + +### Step 4: Site coverage probe + +**Files:** `scripts/probes/site-coverage.sh` +**Description:** Extract documented commands from the site and assert each maps to a probe. +**Tests:** integration test +**Estimated:** 1 hour + +### Step 5: Wire into the release process + +**Files:** `docs/release-operator-checklist.md`, workflow +**Description:** Run before announcement and after the release workflow. +**Dependencies:** Steps 1 to 4 +**Estimated:** 1 hour + +## Rollback Plan + +1. The run is additive and consumes published artefacts; removing it cannot damage a release. +2. If a probe proves unreliable, it can be downgraded to not-executed with a stated reason rather than deleted. + +## Migration (if applicable) + +Not applicable. + +## Dependencies + +### New Dependencies + +| Dependency | Version | Justification | +|------------|---------|---------------| +| docker | present locally | Clean-host installer probe | +| curl, python3 | present | Manifest parsing | + +### Dependency Updates + +| Dependency | From | To | Reason | +|------------|------|-----|--------| +| none | - | - | - | + +## Performance Considerations + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Total runtime | < 10 minutes | timed per run | +| Installer probe runtime | < 3 minutes | timed in container | + +## Open Items + +| Item | Status | Owner | +|------|--------|-------| +| Whether the run gates or follows the release workflow | Pending | release owner | +| Linuxbrew host for non-macOS brew evidence | Pending | release owner | +| Site source ownership for the coverage probe | Pending | release owner | + +## Approval + +- [ ] Technical review complete +- [ ] Test strategy approved +- [ ] Human approval received \ No newline at end of file diff --git a/docs/plans/design-site-installer-2026-09-27.md b/docs/plans/design-site-installer-2026-09-27.md new file mode 100644 index 00000000..97a7d20a --- /dev/null +++ b/docs/plans/design-site-installer-2026-09-27.md @@ -0,0 +1,288 @@ +# Implementation Plan: Manifest-driven public installer at 1.21.16 + +**Status**: Draft +**Research Doc**: `docs/plans/research-site-installer-stale-release-2026-09-27.md` +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Phase**: 2 (disciplined-design) +**Estimated Effort**: 1 day + +## Overview + +### Summary + +Replace the installer's GitHub-Releases resolution with resolution from the published channel manifest, add SHA-256 verification, and make the installer fail closed when it cannot deliver the current release. This converts the documented site command from "installs v1.21.3" to "installs 1.21.16, verified". + +### Approach + +The channel manifest (`https://downloads.terraphim.ai//stable-v2.json`) already carries `version`, per-platform `assets[target].path`, `sha256` and `size`. The installer should read exactly that, and nothing else, for version and integrity. GitHub Releases becomes an optional mirror fallback for the archive bytes, never the source of truth for which version is current. + +### Scope + +**In Scope:** +- Manifest-driven version, platform path and SHA-256 resolution in `scripts/install.sh`. +- SHA-256 verification before install, with `sha256sum`/`shasum -a 256` detection. +- Fail-closed behaviour and a `--version` pin that refuses a version absent from the manifest. +- Descriptive User-Agent so Cloudflare bot management does not 403 the manifest fetch. +- A validation script asserting the documented command installs the manifest version. + +**Out of Scope:** +- Rebuilding, re-promoting or re-tagging 1.21.16. +- Archive naming changes, new platforms, or WSL-to-native Windows support. +- The crates.io and Homebrew formula-name defects (separate items). +- Any Caddy, Gitea or private-infrastructure change. + +**Avoid At All Cost** (from 5/25 analysis): + +| Rejected | Why | +|---|---| +| Publishing a `terraphim-ai` v1.21.16 release to satisfy the old installer | Creates a second public release line and two sources of truth | +| A compiled or packaged installer | Adds distribution surface for no user benefit | +| Supporting arbitrary version strings by guessing URLs | The manifest is authoritative; guessing reintroduces drift | +| Rewriting the installer while also changing its CLI surface | Couples two risks; the invocation must stay stable | + +## Architecture + +### Component Diagram + +``` +terraphim.ai copy + | + v +scripts/install.sh ------------------------------+ + detect os/arch -> map to target triple | + fetch manifest -> downloads.terraphim.ai/... | + select version -> manifest.version (or --version pinned, must exist) + select asset -> manifest.assets[target].path + download bytes -> R2 path, then GitHub mirror fallback + verify sha256 -> manifest.assets[target].sha256 + extract+install -> ~/.local/bin +``` + +### Data Flow + +``` +manifest GET -> {version, assets{target:{path,sha256,size}}} + -> GET https://downloads.terraphim.ai/ + -> sha256(bytes) == assets[target].sha256 ? install : abort +``` + +### Key Design Decisions + +| Decision | Rationale | Alternatives Rejected | +|---|---|---| +| Manifest is the sole source of version and hashes | It is generated by promotion and already verified for all 20 archives | GitHub Releases API; hardcoded versions | +| GitHub release archive as mirror fallback only | Resilience if the channel is briefly unreachable | R2-only; API-only | +| Verify SHA-256 before extraction | Prevents partial or substituted bytes executing | No verification; size-only check | +| Timeout on all network calls | Prevents hanging CI and user shells | Assume network always fast | +| Descriptive User-Agent on manifest fetch | Cloudflare bot management 403s default UAs | Rely on default UA | + +### Eliminated Options (Essentialism) + +| Option Rejected | Why Rejected | Risk of Including | +|---|---|---| +| Pinned version baked into the script | Reintroduces the drift being fixed | Silent staleness on every release | +| Multi-source merge of R2 and GitHub versions | Ambiguity about which version is current | Non-deterministic installs | +| Self-updating the installer | Out of vital few | New failure mode at install time | + +### Simplicity Check + +**What if this could be easy?** It can: read one JSON document, download the one path it names, compare one hash, extract, install. That is the whole design. No caching, no retry ladder, no plugin map, no configuration file. The mirror fallback is a single additional URL, attempted only when the primary fetch fails. + +**Senior Engineer Test**: A senior engineer would accept this. The only additions beyond the minimum are the mirror fallback and timeout handling, both justified by observed failures. + +**Nothing Speculative Checklist**: +- [x] No features the user didn't request +- [x] No abstractions "in case we need them later" +- [x] No flexibility "just in case" +- [x] No error handling for scenarios that cannot occur +- [x] No premature optimization + +## File Changes + +### New Files + +| File | Purpose | +|------|---------| +| `scripts/install.sh` (in `terraphim-clients`) | Manifest-driven installer, replacing the `terraphim-ai` copy as the maintained source | +| `scripts/validate-installer.sh` | Asserts the documented command installs the manifest version and hash | +| `docs/plans/design-site-installer-2026-09-27.md` | This plan | + +### Modified Files + +| File | Changes | +|------|---------| +| Site install section (location to be confirmed) | Point the one-liner at the maintained installer and fix the `brew install terraphim-ai` line | +| `.github/workflows/` scheduled or release validation | Invoke `scripts/validate-installer.sh` | + +### Deleted Files + +| File | Reason | +|------|--------| +| none | The `terraphim-ai` copy is left in place until the site URL is repointed, then removed in a follow-up | + +## API Design + +### Installer CLI (must remain backward compatible) + +``` +install.sh [--install-dir DIR] [--with-cli] [--cli-only] + [--version X.Y.Z] [--skip-verify] [--verbose] +``` + +Existing flags and defaults are preserved: default install dir `$HOME/.local/bin`, default tool `terraphim-agent`. + +### Internal Functions + +```bash +# Map uname output to a channel target triple (e.g. x86_64-unknown-linux-musl) +map_target() -> target + +# Fetch the channel manifest for a binary with a descriptive User-Agent +fetch_manifest(binary) -> json + +# Read manifest.version +manifest_version(json) -> version + +# Read manifest.assets[target].{path,sha256,size} +manifest_asset(json, target) -> path sha256 size + +# Download a path from the channel, falling back to the GitHub mirror +download(path, version) -> file + +# Verify a file against an expected sha256, using sha256sum or shasum -a 256 +verify_sha256(file, expected) -> 0/1 + +# Install the extracted binary into the install dir +install_binary(binary, dir) +``` + +### Error Types (exit codes) + +```rust +// Rust is not used here; behaviour is expressed as exit codes. +// 0 success +// 1 usage error (unsupported architecture, bad flag) +// 2 manifest unreachable or malformed +// 3 requested version absent from manifest +// 4 download failed from all sources +// 5 sha256 mismatch (fail closed, nothing installed) +``` + +## Test Strategy + +### Unit Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_map_target_linux_x86_64` | `scripts/validate-installer.sh` | Triple mapping is exact | +| `test_map_target_linux_aarch64` | `scripts/validate-installer.sh` | Triple mapping is exact | +| `test_manifest_parse` | `scripts/validate-installer.sh` | Version and asset fields read correctly | +| `test_sha256_verification_rejects_tamper` | `scripts/validate-installer.sh` | A corrupted byte fails closed (exit 5) | +| `test_missing_version_exits_3` | `scripts/validate-installer.sh` | Pinned absent version is refused | + +### Integration Tests + +| Test | Location | Purpose | +|------|----------|---------| +| `test_installs_manifest_version` | `scripts/validate-installer.sh` | Clean container: installed `--version` equals `manifest.version` | +| `test_installed_hash_matches_manifest` | `scripts/validate-installer.sh` | Installed binary derives from the verified archive | +| `test_documented_command` | `scripts/validate-installer.sh` | The verbatim site command succeeds | + +No mocks: every test exercises the live channel and real archives. Where a failure path needs a tampered archive, the test creates it locally from downloaded bytes rather than faking a server. + +### Property Tests + +Not applicable to a shell installer; the tamper test above provides the equivalent invariant (any single-byte change must abort). + +## Implementation Steps + +### Step 1: Installer core + +**Files:** `scripts/install.sh` +**Description:** Manifest fetch with descriptive User-Agent and timeouts; target mapping; version selection; asset path and hash extraction; download with mirror fallback; SHA-256 verification; fail-closed exit codes; keep existing flags and defaults. +**Tests:** manifest parse, target mapping, tamper rejection, missing version +**Estimated:** 4 hours + +### Step 2: Validation script + +**Files:** `scripts/validate-installer.sh` +**Description:** Spin a clean `debian:stable-slim` container, run the documented one-liner, assert installed version equals `manifest.version` and that the binary runs. +**Tests:** the integration tests above +**Dependencies:** Step 1 +**Estimated:** 2 hours + +### Step 3: Wire into CI + +**Files:** `.github/workflows/ci.yml` or a scheduled workflow +**Description:** Run the validation script on release and on a schedule so drift is caught without a human. +**Tests:** workflow run output +**Dependencies:** Step 2 +**Estimated:** 1 hour + +### Step 4: Repoint the documented command + +**Files:** site install section; `terraphim-ai` copy deprecated +**Description:** Change the one-liner to the maintained installer and correct the `brew install terraphim-ai` line. +**Tests:** manual re-run of the site command against the live site copy +**Dependencies:** Steps 1 to 3 +**Estimated:** 1 hour + +### Step 5: Record the evidence + +**Files:** release record for v1.21.16 +**Description:** Archive the container transcript showing 1.21.16 installed from the documented command, with hashes. +**Dependencies:** Step 4 +**Estimated:** 30 minutes + +## Rollback Plan + +1. The previous installer remains in `terraphim-ai` until Step 4 completes, so reverting the site copy restores the prior behaviour. +2. The installer change is a single script; `git revert` of the commit restores it. +3. No channel or release artefact is modified by this plan, so no release-level rollback is required. + +## Migration (if applicable) + +Not applicable; no data or schema migration. + +## Dependencies + +### New Dependencies + +| Dependency | Version | Justification | +|------------|---------|---------------| +| none | - | Uses bash, curl or wget, tar, coreutils or shasum only | + +### Dependency Updates + +| Dependency | From | To | Reason | +|------------|------|-----|--------| +| none | - | - | - | + +## Performance Considerations + +### Expected Performance + +| Metric | Target | Measurement | +|--------|--------|-------------| +| Added latency for manifest fetch | < 1 s | timed during validation | +| Total install time on 10 Mbit link | < 60 s | timed in container | + +### Benchmarks to Add + +Not applicable; a shell installer's cost is dominated by the archive download. + +## Open Items + +| Item | Status | Owner | +|------|--------|-------| +| Confirm installer's canonical home (`terraphim-clients` vs `terraphim-ai`) | Pending | release owner | +| Confirm default policy: `latest` from manifest vs explicit pin | Pending | release owner | +| Locate the site source for the install section | Pending | release owner | + +## Approval + +- [ ] Technical review complete +- [ ] Test strategy approved +- [ ] Performance targets agreed +- [ ] Human approval received \ No newline at end of file diff --git a/docs/plans/research-crates-io-parity-2026-09-27.md b/docs/plans/research-crates-io-parity-2026-09-27.md new file mode 100644 index 00000000..9df0443d --- /dev/null +++ b/docs/plans/research-crates-io-parity-2026-09-27.md @@ -0,0 +1,287 @@ +# Research Document: `cargo install terraphim-agent` serves crates.io binaries 13 patch releases behind + +**Status**: Draft +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Reviewers**: pending +**Phase**: 1 (disciplined-research) + +## Executive Summary + +terraphim.ai's headline install command is `cargo install terraphim-agent`, but crates.io serves 1.21.1 (agent and cli) and 1.21.2 (grep) while the release channel, the GitHub release and the Homebrew tap are all at 1.21.16. The gap is not an oversight in the release pipeline: the crates depend on `terraphim_config`, `terraphim_persistence`, `terraphim_settings`, `terraphim_router`, `terraphim_tracker` and `terraphim-markdown-parser` at versions that exist only on the private Gitea registry, and `publish-crates.yml` additionally refuses `terraphim_agent` outright because its dependency graph resolves only against that registry. Publishing the current binaries to crates.io therefore requires publishing the whole dependency family to crates.io first, which is a separate, larger programme. + +## Essential Questions Check + +| Question | Answer | Evidence | +|----------|--------|----------| +| Energizing? | Yes | It is the first command a user is told to run | +| Leverages strengths? | Partially | The publish machinery exists, but the dependency family is the blocker, not the pipeline | +| Meets real need? | Yes | A 13-patch-behind install is a real user-visible defect | + +**Proceed**: Yes (2/3). Note the second answer is the honest weak point: the fix is dependency work, not release work. + +## Problem Statement + +### Description + +Three documented install paths disagree about the current version: + +| Path | Version served | Verified | +|---|---|---| +| crates.io: `cargo install terraphim-agent` | 1.21.1 (2026-08-10) | Yes | +| crates.io: `cargo install terraphim-cli` | 1.21.1 (2026-08-10) | Yes | +| crates.io: `cargo install terraphim-grep` | 1.21.2 (2026-08-12) | Yes | +| R2 channel `downloads.terraphim.ai` | 1.21.16 | Yes, all 20 archives hash-verified | +| Homebrew tap | 1.21.16 | Yes | +| GitHub release v1.21.16 | 1.21.16, 21 assets | Yes | + +### Impact + +A user who prefers cargo - the most cargo-culted path for a Rust tool - gets code four weeks and thirteen patches old, including fixes the release notes advertise. Users then report issues already fixed, and the visible version is inconsistent with every other channel. + +### Success Criteria + +1. Every documented install path either delivers 1.21.16 or states plainly which version it delivers and why. +2. No documented path silently delivers an older line without an explicit, user-visible note. +3. If crates.io is to carry the binaries, the family publish is proven for one release, reproducibly, by automation. + +## Current State Analysis + +### Existing Implementation + +- `publish-crates.yml` is `workflow_dispatch` only (no tag trigger). It refuses `terraphim_agent` with the message "terraphim_agent is private-registry-only: its install graph (terraphim_sessions >= 1.21.2 with cursor-connector) resolves only against the terraphim registry, so a crates.io publish would ship an uninstallable package (#95)". +- It then strips `registry = "terraphim"` refs from every manifest and publishes the requested crates in dependency order with `CARGO_REGISTRY_TOKEN`, skipping versions that already exist. +- `Cargo.toml` carries an explicit comment block explaining that `terraphim_config`, `terraphim_persistence`, `terraphim_settings`, `terraphim_router`, `terraphim_tracker` and `terraphim-markdown-parser` are Gitea-only, and that crates.io 1.20.4 copies of the last four drag in a second `terraphim_config`/`terraphim_types` graph producing "expected ConfigState, found ConfigState" errors. Multi-repo publish tracking is `terraphim/terraphim-core #71`; `Refs #112`. + +### Code Locations + +| Component | Location | Purpose | +|---|---|---| +| Publish workflow | `.github/workflows/publish-crates.yml` | Manual, dependency-ordered crates.io publish with a refusal guard | +| Registry pins | `Cargo.toml` lines 55-85 | Which deps come from the private registry and why | +| Workspace version | `Cargo.toml` `[workspace.package] version = "1.21.16"` | Single version for the family | +| Prior research | `crates/terraphim_grep/RELEASE_RESEARCH.md`, `RELEASE_DESIGN.md` | Earlier analysis of crates.io publishing for grep | + +### Data Flow + +`cargo install terraphim-agent` -> crates.io index -> terraphim-agent 1.21.1 -> transitive deps from crates.io. The published 1.21.1 graph resolved at publish time, which is why it exists at all; the later pins to Gitea-only versions are what stop a repeat. + +### Integration Points + +- crates.io API and index (`https://crates.io/api/v1/crates/` returns `newest_version`; 403s without a User-Agent from datacenter IPs). +- Private Gitea registry, which continues to host the family (out of scope here). + +## Constraints + +### Technical Constraints + +- `cargo publish` rejects manifests whose dependencies pin a non-default registry. +- The dependency family must be present on crates.io at compatible versions before any binary crate can be published there. +- crates.io 1.20.4 copies of four family members are incompatible with this tree; versions must be chosen deliberately, not by "latest". +- Publishing is irreversible: a version, once published, cannot be re-published or deleted (only yanked). + +### Business Constraints + +- crates.io releases are permanent and public; a mistake is visible to every `cargo install` user. +- The multi-repo publish work is already tracked (`terraphim-core #71`); duplicating it here would create conflicting plans. + +### Non-Functional Requirements + +| Requirement | Target | Current | +|-------------|--------|---------| +| Documented channel accuracy | version served matches release notes | 1.21.1 vs 1.21.16 | +| Publish reproducibility | one automated path per release | manual, family incomplete | + +## Vital Few (Essentialism) + +### Essential Constraints (Max 3) + +| Constraint | Why It's Vital | Evidence | +|---|---|---| +| The user-visible link must state the true version | A wrong version claim destroys trust in every channel | `cargo install` is advertised first on the site | +| Publishing to crates.io cannot ship an uninstallable crate | An uninstallable package is worse than a stale one | The #95 refusal guard exists for this reason | +| Choosing a channel policy must be explicit | The current state is an undocumented accident | No note anywhere says crates.io lags | + +### Eliminated from Scope + +| Eliminated Item | Why Eliminated | +|-----------------|----------------| +| Publishing the whole dependency family to crates.io in this pass | It is `terraphim-core #71`; a multi-repo, irreversible programme | +| Removing `registry = "terraphim"` pins from the tree | It changes how the product builds and needs its own research | +| Publishing a 1.21.x bump of the stale crates without the family | It would be refused by the guard or ship an unresolvable graph | +| Yanking the stale crates.io versions | Breaking existing pinned users without a migration path | + +## Dependencies + +### Internal Dependencies + +| Dependency | Impact | Risk | +|---|---|---| +| `publish-crates.yml` | The only publish path; its refusal guard is a safety net, not a solution | Low | +| `Cargo.toml` registry pins | Determine whether a crates.io publish is even resolvable | High | +| `terraphim-core #71` | Owns the real family publish | High, external to this repo | + +### External Dependencies + +| Dependency | Version | Risk | Alternative | +|---|---|---|---| +| crates.io | live | Irreversible publishes; rate limits | none | +| `CARGO_REGISTRY_TOKEN` secret | present | Missing or expired blocks publish | 1Password | + +## Risks and Unknowns + +### Known Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| User publishes a broken crate while aiming for parity | Medium | High | Keep the #95 guard; dry-run first; family-first ordering | +| Documentation note drifts from reality later | High | Medium | Derive the note from the published version at build time where possible | +| Duplicating `terraphim-core #71` work | Medium | Medium | Reference the issue; publish nothing while it is open | + +### Open Questions + +1. Which option for #333? (a) publish family plus binaries, tracked under `terraphim-core #71`; (b) keep crates.io deliberately lagging and document it; (c) keep lagging and steer cargo users to the installer. +2. If (b) or (c): should the stale crates carry a README note, a site footnote, or both? +3. Should `terraphim-grep`, whose dependency graph is simpler, be published first as a proof? (Owner: release owner) + +### Assumptions Explicitly Stated + +| Assumption | Basis | Risk if Wrong | Verified? | +|---|---|---|---| +| The `terraphim_agent` refusal is still correct | In-tree guard with a specific rationale | Publishing would ship an uninstallable crate | No, not re-tested | +| crates.io 1.21.1/1.21.2 installs still resolve | They are published and older | Less relevant | No | +| The family publish is genuinely multi-repo | `terraphim-core #71` reference | Effort estimate wrong | Partially | + +### Multiple Interpretations Considered + +| Interpretation | Implications | Why Chosen/Rejected | +|---|---|---| +| "Publish everything to crates.io" | Correct long term, large, cross-repo, irreversible | Deferred to `terraphim-core #71` | +| "Document crates.io as intentionally lagging" | Cheap, honest, no irreversible action | Viable candidate for this release | +| "Steer cargo users to the installer" | Fixes the user outcome without crates.io work | Viable candidate, composes with the installer fix | + +## Research Findings + +### Key Insights + +1. The blocking dependency is the family, not the publish pipeline. +2. Exactly one crate is structurally refused (`terraphim_agent`); the others are blocked only by resolver reality. +3. Four family members exist on crates.io at 1.20.4 but are known-incompatible with this tree, so "publish and let the resolver sort it out" is unsafe. +4. A cheap, honest fix (document the lag and steer users) delivers most of the user benefit with none of the irreversibility. + +### Relevant Prior Art + +- `crates/terraphim_grep/RELEASE_RESEARCH.md` reached the same conclusion earlier: publishing grep requires the family first. +- The existing #95 guard demonstrates the project already decided that shipping an unresolvable crate is unacceptable. + +### Technical Spikes Needed + +| Spike | Purpose | Estimated Effort | +|-------|---------|------------------| +| Dry-run publish of terraphim-grep only | Establish how far the resolver gets before the family blocks it | 2 hours | +| Inventory of family versions on crates.io vs required | Size the family publish | 1 hour | + +## Recommendations + +### Proceed/No-Proceed + +Proceed, with scope limited to accuracy of the documented channel. Do not attempt the family publish in this pass. + +### Scope Recommendations + +Preferred for this release: correct the documented statement of what `cargo install` delivers, and point cargo users at the self-update path, which already works. Keep the family publish as a tracked, separately-owned programme. + +### Risk Mitigation Recommendations + +Keep the #95 guard. Use `--dry-run` before any real publish. Never publish a version that cannot be reproduced from a tag. + +## Next Steps + +If approved: +1. Phase 2 design: how the site and crate READMEs state the channel situation, and how the cargo path is steered. +2. Confirm the policy choice with the release owner (option a, b or c). +3. If the policy is (b) or (c), implement the documentation change and validate it as part of the public-release acceptance run. + +## Appendix + +### Evidence Captured 2026-09-27 + +- crates.io `newest_version`: terraphim-agent 1.21.1 (2026-08-10), terraphim-grep 1.21.2 (2026-08-12), terraphim-cli 1.21.1 (2026-08-10). +- Family on crates.io: terraphim_types 1.22.1, terraphim_automata 1.21.1, terraphim_service 1.20.6, terraphim_config 1.20.4, terraphim_persistence 1.20.4, terraphim_settings 1.20.4, terraphim_router 1.20.4, terraphim_tracker 1.20.4, terraphim_file_search 1.20.3, terraphim_middleware 1.20.3, terraphim_rolegraph 1.20.4, terraphim_orchestrator 1.20.2, terraphim-markdown-parser 1.20.4. +- `Cargo.toml` rationale block lines 55-85, including the exact-pin comment for `terraphim_config` and `terraphim_persistence` (1.20.4 yanked on Gitea; crates.io 1.20.4 copies cause duplicate-graph type errors). +- `publish-crates.yml` refusal message and dependency-ordered publish loop. +--- + +## Addendum (2026-09-27): the family publish was attempted against live infrastructure + +Decision 2 authorised publishing the dependency family with the existing +scripts. The publish path was executed and the blocker is now measured rather +than inferred. + +### Path taken + +`terraphim-ai/scripts/adf-setup/polyrepo-publish/polyrepo-publish.sh` is the +family publisher. It names one clone of `terraphim-clients` on Gitea and walks +it through Gitea CI, a registry-stripped rewrite, a GitHub mirror push, GitHub +CI, and a crates.io dispatch. It is a CI-orchestration harness rather than an +idempotent publish command, and `dispatch` covers six repositories in +topological order. + +That harness duplicates a mechanism `terraphim-clients` already carries: the +repository's own `publish-crates.yml` accepts a `crate_list` and strips the +private-registry pins itself. The canonical `main` at `fb1a575` also already +contains the released `v1.21.16` tag, so the Gitea-CI gate the harness exists +to provide has already run. Dispatching the repository's workflow directly is +therefore the equivalent operation on the already-validated tree, and it is +the path that was used. + +### Result + +A dry run of the full client family on `main` (run 36314289075) reached the +first crate that must be built from a crates.io-resolved graph and failed: + +``` +error[E0432]: unresolved import `terraphim_automata::parse_markdown_directives_dir` + --> terraphim_config-1.20.4/src/lib.rs:24:21 + note: the item is gated behind the `fs-traversal` feature +error: could not compile `terraphim_config` (lib) +error: failed to verify package tarball +``` + +The complete causal chain, now verified end to end: + +1. `cargo install` on any client crate resolves against crates.io. +2. crates.io carries `terraphim_config` and `terraphim_persistence` at 1.20.4, + while the client crates require 1.20.2 (pinned exactly, because 1.20.4 is + yanked on the Gitea registry). +3. crates.io's `terraphim_automata` is 1.21.1, which gates + `parse_markdown_directives_dir` behind `fs-traversal`, a feature + `terraphim_config` 1.20.4 does not enable. +4. The build of `terraphim_config` 1.20.4 therefore fails, and no client crate + can be published until that is fixed. + +This is exactly the failure the `[patch.crates-io]` block in the clients +`Cargo.toml` documents, reached through the publish pipeline itself. + +### What this means for the options + +| Option | Verdict | +|---|---| +| Publish the client crates now | Blocked: no client crate is publishable while `terraphim_config` 1.20.4 fails to build | +| Publish the family from `terraphim-config-persistence` and `terraphim-core` first | Required, and a larger programme: it spans two other repositories, and each must first carry a green Gitea CI on its own `main` | +| Remove `[patch.crates-io]` and the registry pins in `terraphim-clients` | Not available until the family is on crates.io at matching versions; doing it first breaks the build | + +The dry run also established two smaller facts worth keeping: + +- `terraphim_update` 1.20.2 and `terraphim_command_runtime` 0.1.0 already exist + on crates.io, so those two are no-ops in any future `crate_list`. +- `publish-crates.yml` correctly refuses `terraphim_agent` before any manifest + mutation (issue #95); that guard worked as designed. + +### Consequence for this release + +`cargo install terraphim_agent` cannot be made to serve 1.21.16 by publishing +from `terraphim-clients` alone. The two paths that can serve 1.21.16 today are +the channel installer and Homebrew, and the website was updated to lead with +those and to state plainly what the Cargo path does. Completing the Cargo path +is a cross-repository dependency programme, not a step in this release. diff --git a/docs/plans/research-homebrew-documented-command-2026-09-27.md b/docs/plans/research-homebrew-documented-command-2026-09-27.md new file mode 100644 index 00000000..8c099907 --- /dev/null +++ b/docs/plans/research-homebrew-documented-command-2026-09-27.md @@ -0,0 +1,203 @@ +# Research Document: Documented Homebrew command names a formula that does not exist + +**Status**: Draft +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Reviewers**: pending +**Phase**: 1 (disciplined-research) + +## Executive Summary + +terraphim.ai documents `brew tap terraphim/terraphim && brew install terraphim-ai`. No repository named `terraphim/homebrew-terraphim` formula called `terraphim-ai` exists, and the tap repository itself is `terraphim/homebrew-terraphim`, which the command's `terraphim/terraphim` tap shorthand would not resolve to. The tap does contain correct, checksum-verified formulae for `terraphim-agent` and `terraphim-grep` at 1.21.16. So the tap is healthy and the documented command is wrong: the user-facing defect is one line of copy plus a tap-name mismatch. + +## Essential Questions Check + +| Question | Answer | Evidence | +|----------|--------|----------| +| Energizing? | Yes | It is a documented command that cannot succeed | +| Leverages strengths? | Yes | The formulae already exist and verify against the manifest | +| Meets real need? | Yes | Homebrew is a primary macOS and Linuxbrew path | + +**Proceed**: Yes (3/3). + +## Problem Statement + +### Description + +The site documents `brew tap terraphim/terraphim && brew install terraphim-ai`. Reality, verified 2026-09-27: + +- Tap repository: `terraphim/homebrew-terraphim` (tap shorthand `terraphim/terraphim`). +- Formulae present: `terraphim-agent.rb`, `terraphim-grep.rb`, `terraphim-server.rb`. +- No formula named `terraphim-ai`. +- `terraphim-agent.rb` and `terraphim-grep.rb` are at 1.21.16, with checksums matching the published manifest for `universal-apple-darwin`, `x86_64-unknown-linux-gnu` and `aarch64-unknown-linux-musl`. + +### Impact + +A user following the documented Homebrew command fails at the second step (`brew install terraphim-ai`: no such formula). If the tap shorthand is also wrong, the failure happens at the first step. Either way the documented path is broken while a working one exists under a different name. + +### Success Criteria + +1. The documented Homebrew command succeeds verbatim and installs the current version. +2. The documented tap name resolves to the real tap. +3. The tap carries the current release for every binary the site advertises. + +## Current State Analysis + +### Existing Implementation + +- Tap `terraphim/homebrew-terraphim`: `terraphim-agent.rb` and `terraphim-grep.rb` at v1.21.16; `terraphim-server.rb` pins terraphim-ai v1.20.5 assets. +- `terraphim-agent.rb` selects `universal-apple-darwin` on macOS and musl/gnu on Linux, verifies SHA-256, has a `test do` block that runs `--version`, `learn --help`, `memory --help` and `sessions expand --help`, and on macOS runs `codesign --verify --all-architectures --deep --strict`. +- The tap's latest commit is a merge of PR #3 on 2026-09-25 ("Bump terraphim-agent and terraphim-grep to v1.21.16"). + +### Code Locations + +| Component | Location | Purpose | +|---|---|---| +| Agent formula | `terraphim/homebrew-terraphim` `Formula/terraphim-agent.rb` | Installs terraphim-agent 1.21.16 | +| Grep formula | `terraphim/homebrew-terraphim` `Formula/terraphim-grep.rb` | Installs terraphim-grep 1.21.16 | +| Server formula | `terraphim/homebrew-terraphim` `Formula/terraphim-server.rb` | Pins terraphim-ai v1.20.5 | +| Site copy | terraphim.ai install section | Documents `brew install terraphim-ai` | + +### Data Flow + +Site command -> tap resolution -> formula -> `downloads.terraphim.ai` archive (with a GitHub release mirror) -> SHA-256 check -> `bin.install`. + +### Integration Points + +- Homebrew tap naming convention: repository `homebrew-` maps to tap `/`. +- The formulae reference both the R2 channel and the GitHub release as a mirror, so both must stay valid. + +## Constraints + +### Technical Constraints + +- Homebrew requires the repository to be named `homebrew-` for `brew tap` to resolve the shorthand. +- Formulae must be valid Ruby and pass `brew audit` where practical. +- A macOS-specific codesign test only executes on macOS; Linuxbrew skips it. + +### Business Constraints + +- The tap is public; a formula that fails `brew install` is user-visible immediately. +- The site copy may live in a repository separate from this project. + +### Non-Functional Requirements + +| Requirement | Target | Current | +|---|---|---| +| Documented command succeeds | yes | no | +| Formula version | 1.21.16 | 1.21.16 for agent and grep | +| Checksum parity with manifest | exact | exact (verified) | + +## Vital Few (Essential Constraints) + +| Constraint | Why It's Vital | Evidence | +|---|---|---| +| Documented command must resolve | A command that cannot run is worse than no instruction | No `terraphim-ai` formula exists | +| Formulae must match the manifest | Guarantees the tap cannot serve mismatched bytes | Checksums match today | +| Tap name must be the real one | Otherwise the first command fails | Repository is `homebrew-terraphim` | + +### Eliminated from Scope + +| Eliminated Item | Why Eliminated | +|---|---| +| Creating a `terraphim-ai` formula | Inventing a formula to satisfy wrong copy | +| Renaming the tap | Breaks existing users and the merge history | +| Publishing to homebrew-core | Out of vital few; needs notability and review | +| Rewriting the formula structure | It already verifies and tests correctly | + +## Dependencies + +### Internal Dependencies + +| Dependency | Impact | Risk | +|---|---|---| +| Release promotion to R2 and GitHub | Formulae point at both | Low | + +### External Dependencies + +| Dependency | Version | Risk | Alternative | +|---|---|---|---| +| Homebrew | current | Formula DSL changes | pin the test to documented interfaces | +| GitHub releases | v3 | Asset renames break the mirror | R2 primary, already present | + +## Risks and Unknowns + +### Known Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| Copy fixed in one place and left stale elsewhere | High | Medium | Single checklist item in the release acceptance run | +| Formula fails on real macOS | Unknown | High | This is the separate macOS evidence item | +| `terraphim-server.rb` pins an old terraphim-ai release | Certain | Low | Out of scope for the clients release; note it | + +### Open Questions + +1. Is the site's Homebrew line meant to install the agent, the cli, or a bundle? (Owner: release owner) +2. Should `terraphim-cli` gain a formula, since the site lists `cargo install terraphim-cli`? (Owner: release owner) +3. Where is the site source? (Owner: release owner) + +### Assumptions Explicitly Stated + +| Assumption | Basis | Risk if Wrong | Verified? | +|---|---|---|---| +| The site intends the clients binaries | It also lists cargo installs for them | Wrong copy fixed | Partially | +| Tap shorthand in the copy is wrong | Repository naming convention | Unnecessary change | Yes, by naming rule | +| Checksums will keep matching | They match today and promotion writes both | Silent mismatch | Yes, today | + +### Multiple Interpretations Considered + +| Interpretation | Implications | Why Chosen/Rejected | +|---|---|---| +| Fix the copy to the real formula names | Smallest, honest change | Chosen | +| Add a `terraphim-ai` formula | Satisfies wrong copy, invents a product name | Rejected | +| Point the copy at the installer instead | Removes Homebrew entirely | Rejected; Homebrew is a real, working channel | + +## Research Findings + +### Key Insights + +1. The tap is correct and verified; only the documented command is wrong. +2. The defect is copy plus a tap-name mismatch, not formula work. +3. The tap's own test block already provides install-time validation, including codesign on macOS. +4. An inconsistency exists between channels: the site advertises cli, but the tap has no cli formula. + +### Relevant Prior Art + +- Tap PR #3 (merged 2026-09-25) demonstrates the bump procedure for this release. +- The formula mirrors the R2 primary with a GitHub fallback, matching the plan of record for distribution. + +### Technical Spikes Needed + +| Spike | Purpose | Estimated Effort | +|-------|---------|------------------| +| `brew install` on Linuxbrew for both formulae | First real install evidence on a Linux host | 1 hour | +| `brew audit` on the two formulae | Catch formula smells before macOS evidence | 30 minutes | + +## Recommendations + +### Proceed/No-Proceed + +Proceed. It is a small, low-risk fix with immediate user benefit. + +### Scope Recommendations + +Correct the documented command to the real tap and real formula names, decide whether cli should also have a formula, and record the `terraphim-server.rb` staleness as a separate observation. + +### Risk Mitigation Recommendations + +Add the Homebrew command to the public-release acceptance run so copy and formulae cannot drift apart again. + +## Next Steps + +If approved: +1. Phase 2 design: exact copy change, optional cli formula, and the validation step. +2. Confirm the intended set of Homebrew-installable binaries. + +## Appendix + +### Evidence Captured 2026-09-27 + +- `Formula/` contents: `terraphim-agent.rb`, `terraphim-grep.rb`, `terraphim-server.rb`. +- `terraphim-agent.rb` url `https://downloads.terraphim.ai/terraphim-agent/terraphim-agent-1.21.16-#{target}.tar.gz` with mirror to the GitHub release and SHA-256 `on_system_conditional`. +- Tap checksums match the manifest for `universal-apple-darwin`, `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-musl` for both agent and grep. +- Site copy verbatim: `brew tap terraphim/terraphim && brew install terraphim-ai`. \ No newline at end of file diff --git a/docs/plans/research-macos-homebrew-evidence-2026-09-27.md b/docs/plans/research-macos-homebrew-evidence-2026-09-27.md new file mode 100644 index 00000000..bcb29736 --- /dev/null +++ b/docs/plans/research-macos-homebrew-evidence-2026-09-27.md @@ -0,0 +1,189 @@ +# Research Document: Genuine macOS evidence for the Homebrew channel + +**Status**: Draft +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Reviewers**: pending +**Phase**: 1 (disciplined-research) + +## Executive Summary + +The release operator checklist requires genuine macOS evidence per release. None exists for any release: the only recorded install evidence is Linuxbrew. The formulae themselves are macOS-capable and include a macOS-only codesign assertion in their test block, so the gap is evidence, not capability. This item cannot be completed inside this environment: no macOS host is reachable, and Homebrew does not appear in any workflow other than the release workflow's `macos-15` runners, which build and sign but never install from the tap. + +## Essential Questions Check + +| Question | Answer | Evidence | +|----------|--------|----------| +| Energizing? | Partially | It is a verification gap, not a defect users currently hit | +| Leverages strengths? | Yes | GitHub provides `macos-15` runners already used by the release workflow | +| Meets real need? | Yes | The checklist requires it and macOS is the primary Homebrew platform | + +**Proceed**: Yes (2/3); the weak answer is honest - this is evidence work rather than a user-visible fix. + +## Problem Statement + +### Description + +Every release announces Homebrew support for macOS. The macOS-specific parts of the formulae (universal binary selection, the codesign verification in `test do`) have never been exercised on macOS in a recorded way. + +### Impact + +A macOS-specific breakage in the tap would be discovered by users rather than by the release process. The release currently claims more validation than it has. + +### Success Criteria + +1. A recorded macOS transcript showing install from the tap, version output, a real command, and upgrade to the next release. +2. The transcript is archived in the release record. +3. The Rosetta provisioning gate for the arm64 signing lane is rehearsed and recorded. + +## Current State Analysis + +### Existing Implementation + +- `terraphim-agent.rb` and `terraphim-grep.rb` select `universal-apple-darwin` on macOS and verify SHA-256. +- `test do` runs `--version`, `learn --help`, `memory --help`, `sessions expand --help`, and, when `OS.mac?`, `codesign --verify --all-architectures --deep --strict`. +- `sign-and-notarize-macos` and `create-universal-macos` run on `macos-15` in the release workflow and produce signed, notarised bytes. +- No workflow installs from the tap on macOS; no transcript exists. + +### Code Locations + +| Component | Location | Purpose | +|---|---|---| +| Agent formula | tap `Formula/terraphim-agent.rb` | macOS and Linux install plus codesign test | +| Grep formula | tap `Formula/terraphim-grep.rb` | Same shape | +| macOS build and sign | `release-binaries.yml` jobs `create-universal-macos`, `sign-and-notarize-macos` | Produce signed bytes | +| Checklist | `docs/release-operator-checklist.md` section 6 | Defines the required evidence | + +### Data Flow + +Tap -> formula -> `downloads.terraphim.ai` universal archive -> SHA-256 -> `bin.install` -> `brew test` codesign check. + +### Integration Points + +- GitHub-hosted `macos-15` runners are already in use, so a tap-install job adds no new infrastructure. +- `macos-15-intel` is the thin x86_64 lane referenced by the checklist. + +## Constraints + +### Technical Constraints + +- `codesign --verify --all-architectures` executes only on macOS. +- Rosetta availability on arm64 runners gates the x86_64 lane; the checklist calls this a provisioning gate. +- No macOS host is reachable from this environment. + +### Business Constraints + +- GitHub macOS runner minutes are metered; the job should be short. +- The evidence must be per release, so the check should be automated rather than manual. + +### Non-Functional Requirements + +| Requirement | Target | Current | +|-------------|--------|---------| +| macOS install evidence per release | recorded transcript | none | +| Runtime on a macOS runner | under 10 minutes | n/a | + +## Vital Few (Essential Constraints) + +| Constraint | Why It's Vital | Evidence | +|---|---|---| +| Evidence must come from a real macOS host | A Linuxbrew pass proves nothing about macOS | The formulary contains macOS-only logic | +| Evidence must be archived per release | Evidence that is not recorded did not happen | No transcript exists today | +| The check should be automated | Manual evidence will not survive future releases | The gap exists precisely because it was manual | + +### Eliminated from Scope + +| Eliminated Item | Why Eliminated | +|---|---| +| Provisioning a permanent macOS host | Cost without need; GitHub runners already exist | +| Notarisation re-verification on the user side | Covered by the sign job | +| Intel Macs beyond the thin lane | The checklist names one lane | + +## Dependencies + +### Internal Dependencies + +| Dependency | Impact | Risk | +|---|---|---| +| Signed universal archives | Formulae point at them | Low; the sign job already passes | + +### External Dependencies + +| Dependency | Version | Risk | Alternative | +|---|---|---|---| +| GitHub `macos-15` runner | available | metered minutes, queueing | a self-hosted Mac, not present | +| Rosetta on arm64 runners | availability varies | x86_64 lane blocked | run the thin lane only on `macos-15-intel` | + +## Risks and Unknowns + +### Known Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| Codesign test fails on real macOS | Unknown | High | That is the point; find it before users do | +| Rosetta gate blocks the Intel lane | Medium | Medium | Record as blocked on that runner, not as a pass | +| Runner cost creep | Low | Low | Keep the job to install, version and one command | + +### Open Questions + +1. Is a GitHub `macos-15` runner acceptable evidence, or does the checklist require a human-operated Mac? (Owner: release owner) +2. Must the thin Intel lane be exercised for this release, or only rehearsed? (Owner: release owner) + +### Assumptions Explicitly Stated + +| Assumption | Basis | Risk if Wrong | Verified? | +|---|---|---|---| +| No local macOS host exists | ssh probes and `command -v` results | Effort estimate wrong | Yes | +| GitHub macOS runners are permitted | The release workflow already uses them | Blocked item | Yes | +| The formulae are macOS-capable | They contain macOS-conditional logic | Discovery of a real defect | Partially | + +## Research Findings + +### Key Insights + +1. The gap is evidence, not formula capability. +2. Automation on existing `macos-15` runners converts a manual checklist item into a repeatable check. +3. The codesign assertion already exists in the formula; it simply has never run on macOS. +4. The Intel lane depends on Rosetta provisioning, which the checklist already identifies as a gate. + +### Relevant Prior Art + +- `release-binaries.yml` `create-universal-macos` and `sign-and-notarize-macos` demonstrate the existing macOS runner usage. +- The tap's `test do` block is the ready-made macOS acceptance assertion. + +### Technical Spikes Needed + +| Spike | Purpose | Estimated Effort | +|-------|---------|------------------| +| Trial `brew install` from the tap on a macOS runner | Prove feasibility and capture the first transcript | 2 hours | +| Rosetta check on the arm64 runner | Confirm the Intel lane can run | 1 hour | + +## Recommendations + +### Proceed/No-Proceed + +Proceed as an automated check on an existing runner, not as a one-off manual exercise. + +### Scope Recommendations + +Install agent and grep, print versions, run one real command, exercise the codesign assertion, and archive the transcript in the release record. + +### Risk Mitigation Recommendations + +Do not report a Rosetta-blocked lane as passing; record it the same way the acceptance run handles not-executed. + +## Next Steps + +If approved: +1. Phase 2 design of the macOS evidence job. +2. Run it against the current release and archive the transcript. +3. Add the transcript to the release record for v1.21.16. + +## Appendix + +### Evidence Captured 2026-09-27 + +- No macOS host reachable; `bigbox` reports `Linux x86_64`. +- `command -v brew` absent locally; docker present but cannot run macOS. +- Workflows use `macos-15` and `macos-latest`; no tap-install job exists. +- Formula `test do` includes `codesign --verify --all-architectures --deep --strict` on macOS. \ No newline at end of file diff --git a/docs/plans/research-native-channels-deb-rpm-aur-2026-09-27.md b/docs/plans/research-native-channels-deb-rpm-aur-2026-09-27.md new file mode 100644 index 00000000..a28d697f --- /dev/null +++ b/docs/plans/research-native-channels-deb-rpm-aur-2026-09-27.md @@ -0,0 +1,195 @@ +# Research Document: Optional native channels (DEB/RPM and AUR) + +**Status**: Draft +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Reviewers**: pending +**Phase**: 1 (disciplined-research) + +## Executive Summary + +The release covers curl, cargo (stale, item 2), Homebrew (copy broken, item 3) and the self-updater. It does not cover native distribution: no DEB or RPM packages are published, and Arch users have no AUR package. Both were deferred for external reasons - DEB/RPM waits on central signing in the terraphim-ai repository, and the AUR submission needs `ssh://aur@aur.archlinux.org` credentials that are not available. Neither blocks the public release being correct for the channels it already claims; both extend reach. This item therefore plans them, states the blockers precisely, and does not pretend they are closeable here. + +## Essential Questions Check + +| Question | Answer | Evidence | +|----------|--------|----------| +| Energizing? | Partially | Reach expansion rather than defect repair | +| Leverages strengths? | Yes | The sealed stage already produces per-target archives with receipts | +| Meets real need? | Yes for Arch and enterprise Linux users | Two of the three documented OS families lack native packages | + +**Proceed**: Yes (2/3). Recommended sequencing is after the entry-point defects, which affect every user. + +## Problem Statement + +### Description + +| Channel | State | Blocker | +|---|---|---| +| DEB/RPM | Not published | Awaits central signing in the terraphim-ai repository | +| AUR | Not published | No `aur@aur.archlinux.org` SSH key or account access available | +| Omarchy | Not published | Consumes the AUR package; not on any critical path | + +### Impact + +Debian, Ubuntu, Fedora and RHEL users install through curl or Homebrew rather than their native package manager, and Arch users have no packaged option. The impact is reach, not correctness. + +### Success Criteria + +1. Native packages install, upgrade and remove cleanly on the documented matrices, owning only their binary, licence and receipt. +2. Receipts record `dpkg` or `rpm` under the documented path. +3. AUR submission regenerates `.SRCINFO` and pushes to `master` with the PKGBUILD. + +## Current State Analysis + +### Existing Implementation + +- The sealed stage produces per-target archives with SHA-256 sidecars, receipts and signatures. +- `nFPM` packaging work exists in the build pipeline but publication waits on external signing. +- `crates/terraphim_grep/RELEASE_RESEARCH.md` and `RELEASE_DESIGN.md` contain prior analysis of packaging for grep, including the dependency-ordered publish pattern. + +### Code Locations + +| Component | Location | Purpose | +|---|---|---| +| Package build | release workflow `build-client-packages` | Produces DEB/RPM artefacts | +| Sealed stage | `client-release-stage-*` | Archives, SHA256SUMS, receipts | +| Prior packaging research | `crates/terraphim_grep/RELEASE_RESEARCH.md` | Established packaging context | + +### Data Flow + +Sealed archives -> nFPM -> DEB/RPM -> repository publication (blocked) -> package manager install. + +AUR: PKGBUILD sources the published archives -> `.SRCINFO` -> push to `master` (blocked on credentials). + +### Integration Points + +- Native package managers (`dpkg`, `rpm`), their lint tools, and chroot-based build environments. +- `ssh://aur@aur.archlinux.org/terraphim-clients-bin.git` for submission. +- The central signing contract in the terraphim-ai repository. + +## Constraints + +### Technical Constraints + +- Each package must own only its binary, licence and receipt; MUSL packages declare no glibc or gcc runtime dependency. +- Split PKGBUILD outputs (`terraphim-agent-bin`, `terraphim-grep-bin`) must declare matching `provides` and `conflicts`. +- Reproducibility and exact payload SHA equivalence must hold against the sealed archives. + +### Business Constraints + +- Publication waits on external signing and external credentials; both are outside this repository. +- Package publication is user-visible and less reversible than a binary download. + +### Non-Functional Requirements + +| Requirement | Target | Current | +|-------------|--------|---------| +| Install, upgrade, remove on the documented matrices | clean | not published | +| Reproducible payload hashes | exact | not published | + +## Vital Few (Essential Constraints) + +| Constraint | Why It's Vital | Evidence | +|---|---|---| +| Package owns only its own files | Prevents conflicts with existing installs | Stated contract in the tracking issues | +| Payload hashes equal the sealed archives | Guarantees the packaged binary is the released binary | Reproducibility requirement | +| Blocker ownership must be explicit | Prevents this item from silently stalling the release | Two external blockers exist | + +### Eliminated from Scope + +| Eliminated Item | Why Eliminated | +|---|---| +| Publishing to a distro's official repositories | Separate, lengthy review process | +| Building a full APT/YUM repository service | Infrastructure beyond this release | +| Windows package managers | Not requested and no host to validate | + +## Dependencies + +### Internal Dependencies + +| Dependency | Impact | Risk | +|---|---|---| +| Sealed stage receipts | Package payload basis | Low | +| AUR PKGBUILD | Depends on published archive URLs | Low | + +### External Dependencies + +| Dependency | Version | Risk | Alternative | +|---|---|---|---| +| Central signing (terraphim-ai) | pending | blocks DEB/RPM publication | none | +| AUR account and SSH key | absent | blocks AUR submission | none | +| nFPM | pinned | packaging drift | pin by digest | + +## Risks and Unknowns + +### Known Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| Package conflicts with a curl-installed binary | Medium | Medium | Document coexistence; packages own `/usr/bin` only | +| AUR package drifts from the release | Medium | Medium | Generate `.SRCINFO` from the same manifest | +| Blockers persist indefinitely | Medium | Low (reach only) | State them in the release record rather than implying readiness | + +### Open Questions + +1. What is the current status of central signing, and who owns it? (Owner: terraphim-ai maintainer) +2. Can AUR credentials be provisioned into 1Password for automated submission? (Owner: release owner) +3. Is Omarchy in scope for this release or the next? (Owner: release owner) + +### Assumptions Explicitly Stated + +| Assumption | Basis | Risk if Wrong | Verified? | +|---|---|---|---| +| DEB/RPM publication genuinely waits on central signing | Recorded in the tracking issues | Effort misdirected | Partially | +| No AUR credentials are available | No AUR key present in the environment | Unnecessary deferral | Yes | +| Neither channel blocks user-facing correctness | Their binaries are also reachable via curl and Homebrew | Priority misjudged | Yes | + +## Research Findings + +### Key Insights + +1. Both channels extend reach; neither repairs a current defect. +2. Both have hard external dependencies, so planning must state them rather than absorb them. +3. The packaging contract is already written down in the tracking issues, which reduces design risk. + +### Relevant Prior Art + +- `crates/terraphim_grep/RELEASE_RESEARCH.md` and `RELEASE_DESIGN.md` for the packaging context and the dependency-ordered publish pattern. +- The release operator checklist's packaging section. + +### Technical Spikes Needed + +| Spike | Purpose | Estimated Effort | +|-------|---------|------------------| +| Dry-run nFPM on the sealed archives | Confirm package contents and receipts | 4 hours | +| PKGBUILD build in a clean chroot | Confirm split output ownership | 4 hours | + +## Recommendations + +### Proceed/No-Proceed + +Proceed with design and dry-run validation; do not publish until the blockers clear. + +### Scope Recommendations + +Keep DEB/RPM and AUR as one research item with two designs, because they share the sealed stage and the receipt contract. Treat Omarchy as a downstream consumer of the AUR package. + +### Risk Mitigation Recommendations + +Record the blockers in the release record with named owners, so the public release is not described as incomplete for reasons nobody can act on. + +## Next Steps + +If approved: +1. Phase 2 designs for the DEB/RPM and AUR paths. +2. Dry-run validation that changes no public surface. +3. Re-assess once central signing and AUR credentials are available. + +## Appendix + +### Evidence Captured 2026-09-27 + +- No AUR-specific SSH key in the environment; no Arch tooling present. +- The release workflow contains `build-client-packages`, whose publication path waits on external signing. +- Receipt contract: `dpkg` or `rpm` recorded under `/usr/share/terraphim/package-manager.d/`. \ No newline at end of file diff --git a/docs/plans/research-public-release-acceptance-2026-09-27.md b/docs/plans/research-public-release-acceptance-2026-09-27.md new file mode 100644 index 00000000..48cb76bf --- /dev/null +++ b/docs/plans/research-public-release-acceptance-2026-09-27.md @@ -0,0 +1,201 @@ +# Research Document: Public-release acceptance run (what "100% validated" means executably) + +**Status**: Draft +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Reviewers**: pending +**Phase**: 1 (disciplined-research) + +## Executive Summary + +The channel is verified; the entry points are not, and nothing currently asserts that a user following the published instructions gets the released version. This item defines one repeatable acceptance run that exercises every documented entry point from outside the project and fails when any of them disagrees with the published manifest. It is the mechanism that keeps the other fixes honest, and it is the difference between "we tested the artefacts" and "the release is validated". + +## Essential Questions Check + +| Question | Answer | Evidence | +|----------|--------|----------| +| Energizing? | Yes | It converts a verified channel into a validated release | +| Leverages strengths? | Yes | The manifest, validator and promotion machinery already exist | +| Meets real need? | Yes | Three entry-point defects existed undetected until today | + +**Proceed**: Yes (3/3). + +## Problem Statement + +### Description + +Validation today covers the channel (all 20 archives hashed from the manifest) and the updater contract. It does not cover the commands a user is told to run. The result was three defects invisible to the existing checks: the installer served v1.21.3, cargo served 1.21.1, and the documented Homebrew formula does not exist. + +### Impact + +Without an executable acceptance run, every future release can regress an entry point silently, and the "validated" claim rests on a human reading the site. + +### Success Criteria + +1. One command runs every documented entry point and reports per-entry-point pass or fail. +2. Every entry point that can execute on Linux is executed for real against the live channel. +3. Entry points that cannot execute here (macOS-specific) are reported as not-executed with the reason, never as passed. +4. Exit code is non-zero when any executed entry point disagrees with the manifest. + +## Current State Analysis + +### Existing Implementation + +| Check | Covered | Location | +|---|---|---| +| Channel object and pointer integrity | yes | `scripts/validate-r2-manifests.py` | +| Promotion stage provenance | yes | `scripts/validate-promotion-stage.py` | +| Release archive contents | yes | `scripts/validate-release-archive.py` | +| Updater contract against the R2 channel | partly, by hand | shipped binary `check-update` | +| Documented install commands | no | none | +| Tap formulae checksums | no | none | + +### Code Locations + +| Component | Location | Purpose | +|---|---|---| +| Manifest validator | `scripts/validate-r2-manifests.py` | Channel integrity | +| Promotion | `scripts/promote-release.sh` | Publishes and writes pointers | +| Updater | `crates/terraphim_update/` | Client-side update contract | +| Operator checklist | `docs/release-operator-checklist.md` | Manual steps, partly unexecutable | + +### Data Flow + +`stable-v2.json` (truth) -> acceptance run -> executes each documented path -> compares observed installed version and hash against the manifest -> summary report with exit code. + +### Integration Points + +- Docker is available locally (v29.6.0, overlay2) and `debian:bookworm-slim` is already present, so a clean-host check is cheap. +- GitHub Actions provides macOS runners (`macos-15`, `macos-latest`) in the release workflow; there is no local macOS host. +- Homebrew cannot run on this Linux host; Linuxbrew is not installed. +- `downloads.terraphim.ai` requires a descriptive User-Agent. + +## Constraints + +### Technical Constraints + +- The run must not depend on credentials belonging to the private repositories; it validates the public surface only. +- Clean-host checks must use a container, because the dev host has `~/.cargo/bin` artefacts that could mask a failure. +- Distinguishing "not executed" from "passed" is mandatory; a silent skip is the failure mode this item exists to prevent. + +### Business Constraints + +- Must be runnable by a human before announcing a release, and by CI after one. +- Must not require new paid infrastructure. + +### Non-Functional Requirements + +| Requirement | Target | Current | +|-------------|--------|---------| +| Coverage of documented entry points | 100 per cent executed or explicitly not-executed | 0 per cent | +| Runtime | under 10 minutes | n/a | +| Determinism | same result on repeated runs | n/a | + +## Vital Few (Essential Constraints) + +| Constraint | Why It's Vital | Evidence | +|---|---|---| +| Execute, do not inspect | The defects were invisible to inspection | Three defects found only by running commands | +| Never report a skip as a pass | A false pass is worse than no check | macOS cannot execute here | +| Compare against the manifest, not a constant | Constants rot; the manifest is generated | Manifest is authoritative | + +### Eliminated from Scope + +| Eliminated Item | Why Eliminated | +|---|---| +| Re-validating the sealed stage | Already covered by promotion-stage validation | +| Verifying signatures | Covered by existing archive and stage validation | +| A dashboard or reporting service | Out of vital few; a script and an exit code suffice | +| Windows execution | No Windows host; report as not-executed | + +## Dependencies + +### Internal Dependencies + +| Dependency | Impact | Risk | +|---|---|---| +| Manifest schema | The comparison basis | Low | +| Installer fix (item 1) | The installer entry point can only pass after it lands | High until fixed | +| Homebrew copy fix (item 3) | Same, for the brew entry point | High until fixed | + +### External Dependencies + +| Dependency | Version | Risk | Alternative | +|---|---|---|---| +| Docker | 29.6.0 local | availability on other hosts | document as a prerequisite | +| Linuxbrew | not installed | needed for the brew entry point | mark not-executed until a host exists | +| GitHub Actions macOS runner | available in CI | cost and availability | keep macOS execution in the release workflow | + +## Risks and Unknowns + +### Known Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| Run becomes a rubber stamp | Medium | High | Fail on disagreement; record raw transcripts | +| Container checks diverge from real user hosts | Medium | Medium | Use stock `debian:bookworm-slim` with no extra packages | +| Entry point added to the site without being added here | Medium | Medium | Treat the site copy as the input list during review | + +### Open Questions + +1. Should the acceptance run gate the release workflow, or run after and report? (Owner: release owner) +2. Is a Linuxbrew host acceptable for the non-macOS brew evidence, given the macOS item is separate? (Owner: release owner) +3. Who owns the site copy list so a new entry point cannot be added silently? (Owner: release owner) + +### Assumptions Explicitly Stated + +| Assumption | Basis | Risk if Wrong | Verified? | +|---|---|---|---| +| The manifest is the correct comparison basis | Updater and installer both target it | Wrong standard | Yes | +| Docker is acceptable as a clean host proxy | It is the closest available substitute | Missed host-specific issue | Yes, stated as a limitation | +| No local macOS host exists | ssh probes and `command -v` | Effort estimate wrong | Yes | + +## Research Findings + +### Key Insights + +1. Existing validation is artefact-centric; the gap is entirely entry-point-centric. +2. Three of the documented entry points are Linux-executable today, one (macOS brew) is not. +3. The run doubles as the regression test for items 1, 2 and 3. +4. Docker plus a stock image is sufficient for a clean-host installer check without new infrastructure. + +### Relevant Prior Art + +- `validate-r2-manifests.py` establishes the comparison-against-manifest pattern and the User-Agent requirement. +- The release operator checklist already enumerates manual steps; the run replaces the executable subset. + +### Technical Spikes Needed + +| Spike | Purpose | Estimated Effort | +|-------|---------|------------------| +| Run the installer in `debian:bookworm-slim` today | Capture the failing baseline | 1 hour | +| Confirm the updater check can run unattended | Include it in the run | 30 minutes | + +## Recommendations + +### Proceed/No-Proceed + +Proceed. Without this, the other fixes cannot be shown to hold. + +### Scope Recommendations + +Cover exactly the documented entry points plus the updater; report everything else as not-executed with a reason. + +### Risk Mitigation Recommendations + +Keep raw transcripts in the release record, and make the run fail rather than warn. + +## Next Steps + +If approved: +1. Phase 2 design of the acceptance run script and its reporting format. +2. Execute it before and after the item 1 to 3 changes to demonstrate the delta. + +## Appendix + +### Evidence Captured 2026-09-27 + +- Documented entry points: `curl ... install.sh | bash`; `cargo install terraphim-agent`; `cargo install terraphim-cli`; `brew tap terraphim/terraphim && brew install terraphim-ai`. +- Local runtimes: docker present (29.6.0, overlay2), brew absent, no macOS host. +- Release workflow uses `ubuntu-22.04` and `macos-15` runners. +- Updater contract verified live earlier in the train: shipped 1.21.16 `check-update` reported already-current. \ No newline at end of file diff --git a/docs/plans/research-site-installer-stale-release-2026-09-27.md b/docs/plans/research-site-installer-stale-release-2026-09-27.md new file mode 100644 index 00000000..49716b8e --- /dev/null +++ b/docs/plans/research-site-installer-stale-release-2026-09-27.md @@ -0,0 +1,231 @@ +# Research Document: Public installer path ships stale bytes (v1.21.3 instead of v1.21.16) + +**Status**: Draft +**Author**: Release orchestrator (autonomous session) +**Date**: 2026-09-27 +**Reviewers**: pending +**Phase**: 1 (disciplined-research) + +## Executive Summary + +The published release channel is sound: all 20 archives on `downloads.terraphim.ai` for terraphim-agent, terraphim-grep and terraphim-cli verify byte-exact (SHA-256 and size) at 1.21.16, and the Homebrew tap formulae carry checksums that match those manifests. The entry point a user is told to use is not sound. The headline instruction on terraphim.ai, `curl -fsSL https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh | bash`, resolves releases against the `terraphim/terraphim-ai` repository, whose latest release is **v1.21.3 published 2026-08-16**. That installer therefore delivers bytes from an older line than the release this project just shipped. A user following the site today does not get 1.21.16. + +## Essential Questions Check + +| Question | Answer | Evidence | +|----------|--------|----------| +| Energizing? | Yes | This is the difference between "we shipped a release" and "a user can install it"; it is the public face of the whole release train | +| Leverages strengths? | Yes | The channel, provenance, pointer and validator machinery already exists and passed; only the entry point is miswired | +| Meets real need? | Yes | terraphim.ai advertises the installer as the primary path alongside cargo/brew; today it yields v1.21.3 | + +**Proceed**: Yes (3/3). + +## Problem Statement + +### Description + +`terraphim.ai` presents three install paths. Two of them do not deliver the released version, and one names an artefact that does not exist: + +| Site instruction | What it actually resolves to | Verified | +|---|---|---| +| `curl -fsSL .../terraphim-ai/main/scripts/install.sh \| bash` | `terraphim-ai` latest release **v1.21.3** (2026-08-16), asset naming `terraphim-agent-linux-x86_64` | Yes, 2026-09-27 | +| `cargo install terraphim-agent` / `terraphim-cli` | crates.io newest **1.21.1** (2026-08-10) | Yes | +| `brew tap terraphim/terraphim && brew install terraphim-ai` | **No formula named `terraphim-ai`**; tap holds terraphim-agent, terraphim-grep, terraphim-server | Yes | + +### Impact + +A user who follows the primary documented instruction installs 1.21.3-era bytes from a different repository. They miss 13 patch releases of fixes, and the bug reports that follow will describe behaviour this codebase no longer has. This is the highest-impact remaining defect in the public release. + +### Success Criteria + +1. The documented installer, executed verbatim on a clean Linux host, installs 1.21.16 bytes whose SHA-256 matches the published manifest for the corresponding platform. +2. The installer and every other documented command fail loudly rather than silently installing an older line when 1.21.16 is unavailable. +3. No site instruction references a repository, formula or crate that cannot deliver 1.21.16. + +## Current State Analysis + +### Existing Implementation + +Two distinct release lines exist and the site conflates them: + +- **`terraphim/terraphim-clients`** (this repository): produces terraphim-agent, terraphim-grep, terraphim-cli. Tag `v1.21.16`, GitHub release published 2026-09-25T20:07:17Z with 21 assets; R2 channel objects and six stable pointers verified. +- **`terraphim/terraphim-ai`**: latest release v1.21.3 (2026-08-16, 41 assets). The public installer script lives in this repository at `scripts/install.sh` (HTTP 200 on `main`) and hardcodes `GITHUB_API_BASE="https://api.github.com/repos/terraphim/terraphim-ai"`. + +### Code Locations + +| Component | Location | Purpose | +|---|---|---| +| Public installer | `terraphim/terraphim-ai` `scripts/install.sh` (main) | Resolves and downloads a release asset | +| Release producer | `terraphim-clients` `.github/workflows/release-binaries.yml` | Builds and seals the 20 archives | +| Promotion | `terraphim-clients` `scripts/promote-release.sh` | Publishes to GitHub release and R2 with stable pointers | +| Channel validator | `terraphim-clients` `scripts/validate-r2-manifests.py` | Verifies every channel object and pointer | +| Updater base URL | `terraphim-clients` `crates/terraphim_update/src/manifest.rs:186` | `DEFAULT_BASE_URL = "https://downloads.terraphim.ai"` | +| Tap formulae | `terraphim/homebrew-terraphim` `Formula/*.rb` | Agent, grep, server | + +### Data Flow + +Site copy -> `install.sh` -> GitHub Releases API on `terraphim-ai` -> asset named `terraphim-agent-linux-x86_64` -> `~/.local/bin`. + +Intended flow -> published manifest on `downloads.terraphim.ai` (`/stable-v2.json`) -> archive `-1.21.16-.tar.gz` -> verified SHA-256 -> installed binary. + +The two flows share no component. The installer neither consults the manifest nor verifies a published checksum. + +### Integration Points + +- GitHub Releases REST API (`/repos/terraphim/terraphim-ai/releases/latest`) - returns v1.21.3. +- Asset naming: the installer builds `${tool}-${OS}-${ARCH}` (for example `terraphim-agent-linux-x86_64`) with no version component; the sealed stage ships `${tool}-${version}-${target}.tar.gz` (`terraphim-agent-1.21.16-x86_64-unknown-linux-gnu.tar.gz`) plus a Windows `.zip`. +- `downloads.terraphim.ai` is fronted by Cloudflare bot management, which 403s `Python-urllib/*`; the validator sends `terraphim-r2-manifest-validator/1.0`. +- The self-updater already targets the R2 manifest and reports correct behaviour when installed at 1.21.16. + +## Constraints + +### Technical Constraints + +- The installer must work with `bash` and `curl` or `wget` only; it is piped to a shell and cannot assume extras. +- Windows is supported only through WSL by the current script; the channel publishes `.zip` for `x86_64-pc-windows-msvc`. +- The channel manifest is the single source of truth for version, per-platform path and SHA-256; any installer must read it rather than infer URLs. +- The script lives in `terraphim-ai`, while the artefacts live in `terraphim-clients`; a change therefore spans repositories. + +### Business Constraints + +- `terraphim-ai` is a public repository and the documented installer path; editing it is a public-surface change requiring the same care as the release itself. +- No new hosted infrastructure should be introduced. + +### Non-Functional Requirements + +| Requirement | Target | Current | +|-------------|--------|---------| +| Installed version via site command | 1.21.16 | v1.21.3 | +| Archive integrity check | SHA-256 against published manifest | none | +| Silent stale install | impossible | silent | + +## Vital Few (Essentialism) + +### Essential Constraints (Max 3) + +| Constraint | Why It's Vital | Evidence | +|---|---|---| +| The installer must read the published manifest | It is the only artefact that names the current version, path and hash for every platform | `stable-v2.json` verified today for all three binaries | +| The installer must verify SHA-256 before installing | Prevents a compromised or partial download from being executed | All 20 archives verified byte-exact | +| The installer must fail closed on a version mismatch | A silent older install is worse than a clear failure | v1.21.3 vs v1.21.16 today | + +### Eliminated from Scope + +| Eliminated Item | Why Eliminated | +|-----------------|----------------| +| Rewriting the installer in Rust or shipping a compiled installer | Out of the vital few; bash+curl already works | +| Introducing a CDN or new hosting | Cloudflare R2 already serves the channel | +| Changing the archive naming scheme | It is validated by the tap, the updater and the validator | +| Rebuilding or re-promoting 1.21.16 | It is verified; this is an entry-point defect | +| Publishing terraphim-ai v1.21.16 to satisfy the old installer | Duplicates the release line and perpetuates two sources of truth | + +## Dependencies + +### Internal Dependencies + +| Dependency | Impact | Risk | +|---|---|---| +| `stable-v2.json` schema | Installer correctness depends on `assets[target].path` and `.sha256` | Low; schema is stable and validated | +| `terraphim-clients` promotion workflow | Must keep writing the manifest the installer reads | Low | + +### External Dependencies + +| Dependency | Version | Risk | Alternative | +|---|---|---|---| +| `downloads.terraphim.ai` (Cloudflare R2) | live | Cloudflare bot management may 403 default UAs | Send a descriptive User-Agent | +| GitHub Releases API | v3 | Rate limits for anonymous callers | Manifest on R2 is preferred over the API | +| `bash`, `curl`, `tar`, `sha256sum`/`shasum` | any modern | macOS lacks `sha256sum` | Fall back to `shasum -a 256` | + +## Risks and Unknowns + +### Known Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| Site copy and installer drift again after the fix | High | High | Make the manifest the only source; add an automated check | +| Changing a public installer breaks existing users | Medium | Medium | Keep CLI flags and install directory semantics; test the matrix | +| Installer in a different repository is missed by this project's CI | High | Medium | Add a cross-repository validation job or a scheduled check that executes the documented command | +| macOS `sha256sum` absence | High on macOS | Low | Detect and use `shasum -a 256` | + +### Open Questions + +1. Should the installer move into `terraphim-clients`, or stay in `terraphim-ai` and be repointed? (Owner: release owner) +2. Should the installer default to `latest` from the manifest, or pin a version unless overridden? (Owner: release owner) +3. Is the site source of truth a repository that can be changed in the same pass, so the `brew install terraphim-ai` line is corrected at the same time? (Owner: release owner; requires locating the site source) + +### Assumptions Explicitly Stated + +| Assumption | Basis | Risk if Wrong | Verified? | +|---|---|---|---| +| The site's installer URL is intended to serve the clients binaries | The script installs terraphim-agent and terraphim-cli | Wrong remedy chosen | Partially; the script's own help text names those tools | +| R2 `stable-v2.json` is the durable source of truth | Updater contract and pointer design | Installer targets the wrong source | Yes | +| `terraphim-ai` v1.21.3 is genuinely older, not a parallel product | Asset names and repo description | Two products conflated | Yes, by version and date | + +### Multiple Interpretations Considered + +| Interpretation | Implications | Why Chosen/Rejected | +|---|---|---| +| Repoint the existing `terraphim-ai` installer at the clients manifest | Smallest change; keeps the documented URL stable | Chosen as primary candidate | +| Move the installer into `terraphim-clients` and change the documented URL | Colocates installer with artefacts; breaks the widely quoted URL | Rejected unless the URL can be redirected | +| Publish terraphim-ai v1.21.16 with the expected asset names | Makes the old installer work untouched; creates a second release line | Rejected: two sources of truth | + +## Research Findings + +### Key Insights + +1. The release itself is complete and verified; the failure is in discovery and entry, not in the artefacts. +2. The site instructs users to install from a different repository and a different product line. +3. The tap is correct and checksum-verified, but the documented formula name does not exist. +4. The installer never verifies a checksum, so today it cannot detect that it is serving old bytes. +5. Local environment note: `~/.cargo/bin/terraphim-agent` is a user-work artefact and must not be touched as part of this work. + +### Relevant Prior Art + +- `scripts/promote-release.sh` + `stable-v2.json`: the manifest already carries version, path and sha256 per platform, which is exactly what an installer needs. +- `crates/terraphim_update/src/manifest.rs`: proves a client can consume the manifest and self-update. +- `scripts/validate-r2-manifests.py`: proves channel integrity can be asserted automatically, including the Cloudflare User-Agent requirement. + +### Technical Spikes Needed + +| Spike | Purpose | Estimated Effort | +|-------|---------|------------------| +| Execute the documented command in a clean container and capture the installed version | Establish the failing baseline objectively | 1 hour | +| Confirm manifest schema stability across all three binaries | Guarantee the installer can rely on it | 30 minutes | +| Locate the site source for the install section | Enable the copy fix in the same pass | 1 hour | + +## Recommendations + +### Proceed/No-Proceed + +Proceed. This is the highest-value outstanding item: the channel is verified, and one entry point contradicts it. + +### Scope Recommendations + +Treat "public release complete" as "the documented command installs the released version with verified integrity". Keep the existing CLI surface; change what the installer reads, not how a user invokes it. + +### Risk Mitigation Recommendations + +Make the installer fail closed on version or hash mismatch, and add an automated check that executes the documented command so drift is caught without a human. + +## Next Steps + +If approved: +1. Phase 2 design: specify manifest-driven resolution, checksum verification, failure modes, and the exact file changes. +2. Phase 2.5 specification interview: confirm installer location and default version policy. +3. Implement and validate in a clean container, then re-verify the site copy. + +## Appendix + +### Reference Materials + +- `https://terraphim.ai/` install section +- `https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh` +- `https://downloads.terraphim.ai/terraphim-agent/stable-v2.json` +- `terraphim/terraphim-ai` latest release: v1.21.3, 2026-08-16 + +### Evidence Captured 2026-09-27 + +- All 20 channel archives SHA-256 and size verified against `stable-v2.json` (agent 7, grep 7, cli 6). +- Tap checksums for agent and grep match the manifest for `universal-apple-darwin`, `x86_64-unknown-linux-gnu` and `aarch64-unknown-linux-musl`. +- `terraphim-ai` latest release v1.21.3; asset naming in the installer is `${tool}-${OS}-${ARCH}`; the channel uses `${tool}-${version}-${target}.tar.gz`. +- Site commands observed verbatim: `cargo install terraphim-agent`, `cargo install terraphim-cli`, `brew tap terraphim/terraphim && brew install terraphim-ai`, and the `curl ... | bash` one-liner. \ No newline at end of file diff --git a/scripts/acceptance-public-release.py b/scripts/acceptance-public-release.py new file mode 100755 index 00000000..281e04c8 --- /dev/null +++ b/scripts/acceptance-public-release.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +"""Read-only acceptance validation of the public release entry points. + +Proves, against live infrastructure only, that a user who follows the +documented instructions gets the current release: + + installer the documented curl|bash one-liner resolves and installs the + current version, with a matching checksum + manifests every binary's channel manifest is well formed and every + advertised archive matches its size and SHA-256 + brew the Homebrew tap exposes the documented formula names + crates the crates.io versions a `cargo install` would serve + +Each check reports one of three states. `not-executed` exists so an +environment limitation (no Homebrew on this host, no crates.io network) is +never silently reported as success. + +Exit codes: 0 all executed checks passed + 1 at least one executed check failed + 2 the installer check could not run + 3 nothing could be reached (no executed checks at all) +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import shutil +import subprocess +import sys +import tempfile +import urllib.error +import urllib.parse +import urllib.request + +PASS = "pass" +FAIL = "fail" +SKIP = "not-executed" + +CHANNEL_DEFAULT = "https://downloads.terraphim.ai" +INSTALLER_URL = ( + "https://raw.githubusercontent.com/terraphim/terraphim-ai/main/scripts/install.sh" +) +UA = "terraphim-release-acceptance/1.0" +CRATES_UA = "terraphim-release-verifier/1.0 (release validation)" + +BINARIES = ("terraphim-agent", "terraphim-cli", "terraphim-grep") +COMMON_TARGETS = { + "aarch64-apple-darwin", + "aarch64-unknown-linux-musl", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", +} +EXPECTED_TARGETS = { + "terraphim-agent": COMMON_TARGETS | {"universal-apple-darwin"}, + "terraphim-grep": COMMON_TARGETS | {"universal-apple-darwin"}, + "terraphim-cli": COMMON_TARGETS, +} + +GREEN = "\033[0;32m" +RED = "\033[0;31m" +YELLOW = "\033[1;33m" +BLUE = "\033[0;34m" +RESET = "\033[0m" + + +class CheckResult: + def __init__(self, name: str) -> None: + self.name = name + self.state = SKIP + self.detail = "" + + def passed(self, detail: str) -> "CheckResult": + self.state, self.detail = PASS, detail + return self + + def failed(self, detail: str) -> "CheckResult": + self.state, self.detail = FAIL, detail + return self + + def skipped(self, detail: str) -> "CheckResult": + self.state, self.detail = SKIP, detail + return self + + def render(self) -> str: + colour, label = { + PASS: (GREEN, "PASS"), + FAIL: (RED, "FAIL"), + SKIP: (YELLOW, "SKIP"), + }[self.state] + return f" {colour}{label}{RESET} {self.name}: {self.detail}" + + +def fetch(url: str, limit: int = 64 * 1024 * 1024, user_agent: str = UA) -> bytes: + """Fetch a URL, refusing responses larger than `limit`. + + Release archives run to roughly 20 MB; the cap exists to stop a + misdirected request from pulling down something unbounded. + """ + request = urllib.request.Request(url, headers={"User-Agent": user_agent}) + with urllib.request.urlopen(request, timeout=120) as response: + data = response.read(limit + 1) + if len(data) > limit: + raise ValueError(f"{url}: response exceeds {limit} bytes") + return data + + +def digest(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def check_manifests(base_url: str) -> CheckResult: + """Every advertised archive must exist and match its recorded size+digest.""" + result = CheckResult("channel manifests") + problems: list[str] = [] + checked = 0 + versions: set[str] = set() + + for binary in BINARIES: + url = f"{base_url}/{binary}/stable-v2.json" + try: + manifest = json.loads(fetch(url, 1 << 20)) + except (urllib.error.URLError, ValueError, json.JSONDecodeError) as error: + problems.append(f"{binary}: manifest unreadable ({error})") + continue + + version = manifest.get("version", "?") + versions.add(version) + assets = manifest.get("assets") + if not isinstance(assets, dict) or set(assets) != EXPECTED_TARGETS[binary]: + problems.append(f"{binary}: target set is not exact") + continue + + for target, asset in sorted(assets.items()): + path = asset.get("path", "") + parts = urllib.parse.urlsplit(path) + if parts.scheme or parts.netloc or ".." in path.split("/"): + problems.append(f"{binary}/{target}: unsafe asset path") + continue + try: + body = fetch(f"{base_url}/{path}") + except urllib.error.URLError as error: + problems.append(f"{binary}/{target}: archive unreachable ({error})") + continue + checked += 1 + if len(body) != asset.get("size"): + problems.append( + f"{binary}/{target}: size {len(body)} != {asset.get('size')}" + ) + continue + if digest(body) != asset.get("sha256"): + problems.append(f"{binary}/{target}: sha256 mismatch") + del body + + if problems: + return result.failed("; ".join(problems[:4]) + (" …" if len(problems) > 4 else "")) + if not versions: + return result.skipped("no manifests reachable") + if len(versions) != 1: + return result.failed(f"binaries disagree on version: {sorted(versions)}") + return result.passed(f"{len(BINARIES)} binaries at {versions.pop()}, {checked} archives byte-verified") + + +def check_installer(channel: str, install_dir: str) -> tuple[CheckResult, str | None]: + """Run the documented one-liner against a scratch directory.""" + result = CheckResult("documented installer") + if shutil.which("bash") is None or shutil.which("curl") is None: + return result.skipped("bash or curl unavailable"), None + + try: + script = fetch(INSTALLER_URL) + except urllib.error.URLError as error: + return result.failed(f"installer unreachable ({error})"), None + + script_path = os.path.join(install_dir, "install.sh") + os.makedirs(install_dir, exist_ok=True) + with open(script_path, "wb") as handle: + handle.write(script) + + # Version pinning is only meaningful once the published script is the + # manifest-driven one. Until then a pinned run fails for reasons that say + # nothing about the release, so it is left to the default (`latest`). + args = [script_path, "--install-dir", install_dir] + + # The sibling utilities are fetched by the installer unless it can see + # them; run the script alone so the piped path is the one under test. + env = dict(os.environ) + env.setdefault("UTILS_REVISION", "main") + completed = subprocess.run( + ["bash", *args], + capture_output=True, + text=True, + env=env, + timeout=600, + ) + output = (completed.stdout or "") + (completed.stderr or "") + + if completed.returncode != 0: + return result.failed(f"exit {completed.returncode}: {output.strip().splitlines()[-1] if output.strip() else 'no output'}"), None + + binary = os.path.join(install_dir, "terraphim-agent") + if not os.path.isfile(binary) or not os.access(binary, os.X_OK): + return result.failed("installer produced no executable terraphim-agent"), None + + reported = subprocess.run([binary, "--version"], capture_output=True, text=True, timeout=60) + version_line = (reported.stdout or "").strip() + if reported.returncode != 0 or not version_line: + return result.failed("installed binary did not report a version"), None + + parts = version_line.split() + version = parts[-1] if parts else "?" + if "SHA-256 verified" not in output and "unverified" not in output: + return result.failed("installer did not report checksum verification"), version + return result.passed(f"{version_line} installed and checksum-verified"), version + + +def check_brew() -> CheckResult: + result = CheckResult("homebrew formula") + if shutil.which("brew") is None: + return result.skipped("brew not available on this host") + tap = "terraphim/terraphim" + try: + subprocess.run(["brew", "tap", tap], capture_output=True, text=True, timeout=300) + info = subprocess.run( + ["brew", "info", "terraphim-agent"], capture_output=True, text=True, timeout=300 + ) + except subprocess.SubprocessError as error: + return result.failed(f"brew invocation failed ({error})") + if info.returncode == 0 and "terraphim-agent" in info.stdout: + return result.passed("brew install terraphim-agent resolves") + return result.failed("documented formula does not resolve in the tap") + + +def check_crates(crates: tuple[str, ...], expected_version: str | None) -> CheckResult: + result = CheckResult("crates.io cli family") + stale: list[str] = [] + missing: list[str] = [] + for crate in crates: + try: + body = fetch(f"https://crates.io/api/v1/crates/{crate}", 1 << 20, CRATES_UA) + data = json.loads(body) + except (urllib.error.URLError, ValueError, json.JSONDecodeError): + missing.append(crate) + continue + served = data.get("crate", {}).get("max_stable_version") or "?" + if expected_version and served != expected_version: + stale.append(f"{crate}={served}") + if missing and len(missing) == len(crates): + return result.skipped("crates.io unreachable") + if missing: + return result.failed(f"unpublished: {', '.join(missing)}") + if stale: + return result.failed(f"not at {expected_version}: {', '.join(stale)}") + return result.passed(f"{len(crates)} crates at {expected_version}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--channel", default=CHANNEL_DEFAULT) + parser.add_argument("--expected-version", default=None) + parser.add_argument( + "--crates", + default="terraphim_agent terraphim_cli terraphim_grep", + help="space-separated crates.io packages to check", + ) + parser.add_argument("--skip-installer", action="store_true") + parser.add_argument("--skip-crates", action="store_true") + args = parser.parse_args() + + print(f"{BLUE}Terraphim public release acceptance{RESET}") + print(f" channel: {args.channel}") + print() + + with tempfile.TemporaryDirectory(prefix="terraphim-acceptance-") as work: + install_dir = os.path.join(work, "bin") + if args.skip_installer: + installer_result = CheckResult("documented installer").skipped("disabled") + installed_version = None + else: + installer_result, installed_version = check_installer(args.channel, install_dir) + + expected_version = args.expected_version or installed_version + + results = [ + installer_result, + check_manifests(args.channel), + check_brew(), + ( + CheckResult("crates.io cli family").skipped("disabled") + if args.skip_crates + else check_crates(tuple(args.crates.split()), expected_version) + ), + ] + + print(f"{BLUE}Checks{RESET}") + for result in results: + print(result.render()) + + executed = [r for r in results if r.state != SKIP] + failed = [r for r in executed if r.state == FAIL] + + print() + summary = f"{len(executed)} executed, {len(failed)} failed, {len(results) - len(executed)} not executed" + print(f"{BLUE}{summary}{RESET}") + + if not executed: + print(f"{RED}Nothing was reachable; acceptance is inconclusive.{RESET}") + return 3 + if installer_result.state == FAIL and not args.skip_installer: + return 2 + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file