# PACKAGING-PLAN — M2: rebound ships to users who do not build it (JIT)

> **RETIRED 2026-07-26 — absorbed into `PACER-PLAN.md` (M3).** spt-core **0.44.0** dissolved
> this plan's blocker: a shell spawn's bare program token now resolves against the adapter's
> install dir before PATH, and a fat `.spt` ships the binaries themselves (triple subdirs,
> flattened on install). The `{adapter_dir}` substitution key we filed for also landed, and is
> no longer even needed. Packaging ships as part of `PACER` 0.3.0
> (`min_spt_core_version = "0.44.0"`). Kept for the M2 record — do not build from this file.

> The next body of work after M1. Vocabulary is authoritative in `CONTEXT.md`; the seam
> dependency is closed (`docs/FIELD-VERIFICATION-0.42.0.md`). Read the **published docs**
> before touching anything here: `http://localhost:5474` (`spt docs url`) — especially
> `shells/getting-started`, `shells/frames`, and `harness-contract/manifest`.

## Correction first: rebound was never uninstallable

An earlier version of this plan was built on a premise that is simply false — that the
shipped manifest could not launch anything and no user could `adapter add` rebound. That
generated a filing, a fork, a ruling, a retraction, and a re-ruling from two other agents
before it was checked against the docs.

**The documented install path works today, verified end to end on spt-core 0.42.0:**

```sh
cargo install --path .    # puts rebound-shell on PATH (~/.cargo/bin)
spt adapter add .         # registers the SHIPPED manifest, bare-name spawn and all
spt shell spawn rebound   # -> status: online
```

This is precisely the pattern `spt-shell-notify` documents in `shells/getting-started`,
whose own manifest declares `spawn = "notify-shell --link {link_token} --id {id}"` — a
bare name, resolved because `cargo install` put it on PATH.

**What actually went wrong:** the rig registered an adapter dir with the exe sitting
*beside the manifest* and never on PATH, so a bare name could not resolve. That is a true
observation about adapter-dir resolution and a false conclusion about installability. The
lesson is in the repo's working rules and in perri's durable role: read the published
chapter before filing, and check a shipping adapter's real manifest before inventing a
pattern.

## What is genuinely left

Source-install works. **Binary distribution does not, yet** — and that is the real M2.

A user without a Rust toolchain needs prebuilt binaries, which arrive via
`adapter add --release user/repo` as an `adapter.spt` archive unpacked into the adapter
directory. Nothing puts that binary on PATH, so the bare name that works for a source
install will *not* resolve for a release install. Naming it needs `{adapter_dir}` in
`[shell].spawn` — the parity gap filed with todlando and ruled by doyle (`[shell].spawn`
gains the key via the same shared helper, meaning `source_dir` as it already does on the
harness-spawn, `[digest]`, and `[update.post]` surfaces). It rides the shell wave.

So the two install stories want two different spawn strings, which one manifest cannot
spell:

| install | binary lands | `spawn` that resolves |
|---|---|---|
| `cargo install --path .` | `~/.cargo/bin` (on PATH) | bare `rebound-shell` — **works today** |
| `adapter add --release` | the adapter dir | `{adapter_dir}/rebound-shell.exe` — **needs the parity fix** |

Deciding between them is the first real task, and it is a product decision, not a
workaround: source-install-only is honest and works now; release distribution reaches
users who will never run `cargo`.

## ✅ v0.1.0 IS CUT — the source release shipped 2026-07-25

