=== schema version/header ===
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "spt-core runtime manifest",
  "description": "Per-adapter runtime manifest for the spt-core harness contract. Authored as TOML (this schema describes the equivalent data model). A manifest declares only what varies per harness/shell; command templates are opaque strings spt-core never parses. Cross-field invariants (kind<->[shell] agreement, strategy/avenue field requirements) are enforced by spt-core's validate step beyond this schema.",
  "type": "object",
  "properties": {
    "adapter": {
      "$ref": "#/$defs/Adapter"
    },
    "hooks": {
      "description": "`[hooks.<event>]` — inbound hook table, keyed by harness event name.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/Hook"
      }
=== CHANGELOG: F-014 / multi-platform / 0.13 mentions ===
docs/SPT-CORE-FINDINGS.md:22:| F-011 | 2026-06-17 | **CONFIRMED + ROOT-CAUSED (doyle, spt-core source) — case-3 robustness, NON-blocking**. doyle: `registry.rs` `manifest_dir` — Pointer/GhReleaseManaged adapters read the manifest LIVE from `source_dir`; a deferred install whose manifest isn't extracted yet → `load_manifest` fails → `registered()` `filter_map(...ok())` **SILENTLY DROPS** the adapter → zero host_binaries candidates (`ADAPTER_UNRESOLVED`) AND `resolve_option/set_active` reads the absent manifest → bare **os-error-2**. Fix shape (doyle minting REQ-HAZARD→todlando): clear diagnostic at resolver + `adapter use` instead of silent-drop/cryptic-os2, possibly eager manifest extract at register. Real `--release`/extracted-dir installs work (v0.3.0 dogfooded clean). **RECURRED 2026-06-22 via the `*-proof` commands (translate-proof/digest-proof) on a GhReleaseManaged dev adapter — acked + batched by doyle into v0.13.x adapter-DX (a `--dir`/`--manifest` override); see "F-011 (cont.)" below.** | A registered **Pointer**-mode adapter whose deferred install dir lacks the extracted manifest vanishes from the active set: bare resolution fails `ADAPTER_UNRESOLVED` (host_binaries never consulted) and `spt adapter use <adapter>` fails cryptic `os error 2` (no path/cause), even though the manifest declares everything needed |
docs/SPT-CORE-FINDINGS.md:23:| F-016 | 2026-06-22 | **RESOLVED — both fixes SHIPPED** (doyle, broker.rs-confirmed). The published `[message-idle-translation-binary]` doc omitted `{commit}` from the stdout vocabulary AND its degenerate baseline `{text}{key:enter}` would itself FAULT. `{commit:true}` is the MANDATORY inject-sequence terminator (`run_inject_worker` broker.rs:1075-1090; no-commit → 5s `INJECT_COMMIT_DEADLINE` FAULT, broker.rs:151-169; reference `{text}{key:enter}{commit:true}` translation.rs:74-78). **(i) adapter binary appends trailing `{commit:true}` — DONE (cc-spt-idle-translate, 11 tests); (ii) contract republished + LIVE on gh-pages 2026-06-22 — `{commit}` in the vocabulary + commit-deadline/inject-floor semantics + corrected degenerate; re-verified by re-fetch.** Residual: end-to-end `translate-proof` re-confirm pending v0.13.1 (counter 28+, runner-load delay, not code). Caught by the blind-build BEFORE a live FAULT | The published harness-contract documented the idle-translation binary's stdout vocabulary as `{key}`/`{delay_ms}`/`{text}` only, with a `{text}{key:enter}` degenerate example — but the broker requires a trailing `{commit:true}` terminator or every delivery FAULTs at the 5s commit deadline. A harness author building from the public surface alone ships a binary that faults live |
docs/SPT-CORE-FINDINGS.md:24:| F-017 | 2026-06-22 | **REPORTED to doyle — latent multi-subnet bringup gap; fix QUEUED (doyle ADRs 0026/0027, its own milestone v0.13.3+)**. NOT a version regression — established home-assignment policy that only BITES once a node holds 2+ subnets (real trigger: this node gaining BIGNET as a 2nd subnet). On a multi-subnet node a NEW-endpoint bind must be told its home: `spt api bind <id> --set-session-id <sid>` → **`HOME_REFUSED: this node holds 2 subnets … pass --subnet`**; the same bind WITH `--subnet <name>` → BOUND. The spt-HOSTED bringup cannot supply it: `spt endpoint run` has **no `--subnet` flag** and the broker injects `$SPT_ENDPOINT_ID` but **no home-subnet**, so the plugin SessionStart `bind)` branch fires `api bind … --set-session-id …` (no `--subnet`) → HOME_REFUSED → the hook's `\|\| true` swallows it → zero perch → server `ENDPOINT_RUN_ONLINE_TIMEOUT` (~25s). Pinned HOME_REFUSED on 0.11.0/0.12.0/0.12.1/0.13.1/**0.13.2** (policy, not a code bug). **doyle's fix (ADRs 0026/0027): broker injects `$SPT_ENDPOINT_SUBNET` + `api bind` env-fallback → the SHIPPED hooks stay UNCHANGED.** Adapter manifest/hooks are CORRECT as-is; fix is spt-core-side. **Regression seed COMMITTED: `ci/subnet/multi-subnet-bringup-int.sh`** — 3 cases (Case 2 no-subnet→HOME_REFUSED, Case 3 --subnet→BOUND, Case 1 E2E endpoint-run→no-perch/timeout, gated SPTC_ACCEPTANCE=1); multi-subnet-gated (single-subnet auto-homes + hides it). Operator earlier DECLINED a temp `--subnet` hook band-aid (wait for the real fix). | On a node holding 2+ subnets, every NEW-endpoint bind needs `--subnet <home>` (else HOME_REFUSED), but the spt-hosted `endpoint run` path provides no way to pass it — the broker injects the id but not the home subnet, so the SessionStart self-bind HOME_REFUSEs and the spt-hosted bringup silently yields zero perch (ONLINE_TIMEOUT). Single-subnet nodes auto-home and never see it |
docs/SPT-CORE-FINDINGS.md:25:| F-018 | 2026-06-22 | **REPORTED to doyle (perri) — destructive footgun, os-2 family (F-011 sibling)**. Surfaced while constructing the F-015 brick repro on v0.13.2. `spt adapter add --github <user/repo>` on an **already-registered, already-extracted gh_release Pointer adapter** is destructive-then-cryptic: it git-clones the SOURCE repo over the extracted install dir (wiping the root `manifest.toml` + the 3 runtime `.exe` binaries the Pointer resolves), then the post-add first-`[update]` conduct fails bare **`ADAPTER_ADD_FAIL: io: ... (os error 2)`** — leaving the adapter `manifest-not-present` (same broken state as F-011, but operator-induced). No confirm prompt, no "already registered — did you mean `adapter update`?" guard, no rollback. Recovered by extracting `dist/adapter-windows-x86_64.spt` over the install dir (manifest+binaries restored, version 0.6.0, translate-proof OK). **Needs (doyle):** for an already-registered gh_release adapter, `add --github` should refuse/route to `adapter update` (or at least not wipe the working install before the avenue succeeds), and the `os error 2` should carry path+cause (the F-011 diagnostic ask, same resolver). | Re-running `spt adapter add --github user/repo` to "reinstall" a healthy gh_release Pointer adapter silently destroys the working install (clones raw source over the extracted runtime, drops manifest+binaries) and fails `os error 2`, requiring a manual `.spt` re-extract to recover |
docs/SPT-CORE-FINDINGS.md:645:  `manifest.schema.json`. Live `spt adapter update` test rides the per-OS re-release (the registered
docs/SPT-CORE-FINDINGS.md:647:  - **LIVE-TESTED 2026-06-20 (v0.5.0 release) — surfaced TWO gh_release-update gaps (F-014, F-015).**
docs/SPT-CORE-FINDINGS.md:651:## F-014 — gh_release update fetches a single fixed `asset` (default `adapter.spt`); no per-OS resolution
docs/SPT-CORE-FINDINGS.md:654:`ADAPTER_UPDATE_FAIL: fetch: HTTP 404 Not Found` fetching `adapter.spt`. The release ships **per-OS**
docs/SPT-CORE-FINDINGS.md:658:manifest `[update]` has no `{os}`/`{arch}` placeholder. So the avenue cannot target a per-OS asset.
docs/SPT-CORE-FINDINGS.md:660:OS; the active fleet is windows). **Needs (doyle):** per-OS resolution in the gh_release update avenue
docs/SPT-CORE-FINDINGS.md:665:**Surfaced:** 2026-06-20 — after the F-014 stopgap, update got past fetch but →
docs/SPT-CORE-FINDINGS.md:678:- **W3 FIX VALIDATED on spt-core v0.13.2 (perri, 2026-06-22).** doyle's ADR-0025 W3 fix
docs/SPT-CORE-FINDINGS.md:695:    translate-proof OK). **F-010×F-015 psyche-lock: validated CLOSED on v0.13.2.** (A destructive
docs/SPT-CORE-FINDINGS.md:777:- **(b) adapter ships a wrapper launcher** — `command = "claude-spt-launch {id}"`, a tiny per-OS shim
docs/SPT-CORE-FINDINGS.md:910:**Surfaced:** 2026-06-22, building the `[message-idle-translation-binary]` seam (v0.13.0 harness
docs/SPT-CORE-FINDINGS.md:946:v0.13.1 cut (counter 28+) — not a code wait. Also spawned the sibling DX win (no `translate-proof`):
docs/SPT-CORE-FINDINGS.md:956:**Surfaced:** 2026-06-22, running `spt adapter translate-proof` (v0.13.1) against `claude-spt` in
docs/SPT-CORE-FINDINGS.md:959:doyle acked it 2026-06-22 and is folding the fix into the v0.13.x adapter-DX batch.
docs/SPT-CORE-FINDINGS.md:981:now **stale on 0.13.x** (would hit the same error); it needs the same install-dir staging.
docs/SPT-CORE-FINDINGS.md:983:**Suggested fix (doyle agreed, batched into v0.13.x adapter-DX).** Add a `--dir <path>` / `--manifest


go ahead and apply (C) to the existing release


Let me read the F-014 finding section in full — it records the ADR-0024 resolution shape (the actual published multi-platform format).


651	## F-014 — gh_release update fetches a single fixed `asset` (default `adapter.spt`); no per-OS resolution
652
653	**Surfaced:** 2026-06-20 — `spt adapter update claude-spt` →
654	`ADAPTER_UPDATE_FAIL: fetch: HTTP 404 Not Found` fetching `adapter.spt`. The release ships **per-OS**
655	assets (`adapter-<os>-<arch>.spt`, REQ-DIST-ADAPTER-PEROS) but the update avenue fetches one fixed
656	name — `asset` omitted ⇒ default `adapter.spt`, which we deliberately do NOT publish (the manifest
657	comment pre-flagged exactly this). `spt adapter update --help` exposes no `--asset` override and the
658	manifest `[update]` has no `{os}`/`{arch}` placeholder. So the avenue cannot target a per-OS asset.
659	**Stopgap (applied):** published `adapter.spt` = the windows build to the v0.5.0 release (this node's
660	OS; the active fleet is windows). **Needs (doyle):** per-OS resolution in the gh_release update avenue
661	— an `asset = "adapter-{os}-{arch}.spt"` placeholder, or host-derive like `--release` acquisition.
662

## F-014 — gh_release update fetches a single fixed `asset` (default `adapter.spt`); no per-OS resolution

**Surfaced:** 2026-06-20 — `spt adapter update claude-spt` →
`ADAPTER_UPDATE_FAIL: fetch: HTTP 404 Not Found` fetching `adapter.spt`. The release ships **per-OS**
assets (`adapter-<os>-<arch>.spt`, REQ-DIST-ADAPTER-PEROS) but the update avenue fetches one fixed
name — `asset` omitted ⇒ default `adapter.spt`, which we deliberately do NOT publish (the manifest
comment pre-flagged exactly this). `spt adapter update --help` exposes no `--asset` override and the
manifest `[update]` has no `{os}`/`{arch}` placeholder. So the avenue cannot target a per-OS asset.
**Stopgap (applied):** published `adapter.spt` = the windows build to the v0.5.0 release (this node's
OS; the active fleet is windows). **Needs (doyle):** per-OS resolution in the gh_release update avenue
— an `asset = "adapter-{os}-{arch}.spt"` placeholder, or host-derive like `--release` acquisition.


The F-014 section was never updated with an ADR-0024 resolution — the in-repo record still shows it open with two candidate fixes (`asset = "adapter-{os}-{arch}.spt"` placeholder **or** host-derive). The installed v0.13.2 schema/help show only a single `asset` (no placeholder). I cannot confirm the shipped multi-platform format from what I've seen — and guessing violates the public-surface constraint. Verify against the **published** surface + the actual v0.6.0 release assets.


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code" && echo "=== v0.6.0 release assets ==="; gh release view v0.6.0 --repo SaberMage/spt-claude-code --json assets --jq '.assets[].name' 2>&1; echo "=== latest releases ==="; gh release list --repo SaberMage/spt-claude-code --limit 5 2>&1; echo "=== installed manifest [update] (v0.6.0 as shipped) ==="; grep -n -A4 '^\[update\]' "C:/Users/decid/AppData/Local/spt-core/adapters/_github/SaberMage-spt-claude-code/manifest.toml" 2>&1

List v0.6.0 assets, releases, installed [update] block


=== v0.6.0 release assets ===
adapter-linux-x86_64.spt
adapter-windows-x86_64.spt
adapter.spt
=== latest releases ===
v0.6.0 — idle message delivery + session resume	Latest	v0.6.0	2026-06-22T10:41:33Z
v0.5.0 — SessionStart identity/ring briefs		v0.5.0	2026-06-20T06:06:29Z
v0.4.0		v0.4.0	2026-06-18T23:02:18Z
v0.3.0		v0.3.0	2026-06-17T09:33:36Z
v0.2.1		v0.2.1	2026-06-17T06:27:33Z
=== installed manifest [update] (v0.6.0 as shipped) ===
65:[update]
66-avenue = "gh_release"
67-repo = "SaberMage/spt-claude-code"
68-
69-# ── [hooks.<event>] ──────────────────────────────────────────────────────────────────────────


Current state confirmed: v0.6.0 ships 3 assets (per-OS pair + the `adapter.spt` windows-copy stopgap), and the installed `[update]` omits `asset` → defaults to the stopgap (breaks on a linux host). To fix F-014 properly I need v0.13.2's actual multi-platform mechanism. Pull the **published** spt-releases v0.13.2 notes + manifest/update reference.


echo "=== spt-releases recent releases ==="; gh release list --repo SaberMage/spt-releases --limit 8 2>&1; echo; echo "=== v0.13.2 / 0.13.x notes (multi-platform / asset / adapter) ==="; for t in v0.13.2 v0.13.1 v0.13.0; do echo "--- $t ---"; gh release view "$t" --repo SaberMage/spt-releases --json body --jq '.body' 2>&1 | grep -iE "multi.?platform|per-os|per-triple|asset|adapter.*update|gh_release|\{os\}|\{arch\}|F-014|ADR-0024|fat" | head -15; done

Fetch published spt-releases notes for multi-platform adapter update


=== spt-releases recent releases ===
spt v0.13.2	Latest	v0.13.2	2026-06-23T02:42:54Z
spt v0.13.1		v0.13.1	2026-06-22T09:17:34Z
spt v0.13.0		v0.13.0	2026-06-22T06:47:17Z
spt v0.12.1		v0.12.1	2026-06-19T06:18:14Z
spt v0.12.0		v0.12.0	2026-06-18T12:24:50Z
spt v0.11.0		v0.11.0	2026-06-17T17:54:02Z
spt v0.10.0		v0.10.0	2026-06-17T10:28:46Z
spt v0.9.1		v0.9.1	2026-06-17T09:17:40Z

=== v0.13.2 / 0.13.x notes (multi-platform / asset / adapter) ===
--- v0.13.2 ---
A release focused on adapter packaging and updates: one adapter package can cover several platforms, adapters update live without restarting your agents, installs can pull from private GitHub repositories, plus a few adapter-tooling conveniences.
- **Adapters update without restarting your agents.** When an adapter updates, the daemon stops just that adapter's background binary, swaps it in place, reloads its manifest, and restarts it — running agents continue across the update.
- **Adapter installs and updates can use private GitHub repositories.** `spt adapter add --release` can fetch from a private repo through the GitHub CLI, with no access token to manage. New `--gh` / `--https` flags choose the transport (default: automatic).
- **Adapters can show a notice after they update.** An adapter may declare a short Markdown message shown once, only when an update is actually applied.
--- v0.13.1 ---
--- v0.13.0 ---


v0.13.2 notes confirm: **"one adapter package can cover several platforms"** (single fat package, shape b) + live update without restart + private-repo + update notice. Need the exact packaging layout. Read the full v0.13.2 body.


gh release view v0.13.2 --repo SaberMage/spt-releases --json body --jq '.body' 2>&1

Read full v0.13.2 release notes


A release focused on adapter packaging and updates: one adapter package can cover several platforms, adapters update live without restarting your agents, installs can pull from private GitHub repositories, plus a few adapter-tooling conveniences.

### Added

- **One adapter package can cover multiple platforms.** A `.spt` adapter can now bundle binaries for several operating systems and CPU architectures alongside one shared manifest; installing extracts the shared files plus the binary for your platform. Existing single-platform packages keep working.
- **Adapters update without restarting your agents.** When an adapter updates, the daemon stops just that adapter's background binary, swaps it in place, reloads its manifest, and restarts it — running agents continue across the update.
- **Adapter installs and updates can use private GitHub repositories.** `spt adapter add --release` can fetch from a private repo through the GitHub CLI, with no access token to manage. New `--gh` / `--https` flags choose the transport (default: automatic).
- **Adapters can show a notice after they update.** An adapter may declare a short Markdown message shown once, only when an update is actually applied.
- **`spt adapter version <name>`** prints an installed adapter's version.
- **`spt adapter digest-proof` and `spt adapter translate-proof` can test an unpackaged adapter** via new `--dir` / `--manifest` options — proof a development or bare-file adapter before it's installed.

### Fixed

- **`spt --help` and the CLI reference no longer leak internal tracking codes.** Generated help and reference text are swept clean of internal identifiers.


Published spec confirms shape (b): **one `.spt` bundles binaries for several OS/arch + shared manifest; install extracts shared files + the host-platform binary.** But the release notes don't give the **on-disk subdir layout** (the make-or-break authoring detail). And it confirms part 2: `digest-proof`/`translate-proof --dir/--manifest`. Find the precise layout from the published packaging contract.


echo "=== adapter verbs (pack?) ==="; spt adapter --help 2>&1 | grep -iE "pack|bundle|platform|build" ; echo "=== how-to topics ==="; spt how-to 2>&1 | head -40; echo "=== how-to adapter/packaging ==="; for t in adapter packaging package multi-platform release publish adapter-packaging; do echo "--- $t ---"; spt how-to "$t" 2>&1 | head -3; done

Look for packaging command and how-to topics


=== adapter verbs (pack?) ===
=== how-to topics ===
Topics (spt how-to <topic>):
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
  subnet  pair machines: create vs join, the 6-digit code, reaching remote agents
  live    run as a live agent: the persistent listen relay, --once is NOT it, the Psyche seam
=== how-to adapter/packaging ===
--- adapter ---
NO_SUCH_TOPIC:adapter — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
--- packaging ---
NO_SUCH_TOPIC:packaging — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
--- package ---
NO_SUCH_TOPIC:package — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
--- multi-platform ---
NO_SUCH_TOPIC:multi-platform — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
--- release ---
NO_SUCH_TOPIC:release — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
--- publish ---
NO_SUCH_TOPIC:publish — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED
--- adapter-packaging ---
NO_SUCH_TOPIC:adapter-packaging — topics:
  ready   receive messages: listener guidance + the --once fallback loop
  send    deliver messages: send vs ring, replies, SENT vs QUEUED


No CLI pack command or how-to for packaging. The authoring layout must live in the published docs/schema. Pull the latest published `manifest.schema.json` from spt-releases and the gh-pages contract.


echo "=== spt-releases v0.13.2 assets ==="; gh release view v0.13.2 --repo SaberMage/spt-releases --json assets --jq '.assets[].name' 2>&1; echo "=== mock-adapter / schema in repo? ==="; gh api repos/SaberMage/spt-releases/contents 2>&1 | grep -iE '"name"' | head -30

List spt-releases assets and repo contents for schema/mock-adapter


spt-releases v0.13.2 ships `manifest.schema.json` (latest published schema), `mock-adapter.zip` (the reference adapter source — explicitly public surface), and `update-set.json`. The mock-adapter is the canonical authoring example. Download the latest schema + mock-adapter and inspect the multi-platform layout.


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; mkdir -p "$SD"; cd "$SD" && \
gh release download v0.13.2 --repo SaberMage/spt-releases --pattern manifest.schema.json --pattern mock-adapter.zip --pattern update-set.json --clobber 2>&1; echo "DL_EXIT=$?"; echo "=== files ==="; ls -la; echo "=== update-set.json ==="; cat update-set.json 2>&1 | head -40

Download latest schema, mock-adapter, update-set


DL_EXIT=0
=== files ===
total 56
drwxr-xr-x 1 decid 197609     0 Jun 22 22:13 .
drwxr-xr-x 1 decid 197609     0 Jun 22 20:10 ..
-rw-r--r-- 1 decid 197609 35148 Jun 22 22:13 manifest.schema.json
-rw-r--r-- 1 decid 197609  8281 Jun 22 22:13 mock-adapter.zip
-rw-r--r-- 1 decid 197609   742 Jun 22 22:13 update-set.json
=== update-set.json ===
{
  "metadata_json": "{\"version\":29,\"channel\":\"stable\",\"expires_at_ms\":1813718568672,\"key_id\":\"rel-primary-2026\",\"artifacts\":{\"x86_64-pc-windows-msvc\":{\"artifact_sha256\":\"44c9090243ebe9a26efbe42c7457120e740e3f430f0a5562eaddbd8f6850d49f\",\"brain_ipc_version\":1,\"broker_resource_abi\":1,\"asset_name\":\"spt-x86_64-windows.exe\"},\"x86_64-unknown-linux-gnu\":{\"artifact_sha256\":\"e270734f05b388fb4ac431b1fa4221726567f9d8609e94c70af4790b4f0afc33\",\"brain_ipc_version\":1,\"broker_resource_abi\":1,\"asset_name\":\"spt-x86_64-linux\"}},\"product_version\":\"0.13.2\"}",
  "signature_hex": "6f9d45bbfee1c18c0f701680db8682340ec8022eacdfdad31a9628ac917447cb7863678dd79a527dd9434d536ad253104b6864b0aa462539afba0fc4f87c2a08"
}


`update-set.json` = spt-core's own binary self-update (per-platform assets keyed by Rust target triple: `x86_64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`). That's the triple convention. Now inspect the authoritative adapter reference — mock-adapter + the latest schema's update/asset section.


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; cd "$SD" && rm -rf mock && mkdir mock && cd mock && unzip -o ../mock-adapter.zip >/dev/null 2>&1; echo "=== mock-adapter tree ==="; find . -type f | head -60; echo; echo "=== any multi-platform/build/pack refs in mock-adapter ==="; grep -rn -iE "platform|triple|x86_64|aarch64|\.spt|bin/|target|package|bundle" . 2>/dev/null | grep -v -iE "\.lock|target/" | head -30

Extract and inspect mock-adapter layout


=== mock-adapter tree ===
./Cargo.toml
./manifest.toml
./src/main.rs
./src/shell.rs
./tests/manifest_valid.rs

=== any multi-platform/build/pack refs in mock-adapter ===
./Cargo.toml:1:[package]
./manifest.toml:82:[env.SPT_AGENT_ID]
./src/main.rs:2://! cross-platform stand-in for a real harness session (no `claude`, no `.claude`,
./src/main.rs:74:            // plumbing. Tiny + cross-platform; absent ⇒ unchanged dummy behavior.


mock-adapter is a single-crate source adapter — doesn't demonstrate multi-platform `.spt` packaging. The latest schema is the remaining authoritative source. Read its update/asset section + any new multi-platform fields.


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; cd "$SD" && echo "=== latest schema: platform/triple/binary/asset/bundle refs ==="; grep -n -iE "platform|triple|binarie|bundle|asset|x86_64|os.?arch|extract|subdir" manifest.schema.json | head -40; echo "=== diff vs vendored (v?) — asset desc ==="; grep -n -A6 '"asset"' manifest.schema.json

Inspect latest schema for multi-platform fields


=== latest schema: platform/triple/binary/asset/bundle refs ===
39:      "description": "`[digest]` — the adapter-declared session-digest extractor seam (ADR-0019).",
155:        "host_binaries": {
156:          "description": "The harness executable basenames this `kind=\"harness\"` adapter hosts\nagents inside (e.g. `host_binaries = [\"claude\"]`). The bind-time\nadapter-resolution match-key (REQ-MANIFEST-8 / ADR-0021): a harness\nsession's parent pid → its exe basename selects the candidate adapters\nwhose `host_binaries` contains it (case-insensitive, `.exe`-stripped), so\n`listen`/`poll` resolve the owning adapter at bind with no mandatory\n`--adapter`. Additive + N-1-safe (omitted from serialization when empty,\nlike `shortcut_basename`); an empty list is harmless (the adapter is never\na bind-time candidate, only reachable via the explicit `--adapter`).",
421:      "description": "`[digest]` — the session-digest extractor seam (ADR-0019). Reverses M9's\n\"no manifest seam\" stance: the digest gets its **own** adapter-declared\nextractor, distinct from `[history]` (which stays opaque + single-session and\nfeeds the echo-commune verbatim). The extractor maps the harness's **native**\nlog → the published `{role, text, tool, ts}` digest-record contract\n([`spt_term::DigestRecord`]).\n\n**Imperative, not a DSL** (ADR-0019 §Decision): real harness logs are nested\n(one line → many entries, mixed block lists, types to filter) — a flat\ndeclarative map cannot express them, and a map powerful enough is a reinvented\nlanguage. So the extractor is an opaque command spt-core never parses, exactly\nlike every other manifest template.\n\n**Source.** By default the extractor reads the **same files as `[history]`**\n(the `locate_template`; DRY). An adapter may override with `source` (the\nown-source escape hatch). `api digest-entry` push remains the always-available\nfallback for a log-less adapter (which declares no `[digest]` at all).\n\n**Presentation.** `window_turns`, `arg_truncation`, and `sprint_collapse` are\nadapter-declared **defaults** any consumer may override at pull/subscribe;\nspt-core ships fallback defaults ([`spt_term::DigestConfig`]) when absent. The\nfixed \"~3 turns\" is no longer an spt-core requirement (ADR-0019).",
424:        "extractor": {
425:          "description": "Opaque extractor command: native harness log → the `{role,text,tool,ts}`\ncontract (one JSON record per output line). `{key}` substitution applies\n(e.g. `{session_id}`, and `{source}` for the resolved log path).",
429:          "description": "Own-source escape hatch: a `locate_template` for the log file(s) the\nextractor reads. Absent ⇒ reuse `[history].locate_template` (DRY).",
462:        "extractor"
566:        "asset": {
567:          "description": "`gh_release` avenue: the release **asset** name to fetch (the adapter\n`.spt` archive). Absent ⇒ the default `adapter.spt`, matching the\n`spt adapter add --release` acquisition primitive (REQ-INSTALL-9). Not\napplicable to `delegated` / `file_pull`.",
633:      "description": "Fetch transport for the `gh_release` avenue (and `spt adapter add --release`):\nhow spt-core retrieves the release asset bytes + the latest-release version.\n(REQ-ADAPTER-GH-TRANSPORT)",
641:          "description": "Shell the pre-authorized `gh` CLI (`gh release download` for the asset,\n`gh api` for the version) — the private-repo path. `gh` honors OAuth +\n`GH_TOKEN`, so spt-core never custodies a token.",
=== diff vs vendored (v?) — asset desc ===
566:        "asset": {
567-          "description": "`gh_release` avenue: the release **asset** name to fetch (the adapter\n`.spt` archive). Absent ⇒ the default `adapter.spt`, matching the\n`spt adapter add --release` acquisition primitive (REQ-INSTALL-9). Not\napplicable to `delegated` / `file_pull`.",
568-          "type": [
569-            "string",
570-            "null"
571-          ]
572-        },


Even the latest v0.13.2 schema's `asset` field still documents only the single `adapter.spt` default — **no multi-platform layout, no `{os}/{arch}` placeholder**. Release notes describe the *behavior* but neither schema, CLI help, nor how-to gives the **authoring subdir layout**. Before concluding it's an undocumented-surface finding, check the GH-Pages docs (the other published surface). Pull `llms-full.txt` and grep for the layout.


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; cd "$SD" && curl -fsSL "https://sabermage.github.io/spt-releases/llms-full.txt" -o llms-full.txt 2>&1; echo "CURL_EXIT=$? size=$(wc -c < llms-full.txt 2>/dev/null)"; echo "=== multi-platform / .spt layout refs ==="; grep -n -iE "multi.?platform|several platform|bundle.*binar|binar.*platform|per-triple|target.?triple|x86_64-pc-windows|x86_64-unknown-linux|\.spt archive|extracts? the (shared|binary)|asset.*\{|\{os\}|\{arch\}" llms-full.txt 2>/dev/null | head -30

Fetch published llms-full.txt and grep for multi-platform layout


CURL_EXIT=0 size=199938
=== multi-platform / .spt layout refs ===


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; cd "$SD" && echo "=== .spt / asset / release-archive mentions ==="; grep -n -iE "\.spt|adapter\.spt|--release|--asset|release asset|archive (root|whose)" llms-full.txt | head -30; echo "=== count of 'platform' ==="; grep -ic "platform" llms-full.txt

Broader grep for .spt/asset/platform in published docs


=== .spt / asset / release-archive mentions ===
1162:resolves against the adapter's **install dir** before `PATH`, so a `.spt` that
1166:adapter was registered (the `--release`/`--github` durable home, or the
1169:`spt adapter digest-proof`. Ship a binary in your `.spt` and reference it by
1403:| `gh_release` | `repo` | spt-core ships your updates from your own GitHub releases (since v0.8.0). `asset` (default `adapter.spt`) and `signing_key` are optional |
1417:`spt adapter add --release`, and your registered adapter stays current.
1423:asset = "adapter.spt"            # optional: the release asset to fetch (default adapter.spt)
1429:version against the installed one and, when newer, fetches the release `.spt`
1430:archive — the same archive `spt adapter add --release` installs — then
1434:fetched `.spt` is trusted on HTTPS + GitHub, exactly like first acquisition.
1435:Declare a `signing_key` and the fetched `.spt` is verified against a **detached
1436:signature** you publish as a sibling release asset named `<asset>.sig` — a
1817:"$SPT" adapter add --release <your-org>/<your-adapter-repo>               # latest
1818:"$SPT" adapter add --release <your-org>/<your-adapter-repo> --tag v1.0.0  # pinned
1833:**`--release` is the recommended distribution.** It fetches a `.spt` archive
1840:packs the archive (`tar -czf adapter.spt manifest.toml strings/ bin/…`) and
1841:uploads it as a release asset, so the adapter ships straight from your existing
1842:repo. Override the asset name with `--asset` (default `adapter.spt`).
1855:binaries your manifest references in the `.spt` (or repo) too, and reference them
1857:against the adapter's install dir before `PATH`, so a `.spt` that ships its
1866:> `--release`-distributed adapter is **`gh_release`** (since v0.8.0): declare
1868:> `spt adapter update` ships the latest release `.spt` to the node — fetched,
1873:> the roadmap**). Deliver the manifest with `adapter add --release` (or
1898:| `SPT_INSTALL_ASSET_BASE` | A URL or local directory holding the release assets + `SHA256SUMS` directly (CI, air-gap, mirrors) |
2708:  [PATH]  Local manifest source (omit when using --github or --release)
2713:      --release <RELEASE>  GitHub release source user/repo — fetch the adapter archive asset from
2716:      --tag <TAG>          Release tag for --release (default: the latest release)
2717:      --asset <ASSET>      Release asset name for --release (default: adapter.spt — a tar archive
2719:      --gh                 Force the gh CLI transport for --release (the private-repo path; gh
2722:      --https              Force direct HTTPS transport for --release (public repos). Mutually
4582:  as a release asset with every release.
=== count of 'platform' ===
2


Found it — line 1840 shows the published archive layout: `tar -czf adapter.spt manifest.toml strings/ bin/…`. Read the full packaging guidance (1150-1175 and 1830-1875) for the multi-platform `bin/` convention.


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; cd "$SD" && echo "=== 1150-1180 ==="; sed -n '1150,1180p' llms-full.txt; echo "=== 1825,1875 ==="; sed -n '1825,1875p' llms-full.txt

Read packaging guidance sections


=== 1150-1180 ===
detach = true
keys = ["psyche_prompt", "psyche_dir"]
```

For the Psyche roles (`psyche_init` / `psyche_resume`) spt-core fills exactly
`{id}` (the nested `<parent>-psyche` id), `{session_id}`, `{psyche_dir}`, and
`{psyche_prompt}` — **not** `{session_name}` (that key is a `[session.self]`
fill). Declaring a key your role's spawn isn't given fails at spawn, so a
`psyche_init` template must template only those four.

**Shipped binaries resolve from the install dir (since v0.8.0).** A command
template's bare program token (its first token, e.g. `my-harness-digest`)
resolves against the adapter's **install dir** before `PATH`, so a `.spt` that
ships its own binaries is self-contained — no PATH placement needed. spt-core
runs `<install_dir>/<program>` (on Windows also trying the `.exe` suffix) when
that file exists, else falls back to `PATH`. The install dir is where your
adapter was registered (the `--release`/`--github` durable home, or the
copy-mode source dir). This applies to the `[session.psyche_init]` runner, the
[`[digest]`](#digest--session-digest-extractor) extractor, and
`spt adapter digest-proof`. Ship a binary in your `.spt` and reference it by
bare name; you need not place it on `PATH`.

| Field | Required | Meaning |
|---|---|---|
| `command` | yes | Opaque command line with `{key}` placeholders |
| `cwd` | no | Working directory (substitutable) |
| `recursion_guard_env` | no | Env var set on summarizer children so *their* hooks bail (no summarizer-of-summarizer loops) |
| `detach` | no (default `false`) | Spawn detached |
| `env_remove` | no | Env vars stripped from the child's inherited environment |
| `keys` | no | The substitution keys spt-core fills for this role |

=== 1825,1875 ===
`adapter add` is **manifest-first** — a clean add proves the cross-field manifest
shape — and it conducts your
[`[update]`](manifest.md#update--adapter-self-update) avenue once (install is the
first update). Confirm with `spt adapter list`: your adapter and its version
appear. Keep this idempotent in your bootstrap the same way the binary check is —
register when `adapter list` shows your adapter missing or below the expected
version.

**`--release` is the recommended distribution.** It fetches a `.spt` archive
asset — a tar whose root holds `manifest.toml` + `strings/` + the binaries the
manifest points at — from the named GitHub release, extracts it to the durable
registry home, and registers the root. That ships your **built binaries**,
source-free and **versioned by tag** (`--tag`, default the latest release), and
first-acquisition trusts HTTPS + GitHub exactly like the install one-liner's
first binary fetch. A development **monorepo stays a monorepo**: your release CI
packs the archive (`tar -czf adapter.spt manifest.toml strings/ bin/…`) and
uploads it as a release asset, so the adapter ships straight from your existing
repo. Override the asset name with `--asset` (default `adapter.spt`).

`--github` is the alternative for an adapter whose **repo root already holds
`manifest.toml`**: it clones the repo and registers the clone root (`adapter add`
resolves a directory source to `<dir>/manifest.toml` at the root). Local
development uses the directory form, which takes any path or filename:
`spt adapter add ./adapters/my-adapter.toml`.

What registration holds under `adapters/<name>/` follows your
[`[update]`](manifest.md#update--adapter-self-update) avenue: a `delegated` or
`gh_release` adapter is **pointer-mode** (the manifest and `strings/` are read
live from the durable home), and a `file_pull` (or avenue-less) adapter is
**copy-mode** (the `manifest.toml` and `strings/` are copied in). Publish the
binaries your manifest references in the `.spt` (or repo) too, and reference them
**by bare name**: since v0.8.0, a command template's program token resolves
against the adapter's install dir before `PATH`, so a `.spt` that ships its
binaries is **self-contained** — the shipped binary is found without any PATH
placement. (Absolute paths still work; an unshipped tool still falls back to
`PATH`.) This applies to the `[session.psyche_init]` runner, the `[digest]`
extractor, and `spt adapter digest-proof`.

> **"Install the plugin, get the adapter for free" — include the activation
> step.** The [`[update]`](manifest.md#update--adapter-self-update) avenues
> keep a *registered* adapter current. The straightforward path for a
> `--release`-distributed adapter is **`gh_release`** (since v0.8.0): declare
> `avenue = "gh_release", repo = "your-org/your-adapter"` and
> `spt adapter update` ships the latest release `.spt` to the node — fetched,
> optionally verified against your `signing_key`, re-extracted, and
> re-registered. The other avenues: `delegated` (your harness's own updater
> installs the content — set `self_verifies = true` to attest it verifies what
> it installs), and `file_pull` (its automatic network-pull transport is **on
> the roadmap**). Deliver the manifest with `adapter add --release` (or
> `--github`, or a packed local dir) and let `gh_release` carry updates.


The published packaging docs (llms-full.txt §`--release`) give the **single-platform** layout: `tar -czf adapter.spt manifest.toml strings/ bin/…`, binaries bare-name-resolved from the install-dir root. But the **multi-platform** subdir convention (which dir each platform's binary goes in, so install extracts "the binary for your platform") is **absent**. Final exhaustive check before concluding it's a gap.


SD="C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-claude-code/e2a44c8e-df24-4cba-8ede-fff02a618f5e/scratchpad"; cd "$SD" && echo "=== broad platform/arch/os/extract terms ==="; grep -n -iE "msvc|unknown-linux|operating system|architecture|cpu arch|your platform|the binary for|extracts? (the )?(shared|binary|files)|several (operating|platform)|host (platform|os|triple)" llms-full.txt | head -25; echo "=== 'bin/' occurrences ==="; grep -n "bin/" llms-full.txt | head

Final broad grep for multi-platform extraction layout


=== broad platform/arch/os/extract terms ===
=== 'bin/' occurrences ===
1783:  SPT="$HOME/.local/bin/spt"
1840:packs the archive (`tar -czf adapter.spt manifest.toml strings/ bin/…`) and
1887:sourced) is handled the same way: `$HOME/.local/bin/spt` absolutely, once.


Exhaustively confirmed: the published surface documents the multi-platform `.spt` **behavior** but not the **authoring layout** (the platform-subdir convention an author must pack so install extracts the host's binary). The single-platform layout is documented; the multi-platform one is absent across release notes, schema, llms-full, CLI help, how-to, and mock-adapter.

That blocks C's main half (drop F-014 → single fat `.spt`) — I won't guess spt-core's extraction convention and ship a release that could brick end-user auto-update. doyle authored this (v0.13.2) and is online — ring him to confirm the layout (or that it's an undocumented gap) before I either build it or log a finding.


printf '%s' "perri — picking up C (modernize claude-spt packaging on v0.13.2: drop the F-014 per-OS stopgap for a single multi-platform .spt). Blocked on an AUTHORING-LAYOUT question I cant answer from the public surface:

v0.13.2 notes say one .spt can bundle binaries for several OS/arch + one shared manifest, and install extracts the shared files + the host-platform binary. But I cant find the ON-DISK LAYOUT an author must pack — i.e. WHICH subdir each platforms binary goes in so spt-core extracts the right one. Checked exhaustively: v0.13.2 release notes (behavior only), latest manifest.schema.json (asset still = single adapter.spt, no platform layout / no {os}{arch} placeholder), llms-full.txt (only the SINGLE-platform layout: tar -czf adapter.spt manifest.toml strings/ bin/...), adapter add --help, how-to (no packaging topic), and the mock-adapter (single-crate source, no multi-platform example).

Q1: whats the exact multi-platform .spt layout? e.g. bin/<target-triple>/<binary> (x86_64-pc-windows-msvc / x86_64-unknown-linux-gnu, matching update-set.json), or <os>-<arch>/, or a manifest table mapping platform->path? And does the shared-binary bare-name resolution still hit <install_dir>/<program> at root post-extract?
Q2: is this layout documented anywhere published I missed? If not, its a public-surface gap (a harness author cant author a multi-platform .spt from the published surface) — Ill log it as a finding unless you point me at the doc.

If you give me the layout Im unblocked to repack v0.6.0 as one fat .spt + drop the stopgap. (The other half of C — simplifying my translate-proof int to --dir/--manifest — is unblocked, doing that now.)" | $OWL ring doyle --timeout 90 2>&1; echo "RING_EXIT=$?"

Ring doyle for multi-platform .spt layout


error: unexpected argument '--timeout' found

  tip: to pass '--timeout' as a value, use '-- --timeout'

Usage: owl.exe ring <TARGET> [FROM]

For more information, try '--help'.
RING_EXIT=2


Legacy `ring` has no `--timeout` and would block my poll loop. Use `send` (fire-and-forget) so I stay reachable; doyle's reply arrives on the event stream. Then proceed with the unblocked half.


$OWL send doyle <<'EOF'
perri — picking up C (modernize claude-spt packaging on v0.13.2: drop the F-014 per-OS stopgap for a single multi-platform .spt). Blocked on an AUTHORING-LAYOUT question I can't answer from the public surface:

v0.13.2 notes say one .spt can bundle binaries for several OS/arch + one shared manifest, and install extracts the shared files + the host-platform binary. But I can't find the ON-DISK LAYOUT an author must pack — WHICH subdir each platform's binary goes in so spt-core extracts the right one. Checked exhaustively: v0.13.2 release notes (behavior only), latest manifest.schema.json (asset still = single adapter.spt, no platform layout / no {os}{arch} placeholder), llms-full.txt (only the SINGLE-platform layout: tar -czf adapter.spt manifest.toml strings/ bin/...), adapter add --help, how-to (no packaging topic), mock-adapter (single-crate source, no multi-platform example).

Q1: exact multi-platform .spt layout? e.g. bin/<target-triple>/<binary> (x86_64-pc-windows-msvc / x86_64-unknown-linux-gnu, matching update-set.json), or <os>-<arch>/, or a manifest table mapping platform->path? And does shared-binary bare-name resolution still hit <install_dir>/<program> at root post-extract?
Q2: documented anywhere published I missed? If not it's a public-surface gap (a harness author can't author a multi-platform .spt from the published surface) — I'll log it as a finding unless you point me at the doc.

Give me the layout and I'm unblocked to repack v0.6.0 as one fat .spt + drop the stopgap. The other half of C (simplifying my translate-proof int to --dir/--manifest) is unblocked — doing that now.
EOF
echo "SEND_EXIT=$?"

Send layout question to doyle (non-blocking)


➜ SENT:doyle
SEND_EXIT=0


Sent to doyle. Now the unblocked half — simplify `translate-proof-int.sh` to use the v0.13.2 `--dir`/`--manifest` override (F-011 closed). Read the current script.


1	#!/bin/sh
2	# Integration proof: `spt adapter translate-proof` spawns + feeds the claude-spt
3	# [message-idle-translation-binary] (cc-spt-idle-translate) EXACTLY as the daemon does at idle
4	# delivery (init line then the --event envelope), reads back the emitted keystroke-command stream,
5	# and gates it. This is the EMIT half of REQ-DIST-IDLE-TRANSLATE — the cross-process author-time proof
6	# (ADR-0022, the EMIT-half mirror of digest-proof). It does NOT exercise the daemon's atomic PTY APPLY
7	# or controller buffering — that half stays the real-claude bringup int (deferred). [int->REQ-DIST-IDLE-TRANSLATE]
8	#
9	# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) + a
10	# present spt >= 0.13.1 (translate-proof landed in v0.13.1 / counter 28) + a built binary. Idempotent.
11	# Run: SPTC_ACCEPTANCE=1 sh ci/idle-translate/translate-proof-int.sh
12	#
13	# GREEN since spt v0.13.1 (counter 28) against the {commit}-terminated binary: emits the 4-command
14	# choreography ctrl+s · 50ms · {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK /
15	# "commit: yes". The no-{commit} form FAULTs (the F-016 defect the proof's no-commit gate catches).
16	#
17	# INSTALL-DIR NOTE: claude-spt declares [update] avenue="gh_release", so a bare-file `adapter add`
18	# registers it GhReleaseManaged and the runtime resolver wants the EXTRACTED install (manifest.toml +
19	# the binary co-located), not the dev source file. So this int stages a real install DIR — manifest.toml
20	# (the renamed manifest) + strings/ + the built binary — exactly the shape `adapter add --release`
21	# extracts, and points the adapter at it. (The sibling ci/digest/digest-proof-int.sh predates this
22	# GhReleaseManaged behavior and uses the now-stale bare-file form.)
23	set -u
24	ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
25	MANIFEST="$ROOT/adapter/claude-spt.toml"
26	STRINGS="$ROOT/adapter/strings"
27	RELDIR="$ROOT/tools/cc-spt-idle-translate/target/release"
28	EVENT='<EVENT type="msg" from="ci">translate-proof int probe</EVENT>'
29
30	[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }
31	command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
32
33	# translate-proof is a v0.13.1+ subcommand — announce the version gap rather than fail on an old binary.
34	if ! spt adapter --help 2>&1 | grep -q 'translate-proof'; then
35	  echo "SKIP: spt has no 'adapter translate-proof' (needs v0.13.1 / counter 28+). Binary itself: cargo tests green."
36	  exit 0
37	fi
38
39	BIN="$RELDIR/cc-spt-idle-translate"
40	[ -x "$BIN" ] || BIN="$RELDIR/cc-spt-idle-translate.exe"
41	[ -x "$BIN" ] || { echo "SKIP: binary not built (run sh ci/idle-translate/build.sh)"; exit 0; }
42
43	# Stage the install dir the resolver expects (manifest.toml + strings/ + the binary co-located).
44	STAGE=$(mktemp -d "${TMPDIR:-/tmp}/cc-xlate-int.XXXXXX") || { echo "FAIL: mktemp"; exit 1; }
45	trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true; rm -rf "$STAGE"' EXIT INT TERM
46	cp "$MANIFEST" "$STAGE/manifest.toml" || { echo "FAIL: stage manifest"; exit 1; }
47	cp -r "$STRINGS" "$STAGE/strings"     || { echo "FAIL: stage strings"; exit 1; }
48	cp "$BIN" "$STAGE/"                    || { echo "FAIL: stage binary"; exit 1; }
49
50	spt adapter add "$STAGE" >/dev/null 2>&1 || { echo "FAIL: spt adapter add (install dir)"; exit 1; }
51
52	out=$(spt adapter translate-proof claude-spt --event "$EVENT" 2>&1)
53	rc=0
54	case "$out" in
55	  *TRANSLATE_PROOF_OK*) echo "ok  translate-proof: TRANSLATE_PROOF_OK" ;;
56	  *) echo "FAIL: translate-proof did not pass:"; printf '%s\n' "$out"; exit 1 ;;
57	esac
58	# The {commit} terminator is the F-016 fix — assert the proof saw it (no-commit would FAULT live).
59	case "$out" in
60	  *"commit: yes"*) echo "ok  emitted the mandatory {commit} terminator (commit: yes)" ;;
61	  *) echo "FAIL: proof reports no commit terminator (the F-016 fault condition):"; printf '%s\n' "$out"; rc=1 ;;
62	esac
63	# And the verbatim \r submit rides inside the text command (not a separate enter key).
64	case "$out" in
65	  *'\r"'*) echo "ok  text command carries the trailing \\r submit" ;;
66	  *) echo "FAIL: no trailing \\r in the text command:"; printf '%s\n' "$out"; rc=1 ;;
67	esac
68
69	[ "$rc" -eq 0 ] && { echo "TRANSLATE-PROOF-INT OK"; exit 0; } || { echo "TRANSLATE-PROOF-INT FAIL"; exit 1; }
70

#!/bin/sh
# Integration proof: `spt adapter translate-proof` spawns + feeds the claude-spt
# [message-idle-translation-binary] (cc-spt-idle-translate) EXACTLY as the daemon does at idle
# delivery (init line then the --event envelope), reads back the emitted keystroke-command stream,
# and gates it. This is the EMIT half of REQ-DIST-IDLE-TRANSLATE — the cross-process author-time proof
# (ADR-0022, the EMIT-half mirror of digest-proof). It does NOT exercise the daemon's atomic PTY APPLY
# or controller buffering — that half stays the real-claude bringup int (deferred). [int->REQ-DIST-IDLE-TRANSLATE]
#
# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) + a
# present spt >= 0.13.1 (translate-proof landed in v0.13.1 / counter 28) + a built binary. Idempotent.
# Run: SPTC_ACCEPTANCE=1 sh ci/idle-translate/translate-proof-int.sh
#
# GREEN since spt v0.13.1 (counter 28) against the {commit}-terminated binary: emits the 4-command
# choreography ctrl+s · 50ms · {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK /
# "commit: yes". The no-{commit} form FAULTs (the F-016 defect the proof's no-commit gate catches).
#
# INSTALL-DIR NOTE: claude-spt declares [update] avenue="gh_release", so a bare-file `adapter add`
# registers it GhReleaseManaged and the runtime resolver wants the EXTRACTED install (manifest.toml +
# the binary co-located), not the dev source file. So this int stages a real install DIR — manifest.toml
# (the renamed manifest) + strings/ + the built binary — exactly the shape `adapter add --release`
# extracts, and points the adapter at it. (The sibling ci/digest/digest-proof-int.sh predates this
# GhReleaseManaged behavior and uses the now-stale bare-file form.)
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
STRINGS="$ROOT/adapter/strings"
RELDIR="$ROOT/tools/cc-spt-idle-translate/target/release"
EVENT='<EVENT type="msg" from="ci">translate-proof int probe</EVENT>'

[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }

# translate-proof is a v0.13.1+ subcommand — announce the version gap rather than fail on an old binary.
if ! spt adapter --help 2>&1 | grep -q 'translate-proof'; then
  echo "SKIP: spt has no 'adapter translate-proof' (needs v0.13.1 / counter 28+). Binary itself: cargo tests green."
  exit 0
fi

BIN="$RELDIR/cc-spt-idle-translate"
[ -x "$BIN" ] || BIN="$RELDIR/cc-spt-idle-translate.exe"
[ -x "$BIN" ] || { echo "SKIP: binary not built (run sh ci/idle-translate/build.sh)"; exit 0; }

# Stage the install dir the resolver expects (manifest.toml + strings/ + the binary co-located).
STAGE=$(mktemp -d "${TMPDIR:-/tmp}/cc-xlate-int.XXXXXX") || { echo "FAIL: mktemp"; exit 1; }
trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true; rm -rf "$STAGE"' EXIT INT TERM
cp "$MANIFEST" "$STAGE/manifest.toml" || { echo "FAIL: stage manifest"; exit 1; }
cp -r "$STRINGS" "$STAGE/strings"     || { echo "FAIL: stage strings"; exit 1; }
cp "$BIN" "$STAGE/"                    || { echo "FAIL: stage binary"; exit 1; }

spt adapter add "$STAGE" >/dev/null 2>&1 || { echo "FAIL: spt adapter add (install dir)"; exit 1; }

out=$(spt adapter translate-proof claude-spt --event "$EVENT" 2>&1)
rc=0
case "$out" in
  *TRANSLATE_PROOF_OK*) echo "ok  translate-proof: TRANSLATE_PROOF_OK" ;;
  *) echo "FAIL: translate-proof did not pass:"; printf '%s\n' "$out"; exit 1 ;;
esac
# The {commit} terminator is the F-016 fix — assert the proof saw it (no-commit would FAULT live).
case "$out" in
  *"commit: yes"*) echo "ok  emitted the mandatory {commit} terminator (commit: yes)" ;;
  *) echo "FAIL: proof reports no commit terminator (the F-016 fault condition):"; printf '%s\n' "$out"; rc=1 ;;
esac
# And the verbatim \r submit rides inside the text command (not a separate enter key).
case "$out" in
  *'\r"'*) echo "ok  text command carries the trailing \\r submit" ;;
  *) echo "FAIL: no trailing \\r in the text command:"; printf '%s\n' "$out"; rc=1 ;;
esac

[ "$rc" -eq 0 ] && { echo "TRANSLATE-PROOF-INT OK"; exit 0; } || { echo "TRANSLATE-PROOF-INT FAIL"; exit 1; }


Current script stages a temp install dir + `adapter add`/`adapter remove claude-spt` — which mutates (and soft-removes) the **real** registered adapter. The v0.13.2 `--dir`/`--manifest` override runs the proof against an unpackaged adapter with no registry mutation. Check the exact `--dir`/`--manifest` usage.


Show translate-proof --dir/--manifest usage


Prove an adapter's [message-idle-translation-binary] against an inbound event. Spawns and feeds
the declared translation binary exactly as the daemon does at idle-delivery — sends the init line
then the --event envelope and reads back the emitted keystroke-command stream
({key}/{text}/{delay_ms}/{commit}), printed author-readable. This is the EMIT half ONLY: it
proves the binary's spawn-feed-emit contract; it does NOT exercise the daemon's atomic PTY apply or
controller buffering. Fills {id} and {session_id} into the envelope the same way the daemon does
(use --session to pin the session id). Exit 1 if the binary fails to spawn, emits nothing, emits
no commit, or emits an unparseable line

Usage: spt.exe adapter translate-proof [OPTIONS] --event <EVENT> <OPTION>

Arguments:
  <OPTION>  <adapter> or <adapter>:<profile> (must declare [message-idle-translation-binary])

Options:
      --event <EVENT>        The inbound <EVENT…> envelope to feed. {id} and {session_id}
                             tokens in it are filled as the daemon fills them
      --session <SESSION>    The {session_id} to fill into the event envelope (the daemon fills
                             the live one at runtime). Defaults to a placeholder; pin a real id when
                             the binary's behavior depends on it
      --dir <DIR>            Proof against an on-disk install dir instead of the registered
                             adapter: the translation binary resolves in this dir before PATH (the
                             same resolution the daemon uses) and the manifest defaults to
                             <dir>/manifest.toml. No full extracted install needed — proof a DEV
                             binary from its build dir
      --manifest <MANIFEST>  Pin the manifest file for the proof (overrides
                             <dir>/manifest.toml; absent --dir, its parent dir is the install
                             dir). Lets a bare-file gh_release adapter proof without staging an
                             extracted install
  -h, --help                 Print help


Clean override available: `--dir` (binary resolves there) + `--manifest` (pin the bare-file manifest) — no staging, no `adapter add`/`remove`, no registry mutation. Test it against the dev binary before rewriting.


cd "C:/Users/decid/Documents/projects/spt-claude-code" && RELDIR="tools/cc-spt-idle-translate/target/release"; ls "$RELDIR"/cc-spt-idle-translate* 2>&1; echo "=== override invocation ==="; spt adapter translate-proof claude-spt --event '<EVENT type="msg" from="ci">translate-proof int probe</EVENT>' --manifest adapter/claude-spt.toml --dir "$RELDIR" 2>&1; echo "RC=$?"

Test translate-proof --dir/--manifest override


tools/cc-spt-idle-translate/target/release/cc-spt-idle-translate.d
tools/cc-spt-idle-translate/target/release/cc-spt-idle-translate.exe
=== override invocation ===
=== translate-proof: claude-spt ===
emitted 4 command(s), commit: yes

--- emitted keystroke commands ---
  key   ctrl+s -> [13]
  delay 50ms
  text  "<EVENT type=\"msg\" from=\"ci\">translate-proof int probe</EVENT>\r"
  commit true
TRANSLATE_PROOF_OK:claude-spt
RC=0


Override works clean (TRANSLATE_PROOF_OK, commit: yes, no registry mutation). Before rewriting, check how `run-gates.sh` invokes this int (the old SPTC_ACCEPTANCE gating) so I drop the gate consistently.


cd "C:/Users/decid/Documents/projects/spt-claude-code" && grep -n -iE "translate-proof|SPTC_ACCEPTANCE|idle-translate" ci/run-gates.sh ci/acceptance/run-acceptance.sh 2>&1 | head -20

Check how gates invoke translate-proof-int


ci/run-gates.sh:39:gate "idle-translate (cargo build + test)"
ci/run-gates.sh:40:if [ -f "$ROOT/ci/idle-translate/build.sh" ]; then
ci/run-gates.sh:41:  if sh "$ROOT/ci/idle-translate/build.sh"; then :; else fail "idle-translate"; fi
ci/run-gates.sh:43:  echo "SKIP: no idle-translate build yet"
ci/acceptance/run-acceptance.sh:6:# SLOW LANE — env-gated. Skips cleanly (rc 0) unless SPTC_ACCEPTANCE=1 AND `claude` is on PATH, so
ci/acceptance/run-acceptance.sh:13:if [ "${SPTC_ACCEPTANCE:-0}" != "1" ]; then
ci/acceptance/run-acceptance.sh:14:  echo "SKIP acceptance: set SPTC_ACCEPTANCE=1 to run the real-claude lane (deterministic gates unaffected)"


cd "C:/Users/decid/Documents/projects/spt-claude-code" && echo "=== who calls translate-proof-int.sh ==="; grep -rn "translate-proof-int" ci/ 2>&1; echo "=== run-acceptance body (what it runs) ==="; sed -n '1,60p' ci/acceptance/run-acceptance.sh

Find callers of translate-proof-int and acceptance lane contents


=== who calls translate-proof-int.sh ===
ci/idle-translate/translate-proof-int.sh:11:# Run: SPTC_ACCEPTANCE=1 sh ci/idle-translate/translate-proof-int.sh
=== run-acceptance body (what it runs) ===
#!/bin/sh
# sptc CI ACCEPTANCE — slice 1: spawn a REAL `claude` as the system-under-test, assert it fired
# the UserPromptSubmit hook (a deterministic digest-marker side-effect). The LLM is the SUT; this
# orchestration is deterministic and never judges model text. [impl->REQ-CI-ACCEPTANCE]
#
# SLOW LANE — env-gated. Skips cleanly (rc 0) unless SPTC_ACCEPTANCE=1 AND `claude` is on PATH, so
# the deterministic gate run stays green on hosts without claude/auth. Real-claude execution is the
# cross-process evidence for REQ-CI-ACCEPTANCE: [int->REQ-CI-ACCEPTANCE]
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
. "$ROOT/ci/acceptance/lib.sh"

if [ "${SPTC_ACCEPTANCE:-0}" != "1" ]; then
  echo "SKIP acceptance: set SPTC_ACCEPTANCE=1 to run the real-claude lane (deterministic gates unaffected)"
  exit 0
fi
if ! command -v claude >/dev/null 2>&1; then
  echo "SKIP acceptance: no \`claude\` on PATH (fleet host without the harness)"
  exit 0
fi

# Disposable identity FIRST — before any scaffold/spawn — so nothing can resolve the operator perch.
# [int->REQ-HAZARD-PERCH-COLLISION]
sptc_ci_identity "$$"          # sets+exports SPTC_CI_ID / SPT_AGENT_ID / OWL_SESSION_ID in-shell
sptc_ci_is_disposable "$SPTC_CI_ID" || { echo "FATAL: refusing to spawn under non-disposable id '$SPTC_CI_ID'"; exit 2; }
echo "acceptance: SUT identity = $SPTC_CI_ID (operator perch protected)"

work=$(mktemp -d 2>/dev/null) || { echo "FATAL: mktemp -d failed"; exit 2; }
proj="$work/proj"
digest="$work/digest.txt"
: > "$digest"
cleanup() { rm -rf "$work" 2>/dev/null || true; }
trap cleanup EXIT INT TERM

sptc_ci_mkproject "$proj" "$digest" >/dev/null || { echo "FATAL: scaffold failed"; exit 2; }

probe="sptc-acceptance-probe-$$"
echo "acceptance: driving real claude -p in $proj (probe='$probe')"
# Drive the SUT. We assert on the hook side-effect, not stdout — model text is irrelevant.
( cd "$proj" && claude -p "$probe" >/dev/null 2>&1 ) || \
  echo "note: claude -p exited non-zero (model/auth) — asserting on hook side-effect regardless" >&2

rc=0
sptc_ci_assert "UPS hook fired in real claude" "UPS_FIRED:$probe" "$digest" || rc=1

printf '\n=== ACCEPTANCE: %s ===\n' "$([ "$rc" -eq 0 ] && echo PASS || echo FAIL)"
exit "$rc"


`translate-proof-int.sh` is standalone (no caller in run-gates/acceptance). Since the override makes it read-only (no registry mutation), I'll drop the SPTC_ACCEPTANCE gate and the staging. Rewrite.


Write

#!/bin/sh
# Integration proof: `spt adapter translate-proof` spawns + feeds the claude-spt
# [message-idle-translation-binary] (cc-spt-idle-translate) EXACTLY as the daemon does at idle
# delivery (init line then the --event envelope), reads back the emitted keystroke-command stream,
# and gates it. This is the EMIT half of REQ-DIST-IDLE-TRANSLATE — the cross-process author-time proof
# (ADR-0022, the EMIT-half mirror of digest-proof). It does NOT exercise the daemon's atomic PTY APPLY
# or controller buffering — that half stays the real-claude bringup int (deferred). [int->REQ-DIST-IDLE-TRANSLATE]
#
# Uses the v0.13.2 `--dir`/`--manifest` override (F-011 closed, W5) to proof the DEV binary straight
# from its build dir against the bare-file manifest: --dir resolves the binary (before PATH) exactly
# as the daemon does, --manifest pins the bare-file manifest. NO registry mutation — the prior form
# staged a disposable install dir and `adapter add`/`adapter remove claude-spt`, which soft-removed
# the REAL registered claude-spt on cleanup. Read-only now, so NO SPTC_ACCEPTANCE gate. Needs
# spt >= 0.13.2 (the --dir/--manifest options) + a built binary. Idempotent.
# Run: sh ci/idle-translate/translate-proof-int.sh   (exit 0 = pass).
#
# GREEN against the {commit}-terminated binary: emits the 4-command choreography ctrl+s · 50ms ·
# {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK / "commit: yes". The no-{commit}
# form FAULTs (the F-016 defect the proof's no-commit gate catches).
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
RELDIR="$ROOT/tools/cc-spt-idle-translate/target/release"
EVENT='<EVENT type="msg" from="ci">translate-proof int probe</EVENT>'

command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }

# --dir/--manifest landed in spt v0.13.2 — capability-detect rather than version-parse.
if ! spt adapter translate-proof --help 2>&1 | grep -q -- '--dir'; then
  echo "SKIP: spt 'adapter translate-proof' has no --dir/--manifest (needs v0.13.2). Binary itself: cargo tests green."
  exit 0
fi

BIN="$RELDIR/cc-spt-idle-translate"
[ -x "$BIN" ] || BIN="$RELDIR/cc-spt-idle-translate.exe"
[ -x "$BIN" ] || { echo "SKIP: binary not built (run sh ci/idle-translate/build.sh)"; exit 0; }

# Proof the dev binary in-place: --dir resolves the binary (before PATH) like the daemon, --manifest
# pins the bare-file gh_release manifest — no extracted install, no registry touch.
out=$(spt adapter translate-proof claude-spt --event "$EVENT" --manifest "$MANIFEST" --dir "$RELDIR" 2>&1)
rc=0
case "$out" in
  *TRANSLATE_PROOF_OK*) echo "ok  translate-proof: TRANSLATE_PROOF_OK" ;;
  *) echo "FAIL: translate-proof did not pass:"; printf '%s\n' "$out"; exit 1 ;;
esac
# The {commit} terminator is the F-016 fix — assert the proof saw it (no-commit would FAULT live).
case "$out" in
  *"commit: yes"*) echo "ok  emitted the mandatory {commit} terminator (commit: yes)" ;;
  *) echo "FAIL: proof reports no commit terminator (the F-016 fault condition):"; printf '%s\n' "$out"; rc=1 ;;
esac
# And the verbatim \r submit rides inside the text command (not a separate enter key).
case "$out" in
  *'\r"'*) echo "ok  text command carries the trailing \\r submit" ;;
  *) echo "FAIL: no trailing \\r in the text command:"; printf '%s\n' "$out"; rc=1 ;;
esac

[ "$rc" -eq 0 ] && { echo "TRANSLATE-PROOF-INT OK"; exit 0; } || { echo "TRANSLATE-PROOF-INT FAIL"; exit 1; }


The file C:\Users\decid\Documents\projects\spt-claude-code\ci\idle-translate\translate-proof-int.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#!/bin/sh
# Integration proof: `spt adapter translate-proof` spawns + feeds the claude-spt
# [message-idle-translation-binary] (cc-spt-idle-translate) EXACTLY as the daemon does at idle
# delivery (init line then the --event envelope), reads back the emitted keystroke-command stream,
# and gates it. This is the EMIT half of REQ-DIST-IDLE-TRANSLATE — the cross-process author-time proof
# (ADR-0022, the EMIT-half mirror of digest-proof). It does NOT exercise the daemon's atomic PTY APPLY
# or controller buffering — that half stays the real-claude bringup int (deferred). [int->REQ-DIST-IDLE-TRANSLATE]
#
# Uses the v0.13.2 `--dir`/`--manifest` override (F-011 closed, W5) to proof the DEV binary straight
# from its build dir against the bare-file manifest: --dir resolves the binary (before PATH) exactly
# as the daemon does, --manifest pins the bare-file manifest. NO registry mutation — the prior form
# staged a disposable install dir and `adapter add`/`adapter remove claude-spt`, which soft-removed
# the REAL registered claude-spt on cleanup. Read-only now, so NO SPTC_ACCEPTANCE gate. Needs
# spt >= 0.13.2 (the --dir/--manifest options) + a built binary. Idempotent.
# Run: sh ci/idle-translate/translate-proof-int.sh   (exit 0 = pass).
#
# GREEN against the {commit}-terminated binary: emits the 4-command choreography ctrl+s · 50ms ·
# {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK / "commit: yes". The no-{commit}
# form FAULTs (the F-016 defect the proof's no-commit gate catches).
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
RELDIR="$ROOT/tools/cc-spt-idle-translate/target/release"
EVENT='<EVENT type="msg" from="ci">translate-proof int probe</EVENT>'

command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }

# --dir/--manifest landed in spt v0.13.2 — capability-detect rather than version-parse.
if ! spt adapter translate-proof --help 2>&1 | grep -q -- '--dir'; then
  echo "SKIP: spt 'adapter translate-proof' has no --dir/--manifest (needs v0.13.2). Binary itself: cargo tests green."
  exit 0
fi

BIN="$RELDIR/cc-spt-idle-translate"
[ -x "$BIN" ] || BIN="$RELDIR/cc-spt-idle-translate.exe"
[ -x "$BIN" ] || { echo "SKIP: binary not built (run sh ci/idle-translate/build.sh)"; exit 0; }

# Proof the dev binary in-place: --dir resolves the binary (before PATH) like the daemon, --manifest
# pins the bare-file gh_release manifest — no extracted install, no registry touch.
out=$(spt adapter translate-proof claude-spt --event "$EVENT" --manifest "$MANIFEST" --dir "$RELDIR" 2>&1)
rc=0
case "$out" in
  *TRANSLATE_PROOF_OK*) echo "ok  translate-proof: TRANSLATE_PROOF_OK" ;;
  *) echo "FAIL: translate-proof did not pass:"; printf '%s\n' "$out"; exit 1 ;;
esac
# The {commit} terminator is the F-016 fix — assert the proof saw it (no-commit would FAULT live).
case "$out" in
  *"commit: yes"*) echo "ok  emitted the mandatory {commit} terminator (commit: yes)" ;;
  *) echo "FAIL: proof reports no commit terminator (the F-016 fault condition):"; printf '%s\n' "$out"; rc=1 ;;
esac
# And the verbatim \r submit rides inside the text command (not a separate enter key).
case "$out" in
  *'\r"'*) echo "ok  text command carries the trailing \\r submit" ;;
  *) echo "FAIL: no trailing \\r in the text command:"; printf '%s\n' "$out"; rc=1 ;;
esac

[ "$rc" -eq 0 ] && { echo "TRANSLATE-PROOF-INT OK"; exit 0; } || { echo "TRANSLATE-PROOF-INT FAIL"; exit 1; }

-# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) + a

-# present spt >= 0.13.1 (translate-proof landed in v0.13.1 / counter 28) + a built binary. Idempotent.

-# Run: SPTC_ACCEPTANCE=1 sh ci/idle-translate/translate-proof-int.sh

+# Uses the v0.13.2 `--dir`/`--manifest` override (F-011 closed, W5) to proof the DEV binary straight

+# from its build dir against the bare-file manifest: --dir resolves the binary (before PATH) exactly

+# as the daemon does, --manifest pins the bare-file manifest. NO registry mutation — the prior form

+# staged a disposable install dir and `adapter add`/`adapter remove claude-spt`, which soft-removed

+# the REAL registered claude-spt on cleanup. Read-only now, so NO SPTC_ACCEPTANCE gate. Needs

+# spt >= 0.13.2 (the --dir/--manifest options) + a built binary. Idempotent.

+# Run: sh ci/idle-translate/translate-proof-int.sh   (exit 0 = pass).

-# GREEN since spt v0.13.1 (counter 28) against the {commit}-terminated binary: emits the 4-command

-# choreography ctrl+s · 50ms · {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK /

-# "commit: yes". The no-{commit} form FAULTs (the F-016 defect the proof's no-commit gate catches).

-# INSTALL-DIR NOTE: claude-spt declares [update] avenue="gh_release", so a bare-file `adapter add`

-# registers it GhReleaseManaged and the runtime resolver wants the EXTRACTED install (manifest.toml +

-# the binary co-located), not the dev source file. So this int stages a real install DIR — manifest.toml

-# (the renamed manifest) + strings/ + the built binary — exactly the shape `adapter add --release`

-# extracts, and points the adapter at it. (The sibling ci/digest/digest-proof-int.sh predates this

-# GhReleaseManaged behavior and uses the now-stale bare-file form.)

+# GREEN against the {commit}-terminated binary: emits the 4-command choreography ctrl+s · 50ms ·

+# {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK / "commit: yes". The no-{commit}

+# form FAULTs (the F-016 defect the proof's no-commit gate catches).

-[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }

-# translate-proof is a v0.13.1+ subcommand — announce the version gap rather than fail on an old binary.

-if ! spt adapter --help 2>&1 | grep -q 'translate-proof'; then

-  echo "SKIP: spt has no 'adapter translate-proof' (needs v0.13.1 / counter 28+). Binary itself: cargo tests green."

+# --dir/--manifest landed in spt v0.13.2 — capability-detect rather than version-parse.

+if ! spt adapter translate-proof --help 2>&1 | grep -q -- '--dir'; then

+  echo "SKIP: spt 'adapter translate-proof' has no --dir/--manifest (needs v0.13.2). Binary itself: cargo tests green."

-# Stage the install dir the resolver expects (manifest.toml + strings/ + the binary co-located).

-STAGE=$(mktemp -d "${TMPDIR:-/tmp}/cc-xlate-int.XXXXXX") || { echo "FAIL: mktemp"; exit 1; }

-trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true; rm -rf "$STAGE"' EXIT INT TERM

-cp "$MANIFEST" "$STAGE/manifest.toml" || { echo "FAIL: stage manifest"; exit 1; }

-cp -r "$STRINGS" "$STAGE/strings"     || { echo "FAIL: stage strings"; exit 1; }

-cp "$BIN" "$STAGE/"                    || { echo "FAIL: stage binary"; exit 1; }

-spt adapter add "$STAGE" >/dev/null 2>&1 || { echo "FAIL: spt adapter add (install dir)"; exit 1; }

-out=$(spt adapter translate-proof claude-spt --event "$EVENT" 2>&1)

+# Proof the dev binary in-place: --dir resolves the binary (before PATH) like the daemon, --manifest

+# pins the bare-file gh_release manifest — no extracted install, no registry touch.

+out=$(spt adapter translate-proof claude-spt --event "$EVENT" --manifest "$MANIFEST" --dir "$RELDIR" 2>&1)

#!/bin/sh
# Integration proof: `spt adapter translate-proof` spawns + feeds the claude-spt
# [message-idle-translation-binary] (cc-spt-idle-translate) EXACTLY as the daemon does at idle
# delivery (init line then the --event envelope), reads back the emitted keystroke-command stream,
# and gates it. This is the EMIT half of REQ-DIST-IDLE-TRANSLATE — the cross-process author-time proof
# (ADR-0022, the EMIT-half mirror of digest-proof). It does NOT exercise the daemon's atomic PTY APPLY
# or controller buffering — that half stays the real-claude bringup int (deferred). [int->REQ-DIST-IDLE-TRANSLATE]
#
# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) + a
# present spt >= 0.13.1 (translate-proof landed in v0.13.1 / counter 28) + a built binary. Idempotent.
# Run: SPTC_ACCEPTANCE=1 sh ci/idle-translate/translate-proof-int.sh
#
# GREEN since spt v0.13.1 (counter 28) against the {commit}-terminated binary: emits the 4-command
# choreography ctrl+s · 50ms · {text:"<envelope>\r"} · {commit:true}, exit 0 / TRANSLATE_PROOF_OK /
# "commit: yes". The no-{commit} form FAULTs (the F-016 defect the proof's no-commit gate catches).
#
# INSTALL-DIR NOTE: claude-spt declares [update] avenue="gh_release", so a bare-file `adapter add`
# registers it GhReleaseManaged and the runtime resolver wants the EXTRACTED install (manifest.toml +
# the binary co-located), not the dev source file. So this int stages a real install DIR — manifest.toml
# (the renamed manifest) + strings/ + the built binary — exactly the shape `adapter add --release`
# extracts, and points the adapter at it. (The sibling ci/digest/digest-proof-int.sh predates this
# GhReleaseManaged behavior and uses the now-stale bare-file form.)
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
STRINGS="$ROOT/adapter/strings"
RELDIR="$ROOT/tools/cc-spt-idle-translate/target/release"
EVENT='<EVENT type="msg" from="ci">translate-proof int probe</EVENT>'

[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }

# translate-proof is a v0.13.1+ subcommand — announce the version gap rather than fail on an old binary.
if ! spt adapter --help 2>&1 | grep -q 'translate-proof'; then
  echo "SKIP: spt has no 'adapter translate-proof' (needs v0.13.1 / counter 28+). Binary itself: cargo tests green."
  exit 0
fi

BIN="$RELDIR/cc-spt-idle-translate"
[ -x "$BIN" ] || BIN="$RELDIR/cc-spt-idle-translate.exe"
[ -x "$BIN" ] || { echo "SKIP: binary not built (run sh ci/idle-translate/build.sh)"; exit 0; }

# Stage the install dir the resolver expects (manifest.toml + strings/ + the binary co-located).
STAGE=$(mktemp -d "${TMPDIR:-/tmp}/cc-xlate-int.XXXXXX") || { echo "FAIL: mktemp"; exit 1; }
trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true; rm -rf "$STAGE"' EXIT INT TERM
cp "$MANIFEST" "$STAGE/manifest.toml" || { echo "FAIL: stage manifest"; exit 1; }
cp -r "$STRINGS" "$STAGE/strings"     || { echo "FAIL: stage strings"; exit 1; }
cp "$BIN" "$STAGE/"                    || { echo "FAIL: stage binary"; exit 1; }

spt adapter add "$STAGE" >/dev/null 2>&1 || { echo "FAIL: spt adapter add (install dir)"; exit 1; }

out=$(spt adapter translate-proof claude-spt --event "$EVENT" 2>&1)
rc=0
case "$out" in
  *TRANSLATE_PROOF_OK*) echo "ok  translate-proof: TRANSLATE_PROOF_OK" ;;
  *) echo "FAIL: translate-proof did not pass:"; printf '%s\n' "$out"; exit 1 ;;
esac
# The {commit} terminator is the F-016 fix — assert the proof saw it (no-commit would FAULT live).
case "$out" in
  *"commit: yes"*) echo "ok  emitted the mandatory {commit} terminator (commit: yes)" ;;
  *) echo "FAIL: proof reports no commit terminator (the F-016 fault condition):"; printf '%s\n' "$out"; rc=1 ;;
esac
# And the verbatim \r submit rides inside the text command (not a separate enter key).
case "$out" in
  *'\r"'*) echo "ok  text command carries the trailing \\r submit" ;;
  *) echo "FAIL: no trailing \\r in the text command:"; printf '%s\n' "$out"; rc=1 ;;
esac

[ "$rc" -eq 0 ] && { echo "TRANSLATE-PROOF-INT OK"; exit 0; } || { echo "TRANSLATE-PROOF-INT FAIL"; exit 1; }


Run the rewritten script (no env gate, no registry mutation).


Bash

cd "C:/Users/decid/Documents/projects/spt-claude-code" && sh ci/idle-translate/translate-proof-int.sh; echo "RC=$?"; echo "=== adapter untouched? ==="; spt adapter list 2>&1 | head -2

Run rewritten translate-proof-int and confirm adapter untouched


ok  translate-proof: TRANSLATE_PROOF_OK
ok  emitted the mandatory {commit} terminator (commit: yes)
ok  text command carries the trailing \r submit
TRANSLATE-PROOF-INT OK
RC=0
=== adapter untouched? ===
claude-spt: Harness Pointer active (from C:\Users\decid\AppData\Local\spt-core\adapters\_github\SaberMage-spt-claude-code)
  claude-spt:ccs (shipped)


Green, adapter untouched. The old script's header flagged the sibling `digest-proof-int.sh` as stale (same GhReleaseManaged staging issue). Same F-011 fix applies. Check it.


1	#!/bin/sh
2	# Integration proof: `spt adapter digest-proof` runs the claude-spt [digest] extractor and renders a
3	# non-empty digest from a real CC-shaped sample (the M10 acceptance criterion carried
4	# forward for spt-claude-code). [int->REQ-DIST-DIGEST-EXTRACTOR]
5	#
6	# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) and a
7	# present spt>=0.7 + a built extractor. Idempotent. Run: SPTC_ACCEPTANCE=1 sh ci/digest/digest-proof-int.sh
8	#
9	# GREEN since spt v0.7.2 (doyle's F-004 fix, release counter 14): `digest-proof --sample` now fills
10	# the runtime substitution keys ({id}+{session_id}, + an optional --session) matching the daemon, so
11	# the published-shape extractor command (`--session {session_id} --in {source}`) proofs instead of
12	# hard-failing {session_id}. Was F-004-blocked on <=0.7.1 (empty key map, cli.rs:5135) — the legacy
13	# SKIP branch below stays as a guard so an OLD binary announces the version gap rather than failing.
14	set -u
15	ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
16	MANIFEST="$ROOT/adapter/claude-spt.toml"
17	SAMPLE="$ROOT/ci/digest/sample.jsonl"
18	RELDIR="$ROOT/tools/claude-spt-digest/target/release"
19
20	[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }
21	command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
22	[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }
23
24	trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM
25	# Make the manifest's bare `claude-spt-digest` resolvable (the release dir holds the built binary).
26	PATH="$RELDIR:$PATH"; export PATH
27
28	spt adapter add "$MANIFEST" >/dev/null 2>&1 || { echo "FAIL: spt adapter add"; exit 1; }
29
30	out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" 2>&1)
31	case "$out" in
32	  *DIGEST_PROOF_OK*)
33	    echo "ok  digest-proof: DIGEST_PROOF_OK"
34	    printf '%s\n' "$out" | grep -E 'parsed|dropped' | sed 's/^/    /'
35	    echo "DIGEST-PROOF-INT OK"; exit 0 ;;
36	  *"no value for substitution key {session_id}"*)
37	    echo "SKIP: spt < v0.7.2 — digest-proof --sample doesn't fill {session_id} (pre-F-004-fix binary)."
38	    echo "      upgrade to spt >= 0.7.2 (release counter 14) to run this int. Extractor itself: cargo tests green."
39	    exit 0 ;;
40	  *)
41	    echo "FAIL: unexpected digest-proof output:"; printf '%s\n' "$out"; exit 1 ;;
42	esac
43

#!/bin/sh
# Integration proof: `spt adapter digest-proof` runs the claude-spt [digest] extractor and renders a
# non-empty digest from a real CC-shaped sample (the M10 acceptance criterion carried
# forward for spt-claude-code). [int->REQ-DIST-DIGEST-EXTRACTOR]
#
# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) and a
# present spt>=0.7 + a built extractor. Idempotent. Run: SPTC_ACCEPTANCE=1 sh ci/digest/digest-proof-int.sh
#
# GREEN since spt v0.7.2 (doyle's F-004 fix, release counter 14): `digest-proof --sample` now fills
# the runtime substitution keys ({id}+{session_id}, + an optional --session) matching the daemon, so
# the published-shape extractor command (`--session {session_id} --in {source}`) proofs instead of
# hard-failing {session_id}. Was F-004-blocked on <=0.7.1 (empty key map, cli.rs:5135) — the legacy
# SKIP branch below stays as a guard so an OLD binary announces the version gap rather than failing.
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
SAMPLE="$ROOT/ci/digest/sample.jsonl"
RELDIR="$ROOT/tools/claude-spt-digest/target/release"

[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }

trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM
# Make the manifest's bare `claude-spt-digest` resolvable (the release dir holds the built binary).
PATH="$RELDIR:$PATH"; export PATH

spt adapter add "$MANIFEST" >/dev/null 2>&1 || { echo "FAIL: spt adapter add"; exit 1; }

out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" 2>&1)
case "$out" in
  *DIGEST_PROOF_OK*)
    echo "ok  digest-proof: DIGEST_PROOF_OK"
    printf '%s\n' "$out" | grep -E 'parsed|dropped' | sed 's/^/    /'
    echo "DIGEST-PROOF-INT OK"; exit 0 ;;
  *"no value for substitution key {session_id}"*)
    echo "SKIP: spt < v0.7.2 — digest-proof --sample doesn't fill {session_id} (pre-F-004-fix binary)."
    echo "      upgrade to spt >= 0.7.2 (release counter 14) to run this int. Extractor itself: cargo tests green."
    exit 0 ;;
  *)
    echo "FAIL: unexpected digest-proof output:"; printf '%s\n' "$out"; exit 1 ;;
esac


Same stale bare-file pattern (`adapter add`/`remove` + PATH hack) — broken on 0.13.x (GhReleaseManaged → F-011). Same `--dir/--manifest` fix applies. Verify digest-proof supports it and test.


cd "C:/Users/decid/Documents/projects/spt-claude-code" && echo "=== digest-proof --dir? ==="; spt adapter digest-proof --help 2>&1 | grep -qE -- '--dir' && echo "HAS --dir" || echo "NO --dir"; echo "=== test override ==="; spt adapter digest-proof claude-spt --sample ci/digest/sample.jsonl --manifest adapter/claude-spt.toml --dir tools/claude-spt-digest/target/release 2>&1 | head -8; echo "RC=${PIPESTATUS[0]}"

Test digest-proof --dir/--manifest override


=== digest-proof --dir? ===
HAS --dir
=== test override ===
=== digest-proof: claude-spt ===
parsed 5 record(s), dropped 0

--- parsed records ---
  {"role":"input","text":"add a file at src/a.rs","ts":"2026-06-15T10:00:00.000Z"}
  {"role":"agent","text":"On it — writing src/a.rs now.","ts":"2026-06-15T10:00:01.000Z"}
  {"role":"tool","tool":{"arg":"src/a.rs","name":"Write"},"ts":"2026-06-15T10:00:01.000Z"}
  {"role":"tool","tool":{"arg":"cargo build","name":"Bash"},"ts":"2026-06-15T10:00:04.000Z"}
RC=0


digest-proof override works (parsed 5, dropped 0, RC=0). Rewrite it the same way.


Write

#!/bin/sh
# Integration proof: `spt adapter digest-proof` runs the claude-spt [digest] extractor and renders a
# non-empty digest from a real CC-shaped sample (the M10 acceptance criterion carried
# forward for spt-claude-code). [int->REQ-DIST-DIGEST-EXTRACTOR]
#
# Uses the v0.13.2 `--dir`/`--manifest` override (F-011 closed, W5) to proof the DEV extractor
# straight from its build dir against the bare-file manifest: --dir resolves the binary (before PATH)
# exactly as the daemon does, --manifest pins the bare-file manifest. NO registry mutation — the prior
# form `adapter add`/`adapter remove claude-spt` soft-removed the REAL registered claude-spt on
# cleanup, AND the bare-file add went stale on spt 0.13.x (a gh_release manifest registers
# GhReleaseManaged but unextracted -> F-011 manifest-not-present -> digest-proof failed). Read-only
# now, so NO SPTC_ACCEPTANCE gate. Needs spt >= 0.13.2 (the --dir/--manifest options) + a built
# extractor. Idempotent. Run: sh ci/digest/digest-proof-int.sh   (exit 0 = pass).
#
# GREEN since spt v0.7.2 (doyle's F-004 fix): digest-proof fills the runtime substitution keys
# ({id}+{session_id}, + optional --session) matching the daemon, so the published-shape extractor
# command (`--session {session_id} --in {source}`) proofs instead of hard-failing {session_id}.
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
SAMPLE="$ROOT/ci/digest/sample.jsonl"
RELDIR="$ROOT/tools/claude-spt-digest/target/release"

command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }

# --dir/--manifest landed in spt v0.13.2 — capability-detect rather than version-parse.
if ! spt adapter digest-proof --help 2>&1 | grep -q -- '--dir'; then
  echo "SKIP: spt 'adapter digest-proof' has no --dir/--manifest (needs v0.13.2). Extractor itself: cargo tests green."
  exit 0
fi

[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }

# Proof the dev extractor in-place: --dir resolves the binary (before PATH) like the daemon, --manifest
# pins the bare-file gh_release manifest — no extracted install, no registry touch.
out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" --manifest "$MANIFEST" --dir "$RELDIR" 2>&1)
case "$out" in
  *DIGEST_PROOF_OK*)
    echo "ok  digest-proof: DIGEST_PROOF_OK"
    printf '%s\n' "$out" | grep -E 'parsed|dropped' | sed 's/^/    /'
    echo "DIGEST-PROOF-INT OK"; exit 0 ;;
  *"no value for substitution key {session_id}"*)
    echo "SKIP: spt < v0.7.2 — digest-proof --sample doesn't fill {session_id} (pre-F-004-fix binary)."
    echo "      upgrade to spt >= 0.7.2 (release counter 14) to run this int. Extractor itself: cargo tests green."
    exit 0 ;;
  *)
    echo "FAIL: unexpected digest-proof output:"; printf '%s\n' "$out"; exit 1 ;;
esac


<owl_messages>
<owl_message from="doyle" priority="high">Layout answer + verdict. You read the surface right — this IS a published-docs gap (the layout's in spt-core's internal CONTEXT.md but NOT on docs-site/llms, which is all you have). I'm fixing the published docs; here's the authoritative layout (from the extract code + CONTEXT) so you're unblocked now:

MULTI-PLATFORM .spt LAYOUT — per-Rust-target-triple subdirs at the archive ROOT (your update-set.json guess was right; NOT <os>-<arch>, NOT a manifest table):

  adapter.spt  (tar, optionally gzip — same single asset)
  ├── manifest.toml                  ← SHARED, at root (NOT under a triple)
  ├── strings/                       ← SHARED, at root
  ├── x86_64-pc-windows-msvc/        ← this platform's binaries…
  │   └── bin/…                        …in the SAME relative tree a flat .spt has at root
  └── x86_64-unknown-linux-gnu/
      └── bin/…

RULES (mechanical, from extract_release_archive):
- Classification keys on TOP-LEVEL entry names: if ANY top-level dir name is a known triple → multi-platform; else flat (legacy, unchanged). So shared files MUST stay at root and per-platform binaries MUST go under a <triple>/.
- On install, spt-core places the shared-root entries + FLATTENS this node's <triple>/* into install_dir. So <triple>/bin/prog → install_dir/bin/prog; <triple>/prog → install_dir/prog. Q1 yes: bare-name <install_dir>/<program> resolution (REQ-INSTALL-11) is preserved — just mirror, under each <triple>/, the exact per-platform tree a flat archive puts at root.
- TO CONVERT your current flat (manifest.toml strings/ bin/…): keep manifest.toml + strings/ at root; move bin/ (+ any binaries) under BOTH x86_64-pc-windows-msvc/ and x86_64-unknown-linux-gnu/. One whole-archive Ed25519 signature over the fat tar (unchanged from single).

CONSTRAINTS:
- Only TWO triples are recognized in a fat archive today: x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu (KNOWN_TARGET_TRIPLES). FOOTGUN: a typo'd or unsupported triple (e.g. aarch64/macOS) is NOT recognized → it's treated as a shared ROOT entry and lands flat (silent mis-place, no error). For platforms beyond those two, keep the per-platform split (separate single-triple assets via --asset).
- A fat archive missing THIS node's triple → typed NoArtifactForPlatform (clear refuse, never silent).
- min_spt_core_version must be >= 0.13.2 on a fat archive (the extract is 0.13.2+; older spt-core can't read it).

Q2: documented-anywhere = only internal CONTEXT.md, NOT published. Confirmed gap — I'm adding it to docs-site/harness-contract (the install-on-demand packaging section) + llms now, so the next author has it. You don't need to log it; I've got the doc fix. Go repack v0.6.0 as one fat .spt + drop F-014. Ping if the repack hits anything unexpected.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


The file C:\Users\decid\Documents\projects\spt-claude-code\ci\digest\digest-proof-int.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#!/bin/sh
# Integration proof: `spt adapter digest-proof` runs the claude-spt [digest] extractor and renders a
# non-empty digest from a real CC-shaped sample (the M10 acceptance criterion carried
# forward for spt-claude-code). [int->REQ-DIST-DIGEST-EXTRACTOR]
#
# Uses the v0.13.2 `--dir`/`--manifest` override (F-011 closed, W5) to proof the DEV extractor
# straight from its build dir against the bare-file manifest: --dir resolves the binary (before PATH)
# exactly as the daemon does, --manifest pins the bare-file manifest. NO registry mutation — the prior
# form `adapter add`/`adapter remove claude-spt` soft-removed the REAL registered claude-spt on
# cleanup, AND the bare-file add went stale on spt 0.13.x (a gh_release manifest registers
# GhReleaseManaged but unextracted -> F-011 manifest-not-present -> digest-proof failed). Read-only
# now, so NO SPTC_ACCEPTANCE gate. Needs spt >= 0.13.2 (the --dir/--manifest options) + a built
# extractor. Idempotent. Run: sh ci/digest/digest-proof-int.sh   (exit 0 = pass).
#
# GREEN since spt v0.7.2 (doyle's F-004 fix): digest-proof fills the runtime substitution keys
# ({id}+{session_id}, + optional --session) matching the daemon, so the published-shape extractor
# command (`--session {session_id} --in {source}`) proofs instead of hard-failing {session_id}.
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
SAMPLE="$ROOT/ci/digest/sample.jsonl"
RELDIR="$ROOT/tools/claude-spt-digest/target/release"

command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }

# --dir/--manifest landed in spt v0.13.2 — capability-detect rather than version-parse.
if ! spt adapter digest-proof --help 2>&1 | grep -q -- '--dir'; then
  echo "SKIP: spt 'adapter digest-proof' has no --dir/--manifest (needs v0.13.2). Extractor itself: cargo tests green."
  exit 0
fi

[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }

# Proof the dev extractor in-place: --dir resolves the binary (before PATH) like the daemon, --manifest
# pins the bare-file gh_release manifest — no extracted install, no registry touch.
out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" --manifest "$MANIFEST" --dir "$RELDIR" 2>&1)
case "$out" in
  *DIGEST_PROOF_OK*)
    echo "ok  digest-proof: DIGEST_PROOF_OK"
    printf '%s\n' "$out" | grep -E 'parsed|dropped' | sed 's/^/    /'
    echo "DIGEST-PROOF-INT OK"; exit 0 ;;
  *"no value for substitution key {session_id}"*)
    echo "SKIP: spt < v0.7.2 — digest-proof --sample doesn't fill {session_id} (pre-F-004-fix binary)."
    echo "      upgrade to spt >= 0.7.2 (release counter 14) to run this int. Extractor itself: cargo tests green."
    exit 0 ;;
  *)
    echo "FAIL: unexpected digest-proof output:"; printf '%s\n' "$out"; exit 1 ;;
esac

-# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) and a

-# present spt>=0.7 + a built extractor. Idempotent. Run: SPTC_ACCEPTANCE=1 sh ci/digest/digest-proof-int.sh

+# Uses the v0.13.2 `--dir`/`--manifest` override (F-011 closed, W5) to proof the DEV extractor

+# straight from its build dir against the bare-file manifest: --dir resolves the binary (before PATH)

+# exactly as the daemon does, --manifest pins the bare-file manifest. NO registry mutation — the prior

+# form `adapter add`/`adapter remove claude-spt` soft-removed the REAL registered claude-spt on

+# cleanup, AND the bare-file add went stale on spt 0.13.x (a gh_release manifest registers

+# GhReleaseManaged but unextracted -> F-011 manifest-not-present -> digest-proof failed). Read-only

+# now, so NO SPTC_ACCEPTANCE gate. Needs spt >= 0.13.2 (the --dir/--manifest options) + a built

+# extractor. Idempotent. Run: sh ci/digest/digest-proof-int.sh   (exit 0 = pass).

-# GREEN since spt v0.7.2 (doyle's F-004 fix, release counter 14): `digest-proof --sample` now fills

-# the runtime substitution keys ({id}+{session_id}, + an optional --session) matching the daemon, so

-# the published-shape extractor command (`--session {session_id} --in {source}`) proofs instead of

-# hard-failing {session_id}. Was F-004-blocked on <=0.7.1 (empty key map, cli.rs:5135) — the legacy

-# SKIP branch below stays as a guard so an OLD binary announces the version gap rather than failing.

+# GREEN since spt v0.7.2 (doyle's F-004 fix): digest-proof fills the runtime substitution keys

+# ({id}+{session_id}, + optional --session) matching the daemon, so the published-shape extractor

+# command (`--session {session_id} --in {source}`) proofs instead of hard-failing {session_id}.

-[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }

-[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }

-trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM

-# Make the manifest's bare `claude-spt-digest` resolvable (the release dir holds the built binary).

+# --dir/--manifest landed in spt v0.13.2 — capability-detect rather than version-parse.

+if ! spt adapter digest-proof --help 2>&1 | grep -q -- '--dir'; then

+  echo "SKIP: spt 'adapter digest-proof' has no --dir/--manifest (needs v0.13.2). Extractor itself: cargo tests green."

-spt adapter add "$MANIFEST" >/dev/null 2>&1 || { echo "FAIL: spt adapter add"; exit 1; }

+[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }

-out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" 2>&1)

+# Proof the dev extractor in-place: --dir resolves the binary (before PATH) like the daemon, --manifest

+# pins the bare-file gh_release manifest — no extracted install, no registry touch.

+out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" --manifest "$MANIFEST" --dir "$RELDIR" 2>&1)

echo "ok  digest-proof: DIGEST_PROOF_OK"

#!/bin/sh
# Integration proof: `spt adapter digest-proof` runs the claude-spt [digest] extractor and renders a
# non-empty digest from a real CC-shaped sample (the M10 acceptance criterion carried
# forward for spt-claude-code). [int->REQ-DIST-DIGEST-EXTRACTOR]
#
# Gated behind SPTC_ACCEPTANCE=1 (mutates the node-local adapter registry: add + soft-remove) and a
# present spt>=0.7 + a built extractor. Idempotent. Run: SPTC_ACCEPTANCE=1 sh ci/digest/digest-proof-int.sh
#
# GREEN since spt v0.7.2 (doyle's F-004 fix, release counter 14): `digest-proof --sample` now fills
# the runtime substitution keys ({id}+{session_id}, + an optional --session) matching the daemon, so
# the published-shape extractor command (`--session {session_id} --in {source}`) proofs instead of
# hard-failing {session_id}. Was F-004-blocked on <=0.7.1 (empty key map, cli.rs:5135) — the legacy
# SKIP branch below stays as a guard so an OLD binary announces the version gap rather than failing.
set -u
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
MANIFEST="$ROOT/adapter/claude-spt.toml"
SAMPLE="$ROOT/ci/digest/sample.jsonl"
RELDIR="$ROOT/tools/claude-spt-digest/target/release"

[ "${SPTC_ACCEPTANCE:-0}" = "1" ] || { echo "SKIP: set SPTC_ACCEPTANCE=1 to run (mutates the adapter registry)"; exit 0; }
command -v spt >/dev/null 2>&1 || { echo "SKIP: no spt on PATH"; exit 0; }
[ -x "$RELDIR/claude-spt-digest" ] || [ -x "$RELDIR/claude-spt-digest.exe" ] || { echo "SKIP: extractor not built (run sh ci/digest/build.sh)"; exit 0; }

trap 'spt adapter remove claude-spt >/dev/null 2>&1 || true' EXIT INT TERM
# Make the manifest's bare `claude-spt-digest` resolvable (the release dir holds the built binary).
PATH="$RELDIR:$PATH"; export PATH

spt adapter add "$MANIFEST" >/dev/null 2>&1 || { echo "FAIL: spt adapter add"; exit 1; }

out=$(spt adapter digest-proof claude-spt --sample "$SAMPLE" 2>&1)
case "$out" in
  *DIGEST_PROOF_OK*)
    echo "ok  digest-proof: DIGEST_PROOF_OK"
    printf '%s\n' "$out" | grep -E 'parsed|dropped' | sed 's/^/    /'
    echo "DIGEST-PROOF-INT OK"; exit 0 ;;
  *"no value for substitution key {session_id}"*)
    echo "SKIP: spt < v0.7.2 — digest-proof --sample doesn't fill {session_id} (pre-F-004-fix binary)."
    echo "      upgrade to spt >= 0.7.2 (release counter 14) to run this int. Extractor itself: cargo tests green."
    exit 0 ;;
  *)
    echo "FAIL: unexpected digest-proof output:"; printf '%s\n' "$out"; exit 1 ;;
esac


doyle answered — **unblocked**. Acknowledging first (high-priority), then proceeding with the repack.

Key layout facts from doyle:
- Per-Rust-target-triple subdirs at archive root: `<triple>/…` mirroring the flat per-platform tree; `manifest.toml` + `strings/` stay **shared at root**.
- Only two recognized triples: `x86_64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`. Unrecognized triple → silently treated as a root entry (footgun).
- Install flattens this node's `<triple>/*` → install_dir; bare-name resolution preserved.
- **`min_spt_core_version` must be ≥ 0.13.2 on a fat archive** (mandatory).
- Confirmed published-docs gap; doyle is fixing docs, I don't log it.


Bash

$OWL send doyle <<'EOF'
perri — ACK, crystal clear, thank you. Locked in: per-triple subdirs at root (x86_64-pc-windows-msvc / x86_64-unknown-linux-gnu only), manifest.toml + strings/ SHARED at root, each <triple>/ mirrors the exact tree a flat .spt puts at root (mine = binaries at root, so <triple>/claude-spt-digest.exe etc., NOT bin/), install flattens this node's triple → bare-name resolution preserved, min_spt_core_version >= 0.13.2 mandatory, only those 2 triples recognized (unknown = silent root-flatten footgun — noted, we only ship x86_64 win+linux so fine). Not logging the doc gap — yours.

Repacking v0.6.0 as one fat adapter.spt + dropping the F-014 stopgap now. I'll DOGFOOD it: build the fat .spt, install it on this windows node, and confirm spt-core extracts+flattens the win triple correctly + the adapter resolves (manifest 0.6.0, translate-proof OK) BEFORE any release upload. Will ping if the repack hits anything unexpected. Also done (unblocked half): simplified BOTH translate-proof-int.sh AND digest-proof-int.sh to the --dir/--manifest override (dropped the stale bare-file adapter add/remove staging that soft-removed the real adapter) — green.
EOF
echo "SEND_EXIT=$?"

ACK doyle's layout answer


➜ SENT:doyle
SEND_EXIT=0


Now the repack. First assess: are both platforms' binaries available locally, and read the manifest version/floor lines + changelog to update.