# Install/Upgrade Scenario Catalog

**Owner:** Benjamin Booth
**Status:** Living document — grows as scenarios are identified
**Last updated:** 2026-04-21
**Discipline:** See `docs/adrs/` for the install hardening strategy (six principles). Required CI gate enforces that every scenario marked ✓ stays green.

## Purpose

Axiom's install and upgrade paths must stay reliable across a growing install base (target: 10k–100k federation nodes). This catalog is the single place to see every known install/upgrade scenario, its coverage status, and its open mitigation work.

**The discipline in one line:** every scenario is a test, not a procedure. If a scenario breaks, fix is "add a test + fix the code until it passes" — never SSH hand-patching.

## Status Legend

| Symbol | Meaning |
|:-:|---|
| ✓ | Covered by test — regression will fail CI |
| ⚠ | Partial coverage — some paths tested, others rely on manual verification |
| ❌ | Not covered — regression will ship silently |
| 🚨 | Open regression — test exists and is currently failing |

## Scenarios

| # | Scenario | Severity | Status | Test Reference | Mitigation / Notes |
|--:|---|:-:|:-:|---|---|
| 1 | Fresh install from published PyPI wheel | P0 | ✓ | `tests/install_path/test_clean_install.py::test_version_matches_pypi_pin` | Docker container, pip install pinned version, assert version matches |
| 2 | Top-level help lists core subcommands | P0 | ✓ | `test_clean_install.py::test_help_lists_subcommands` | Checks for `federation`, `nodes` in `axi --help` |
| 3 | `axi federation init` succeeds (cryptography dep resolves) | P0 | ✓ | `test_clean_install.py::test_federation_init_has_cryptography` | Regression guard for v0.10.6 (cryptography promoted to core dep) |
| 4 | `axi nodes list` on clean install returns empty, no crash | P1 | ✓ | `test_clean_install.py::test_nodes_list_empty_clean` | |
| 5 | `axi install-shim` subcommand registered | P0 | ✓ | `test_clean_install.py::test_install_shim_subcommand_registered` | v0.10.10 hardening: stable `~/.local/bin/axi` for SSH peers |
| 6 | Clean uninstall leaves no residue | P2 | ✓ | `test_clean_install.py::test_clean_uninstall_leaves_no_axi_modules` | Import probe after `pip uninstall` |
| 7 | **Builtin extension discovered on clean install** (`axi classroom --help`) | **P0** | ✓ | `test_clean_install.py::test_builtin_extension_discovered` | **v0.10.10 shipped classroom missing from wheel** — root cause was not a code bug, but a release-process failure: classroom files weren't in git at v0.10.10 tag time (2026-04-15), hatchling's VCS-aware inclusion dropped the untracked directory. Fixed in v0.10.11 rebuild from HEAD + new `test_prerelease_local_wheel.py` gate. See scenario #27. |
| 8 | Upgrade 0.10.6 → current preserves classroom extension discovery post-upgrade | P0 | ✓ | `test_upgrade_graph.py::test_upgrade_from_prior_preserves_core_commands` | First upgrade-graph hop. Prior-version federation_init is NOT asserted (0.10.6 had known cryptography gap); post-upgrade federation_init + classroom discovery IS asserted. |
| 9 | Upgrade from 0.10.4 (peer-identity-binding baseline) | P1 | ❌ | — | Add to `PRIOR_VERSIONS` list in `test_upgrade_graph.py` |
| 10 | Upgrade from 0.10.5 (SSH runner depth fix baseline) | P2 | ❌ | — | Add to `PRIOR_VERSIONS` list |
| 11 | Upgrade from 0.10.7, 0.10.8, 0.10.9 | P1 | ❌ | — | Add each as a hop |
| 12 | Downgrade / rollback to prior version | P1 | ❌ | — | Not yet designed; needs schema-compat discipline in extensions |
| 13 | Reinstall from backup (state dir preserved, binary reinstalled) | P1 | ❌ | — | |
| 14 | Really old installs joining current federation (multi-version skew) | P0 | ❌ | — | Preflight peer version check on `axi nodes add` — tracked follow-up |
| 15 | Partially functional node (half-upgraded, crashed services, corrupted state) | P1 | ❌ | — | `axi dr` should diagnose + remediate; not yet wired |
| 16 | Potentially compromised node (key leaked) | P1 | ⚠ | — | TOFU with loud refusal on silent key change is shipped (v0.10.4); full ceremony + revocation propagation ❌ |
| 17 | Orphaned identity (node exists, federation membership lost) | P2 | ❌ | — | Recovery ceremony designed (`project_quarantine_and_recovery_path` memory); no CLI |
| 18 | Forked identity (same node ID on two machines, split-brain) | P2 | ❌ | — | Detection path designed; no test |
| 19 | Air-gapped install (no internet during install) | P2 | ❌ | — | Offline wheel + prebuilt manifest path not yet built |
| 20 | Dev/prod commingling (editable install contaminating prod state) | P2 | ❌ | — | Consider warning emission when `pip install -e` sees existing `~/.axi/` with production state |
| 21 | Ephemeral cloud installs (fresh container every run, identity churn) | P2 | ❌ | — | Classification: "ephemeral member" per ADR-023 |
| 22 | Multi-tenant hardware (two students sharing a laptop) | P2 | ❌ | — | Per-user state dirs; conflict detection |
| 23 | Clock skew (node's clock drifts > signature TTL window) | P1 | ❌ | — | NTP dependency; graceful clock-drift detection + warning |
| 24 | `axi update` partial failure (deps step fails, overall reports success) | P1 | ❌ | — | Tracked follow-up from 2026-04-15 session |
| 25 | Windows support | P2 | ❌ | — | Windows support spike tracked |
| 26 | Federation lifecycle test matrix (ephemeral/long-running/hub-spoke/hierarchical/multi-root) | P1 | ⚠ | `tests/federation_lifecycle/` | 7 scenarios passing; classroom-lifecycle scenario not yet added |
| 27 | **Pre-publish wheel contains every source-tree extension** | **P0** | ✓ | `test_prerelease_local_wheel.py::test_local_wheel_ships_every_source_tree_extension` | The v0.10.10 failure mode made manifest. Enumerates every `extensions/builtins/<dir>` with a manifest, builds the wheel, asserts every directory survives. CI job `prerelease-wheel-check` runs this before `publish`. Classroom, memory, directive all verified present. |
| 28 | Pre-publish wheel exercises `axi classroom --help` on the local artifact | P0 | ✓ | `test_prerelease_local_wheel.py::test_local_wheel_classroom_help_works` | Second layer: even if the wheel ships the files, the entry-point wiring must work. Catches dispatcher/entry-point regressions distinct from file-inclusion regressions. |

## Observability

Four numbers answer "is install/upgrade getting better?":

| Metric | Current (2026-04-21) | Target |
|---|---:|---:|
| Scenarios covered (%) | 9 of 28 = 32% | 100% |
| Regression count (trailing 24h) | 0 (#7 fixed in 0.10.11) | 0 |
| One-off intervention count (trailing 30d) | (not yet tracked) | declining → 0 |
| Unique install errors observed (30d) | (not yet tracked) | monotonic ↓ |

**How to update:** whenever a PR touches install/upgrade, update the relevant rows here (add scenarios, flip status, link to new tests). A PR that ships a new feature without updating this table when the feature has install/upgrade implications is incomplete.

## Upgrade-Graph Coverage Matrix

The upgrade-graph test exercises multi-hop upgrades. Adding hops is cheap; coverage gaps become visible here.

| From Version | To Current | Covered? | Test |
|:-:|:-:|:-:|---|
| 0.10.4 | ✅ | ❌ | — |
| 0.10.5 | ✅ | ❌ | — |
| 0.10.6 | ✅ | ✓ (failing on #7) | `test_upgrade_graph.py` |
| 0.10.7 | ✅ | ❌ | — |
| 0.10.8 | ✅ | ❌ | — |
| 0.10.9 | ✅ | ❌ | — |

## Related References

- `CLAUDE.md` — project memory and conventions
- `project_install_upgrade_hardening_status.md` (session memory) — narrative status + discipline principles
- `project_prague_classroom_extension_discovery_bug.md` — investigation thread for the open P0 regression
- `.github/workflows/ci.yml` — the `install-path-tests` job that enforces this catalog

_Copyright (c) 2026 The University of Texas at Austin and B-Tree Labs. Apache-2.0 licensed._