`v0.1.0` is tagged and released on `BigscreenVR/spt-rebound-tool`
(<https://github.com/BigscreenVR/spt-rebound-tool/releases/tag/v0.1.0>) as a **source
release: no assets, deliberately.** Install = the three `README.md` commands. `CHANGELOG.md`
carries the user-facing section; `REQ-PACER-INSTALL` is registered at the `doc` stage
(impl/unit/int belong to binary distribution). The box below is why it has no binary, and it
still governs v0.2.

## ⚠️ Read before cutting the first release

**The operator has asked for a first release.** One constraint decides its shape, and
getting it wrong ships a release that cannot run:

**Ship a SOURCE release (tag + `cargo install`), not an `adapter.spt` binary archive —
yet.** A binary archive unpacks into the adapter dir, nothing puts that binary on PATH,
and `[shell].spawn` cannot name its own directory until the `{adapter_dir}` parity fix
ships. todlando has ruled **wait** on that key and asked us not to build against it until
he says it has published. So a binary release today would register fine and then fail at
spawn with `os error 2` — the exact failure this plan was mis-founded on.

What works today, verified end to end (see the correction below): `cargo install --path .`
puts `rebound-shell` on PATH, and the shipped bare-name manifest spawns against it. So
v0.1.0 = a tagged GitHub release whose install instructions are the three commands in
`README.md`, honestly scoped to users with a Rust toolchain.

The binary-archive release is v0.2, gated on the parity key landing. Do not conflate them.

Also note `[adapter].version` is `0.1.0` and `min_spt_core_version` is `0.42.0`; both are
already correct for this release. The floor bumps only when we adopt `{adapter_dir}`.

## Re-verified on spt-core 0.43.0 (2026-07-26) — the gate still holds

Before cutting v0.2.0 the two premises above were re-checked against the live node rather
than trusted from memory. Both hold, and one is now **observed**, not reasoned:

| claim | how it was checked | result |
|---|---|---|
| `{adapter_dir}` is not a shell-spawn key | published manifest chapter's key table: the shell `spawn` template's keys are `{id}`, `{adapter_name}`, `{link_token}`, `{perch_dir}` | **read** — `{adapter_dir}` is not among them |
| …and spawning with it fails | registered a probe adapter with `spawn = "{adapter_dir}/rebound-probe-shell.exe --link {link_token}"` | **observed** — `SHELL_SPAWN_FAIL:…: launch: no value for substitution key {adapter_dir}` |
| a shipped binary resolves from the install dir | registered a probe adapter, binary beside the manifest, bare-name spawn, name **not** on PATH — in copy mode **and** in pointer mode (`source_dir` = the dir holding the exe) | **observed** — both fail `os error 2` |

The third row is the one worth keeping. The docs' install-dir program-token resolution
("since v0.8.0, a command template's bare program token resolves against the adapter's
install dir before PATH") enumerates where it applies — `[session.psyche_resume]`, the
`[digest]` extractor, `spt adapter digest-proof` — and `[shell].spawn` is **not** on that
list. The probe agrees with the list. So a `.spt` that ships `rebound-shell` is not
self-contained the way a harness adapter's is: the broker still resolves the spawn program
against PATH only.

**What that means for a release with assets:** the binary has to reach PATH by some route
the manifest cannot arrange. Until the parity key lands, that route is the user's — one
download, placed on PATH — which is what `cargo install` was already doing for them. The
`[update]` avenue ships the *manifest*; it cannot ship the *binary* to a place the spawn
can find. Say that plainly wherever install is documented, including `[update].message`.

## The update avenue, driven (2026-07-26, spt-core 0.43.0)

Declaring `[update]` is not evidence that it works, and `README.md` had already told users
"`spt adapter update rebound` keeps the manifest current" on the strength of the declaration
alone. So it was driven, on the live node, in both states.

**Up-to-date path** (installed 0.2.0, latest 0.2.0):

```
ADAPTER_UPDATE_UPTODATE:rebound: installed 0.2.0, latest 0.2.0
ADAPTER_UPDATE_SUMMARY:rebound: up to date (0.2.0)
```

That alone proves the thing most likely to be broken: **the `gh` transport reaches a PRIVATE
repo**. Reading the latest release version off `BigscreenVR/spt-rebound-tool` is exactly what
the unauthenticated HTTPS path 404s on, so `transport = "gh"` is doing its job — observed, not
assumed.

But the version compare short-circuits *before* any download, so it leaves the fetch leg
unproven. Inferring the whole from two separately-observed halves is the shape of mistake this
project keeps paying for, so the real path was forced: register the manifest at `0.1.0`, then
update against the published `0.2.0`.

```
ADAPTER_UPDATE_SUMMARY:rebound: updated 0.1.0 -> 0.2.0
```

Fetched through `gh`, extracted, re-registered, exit 0 — and `[update].message` rendered to
stdout exactly as authored, telling the operator to replace the binary themselves. Three
findings:

1. **A running instance is NOT disturbed.** `rebound-0` stayed `online` straight through the
   update. This is the half of task 4 that had been left as "observe it, do not reason about
   it" — now observed.
2. **The payload extracts FLATTENED into the install dir**: `manifest.toml` +
   `rebound-shell.exe` at the root, current triple only. The per-triple subdirectories are an
   archive-side layout, resolved away on install, exactly as the published docs describe.
3. **⚠ The update rewrites the REGISTERED `source_dir`, in place — it does not relocate the
   adapter to a durable home.** Wherever you first registered from is where every future update
   lands, forever. Observed the hard way: the forced test registered from a scratch directory,
   and the update wrote the release payload into that scratch directory, leaving the node's
   registration depending on a temp path that was about to be deleted. Re-registering with
   `adapter add --release … --gh` restored the durable `_github/BigscreenVR-spt-rebound-tool`
   home. **This has a user-facing consequence and it is now in `README.md`.**

## Open design questions

1. ~~Who is the audience at v0.1?~~ **ANSWERED — we are publishing.** v0.1.0 is a source
   release for Rust-toolchain users (see the box above). Binary distribution follows the
   parity key.
2. **If releases: does `{adapter_dir}` land before we need it?** It is ruled and queued on
   the shell wave. If it ships first, the release manifest is a one-line change. If we get
   there first, the fallback is an install step that puts the binary on PATH — which is
   what `cargo install` already does for the source path, so it is a known shape rather
   than an invention.
3. ~~**Cross-platform.**~~ **ANSWERED at v0.2.0 — ship both recognized triples.**
   `x86_64-pc-windows-msvc` and `x86_64-unknown-linux-gnu` go in the fat `adapter.spt`
   under per-triple roots, plus `x86_64-unknown-linux-musl` as a standalone asset for
   pre-glibc-2.39 hosts. Windows remains the only platform where the *loop* has been
   verified end to end; the Linux binaries are built and checksummed, not field-run, and
   the changelog says so rather than implying parity.
4. ~~**Do we want `[update]` at all?**~~ **ANSWERED at v0.2.0 — yes, `gh_release`.** It is
   declared in `manifest.toml` (`repo = "BigscreenVR/spt-rebound-tool"`,
   `transport = "gh"` because the home repo is private, `asset = "adapter.spt"`). It keeps
   the *manifest* current on `spt adapter update rebound` and derives Pointer mode. It
   does **not** keep the binary current in any way the spawn can use — see the probe table
   above — so `[update].message` tells the operator to replace the binary themselves.

## Tasks

1. ~~**Answer Q1** — audience.~~ **DONE** — source release for Rust-toolchain users; v0.1.0
   is cut.
2. ~~**Document the working install path** in `README.md`~~ **DONE** — the three commands are
   in the `Install` section, tagged `[doc->REQ-PACER-INSTALL]`.
3. **Swap `spawn` to `{adapter_dir}/rebound-shell.exe`** once the parity fix ships, and bump
   `min_spt_core_version` to whichever release carries the key — a real floor, not a
   formality. **Still blocked at 0.43.0** (probe table above). The `[update]` avenue half of
   this task is **DONE** at v0.2.0 and did not need the key.
4. ~~**Cut a real release and verify the payload layout**~~ **DONE at v0.2.0, both halves now
   observed** — the release carries `adapter.spt` (multi-platform: shared `manifest.toml` at
   the root, per-triple binary roots) plus standalone per-platform binaries and `SHA256SUMS`,
   packed by `ci/publish/package-adapter.sh`. The two questions this task held open were closed
   by driving a real `0.1.0 → 0.2.0` update on the live node (2026-07-26, spt-core 0.43.0):
   see **The update avenue, driven** below.
5. **`REQ-PACER-INSTALL`** stays at the `doc` stage. The release path is documented and the
   assets ship, but its `impl`/`unit`/`int` stages belong to the *one-command* install — the
   thing the parity key gates — and its `int` stage is an install on a machine that did not
   build the binary. Activating them now would be pre-failing a requirement we cannot yet
   satisfy (traceability rule 5).

## Gate

`cargo build` + `cargo test` green, `traceable-reqs check` green (including
`REQ-PACER-INSTALL`), **and** a from-scratch install on a machine that did not build the
binary, reaching a bound, pinging shell with no hand-edited path. The current rig proves
nothing about installability — it was hand-wired.

## Not in this milestone

Any change to the ping/latch/backoff behavior (M1 is done and verified — do not reopen
it); the spt-core defects filed from the M1 window (`SHELL-LAUNCH-TRUTH`) — we consume the
fix when it ships.
