The file C:\Users\decid\Documents\projects\spt-core\.github\workflows\ci.yml has been updated successfully. (file state is current in your context — no need to Read it back)

      # ---- M6-D2 installer-script rung ----
      # Runs the real install.sh / install.ps1 against a STAGED release
      # artifact (the just-built workspace binary laid out asset-name +
      # SHA256SUMS) on the matching runner, finishing with the installed
      # binary's `spt --version`. Env-gated: the normal sweep silent-skips
      # the same test when SPT_INSTALL_E2E is unset. Hermetic — temp HOME on
      # Linux, SPT_INSTALL_NO_PATH on Windows (never writes the runner's
      # real user PATH).
      - name: Installer E2E (staged-release rung)
        run: cargo test -p spt --test install_e2e -- --nocapture
        env:
          SPT_INSTALL_E2E: "1"

      # ---- D9-3 dormancy-budget measurement (ADR-0003 red-team #9) ----

name: ci

on:
  push:
    branches: [main, dev-freeform]
  pull_request:

# Cancel superseded runs on the same ref.
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ---- Rust build + test + lint (cross-platform: exercises both the
  # cfg(windows) OpenProcess path and the cfg(unix) libc path in proc.rs) ----
  #
  # CI-SELFHOST-PLAN.md: fully self-hosted (2026-06-03) — hfenduleam carries
  # the Windows leg, gravity-linux the Linux leg + traceability. Free minutes;
  # persistent cargo/target = warm builds (the multi-exe iroh link cost is
  # paid once per machine); hung tests debuggable on-box. Both boxes own
  # their rust toolchains (rustup-managed); no toolchain action, no
  # rust-cache (the persistent target dir IS the cache).
  test:
    strategy:
      fail-fast: false
      matrix:
        runner: [[self-hosted, Linux, gravity], [self-hosted, Windows, hfenduleam]]
    runs-on: ${{ matrix.runner }}
    # A wedged test must not block the single self-hosted runner slot (the
    # 2026-06-03 handoff.rs ConPTY stall burned 22 hosted minutes unbounded).
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v5
      - name: Build
        run: cargo build --workspace --all-targets
      - name: Test
        run: cargo test --workspace
      - name: Clippy (deny warnings)
        run: cargo clippy --workspace --all-targets -- -D warnings

      # ---- M5-D8a real-shell dogfood E2E (thin cross-repo hook) ----
      # The notify adapter lives in its own repo (SaberMage/spt-shell-notify,
      # public): the manifest + binary are the ONLY glue — zero spt-core
      # source integration. CI clones + builds it, then re-runs the
      # env-gated E2E leg against the real binary (the normal `cargo test`
      # sweep silent-skips the same test when SPT_NOTIFY_SHELL_BIN is unset).
      - name: Checkout spt-shell-notify (real-shell dogfood)
        uses: actions/checkout@v5
        with:
          repository: SaberMage/spt-shell-notify
          path: .adapter-notify
      - name: Build notify-shell
        run: cargo build --manifest-path .adapter-notify/Cargo.toml
      - name: Real-shell E2E (notify) — linux
        if: runner.os == 'Linux'
        run: cargo test -p spt --test notify_shell_e2e -- --nocapture
        env:
          SPT_NOTIFY_SHELL_BIN: ${{ github.workspace }}/.adapter-notify/target/debug/notify-shell
      - name: Real-shell E2E (notify) — windows
        if: runner.os == 'Windows'
        run: cargo test -p spt --test notify_shell_e2e -- --nocapture
        env:
          SPT_NOTIFY_SHELL_BIN: ${{ github.workspace }}\.adapter-notify\target\debug\notify-shell.exe

      # ---- D9-3 dormancy-budget measurement (ADR-0003 red-team #9) ----
      # Opt-in per push: runs ONLY when the head commit message carries
      # "[budget]" — both rig hosts measure in one tagged push, output lands
      # in the run log and is captured into docs/DORMANCY-BUDGET.md. The
      # harness itself is also env-gated (SPT_BUDGET=1), so an untagged CI
      # run silent-skips it inside the normal test sweep.
      - name: Dormancy budget (shell seats)
        if: contains(github.event.head_commit.message, '[budget]')
        run: cargo test -p spt-daemon --test budget --release -- --nocapture
        env:
          SPT_BUDGET: "1"
      # Two shells: the Windows runner has no bash on PATH (the first tagged
      # run failed on `shell: bash` there), so each OS keeps its native one.
      - name: Dormancy budget (real adapter seats, when installed) — linux
        if: contains(github.event.head_commit.message, '[budget]') && runner.os == 'Linux'
        shell: bash
        run: |
          if command -v claude >/dev/null; then
            SPT_BUDGET=1 SPT_BUDGET_N=3 SPT_BUDGET_PROGRAM=claude SPT_BUDGET_SETTLE_MS=15000 \
              cargo test -p spt-daemon --test budget --release -- --nocapture
          else
            echo "claude not installed on this runner — shell numbers only"
          fi
      - name: Dormancy budget (real adapter seats, when installed) — windows
        if: contains(github.event.head_commit.message, '[budget]') && runner.os == 'Windows'
        shell: powershell
        run: |
          if (Get-Command claude -ErrorAction SilentlyContinue) {
            $env:SPT_BUDGET = "1"
            $env:SPT_BUDGET_N = "3"
            $env:SPT_BUDGET_PROGRAM = "claude"
            $env:SPT_BUDGET_SETTLE_MS = "15000"
            cargo test -p spt-daemon --test budget --release -- --nocapture
          } else {
            # ASCII only: Windows PowerShell 5.1 reads the step's temp script
            # without a BOM as ANSI, so a multibyte dash corrupts the string
            # terminator (the first tagged-run failure mode here).
            Write-Output "claude not installed on this runner - shell numbers only"
          }

  # ---- D9-6 two-host rig ladder (M4-PLAN §D9b) ----
  # Opt-in per push: runs ONLY when the head commit message carries
  # "[twohost]". The two rig hosts ARE the self-hosted runners, so one tagged
  # push runs both roles; `needs: test` releases the two jobs together (each
  # runner has one slot — without it one role could sit queued behind the
  # other machine's test leg and starve the pairing window). The harness is
  # also env-gated (SPT_TWO_HOST=1), so untagged runs silent-skip it inside
  # the normal sweep. Not a standing CI job: the recurring cross-runner
  # choreography stays an M5 seam (docs/TWO-HOST-RUNBOOK.md is the proof
  # procedure).
  twohost-b:
    if: contains(github.event.head_commit.message, '[twohost]')
    needs: test
    runs-on: [self-hosted, Linux, gravity]
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v5
      # The M5 notif→toast rung renders through the REAL notify shell
      # (M5-D9a; same cross-repo hook as the test job's E2E).
      - name: Checkout spt-shell-notify (toast rung)
        uses: actions/checkout@v5
        with:
          repository: SaberMage/spt-shell-notify
          path: .adapter-notify
      - name: Build notify-shell
        run: cargo build --manifest-path .adapter-notify/Cargo.toml
      # The real-mode toast rung also needs the workspace `spt` binary beside
      # the test executable (the resident shell drives `spt api`); the first
      # D9a run proved the twohost job's cargo context does not share the
      # test job's artifacts — build it in-job (warm: seconds).
      - name: Build spt binary (toast rung)
        run: cargo build -p spt --bin spt
      - name: Two-host ladder — role B (seed-holder/server)
        run: cargo test -p spt-daemon --test twohost -- --nocapture
        env:
          SPT_TWO_HOST: "1"
          SPT_TWO_HOST_ROLE: "b"
          SPT_TWO_HOST_SECRET: "rig-twohost-ci"
          SPT_TWO_HOST_PEER_IP: "100.68.35.65" # hfenduleam (tailscale)
          SPT_TWO_HOST_WAIT_SECS: "900"
          SPT_TWO_HOST_NOTIFY_BIN: ${{ github.workspace }}/.adapter-notify/target/debug/notify-shell
  twohost-a:
    if: contains(github.event.head_commit.message, '[twohost]')
    needs: test
    runs-on: [self-hosted, Windows, hfenduleam]
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v5
      - name: Checkout spt-shell-notify (toast rung)
        uses: actions/checkout@v5
        with:
          repository: SaberMage/spt-shell-notify
          path: .adapter-notify
      - name: Build notify-shell
        run: cargo build --manifest-path .adapter-notify/Cargo.toml
      - name: Build spt binary (toast rung)
        run: cargo build -p spt --bin spt
      - name: Two-host ladder — role A (joiner/driver)
        run: cargo test -p spt-daemon --test twohost -- --nocapture
        env:
          SPT_TWO_HOST: "1"
          SPT_TWO_HOST_ROLE: "a"
          SPT_TWO_HOST_SECRET: "rig-twohost-ci"
          SPT_TWO_HOST_PEER_IP: "100.100.62.43" # gravity-linux (tailscale)
          SPT_TWO_HOST_WAIT_SECS: "900"
          SPT_TWO_HOST_NOTIFY_BIN: ${{ github.workspace }}\.adapter-notify\target\debug\notify-shell.exe

  # ---- Requirement-traceability hard gate (TRACEABILITY.md layer 1) ----
  traceability:
    runs-on: [self-hosted, Linux, gravity]
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v5
      - name: Install traceable-reqs
        # The self-hosted gravity runner carries a source-built binary in
        # /usr/local/bin (the release asset needs GLIBC 2.39; gravity's
        # Ubuntu 22.04 has 2.35). The download is the portability fallback
        # for any other runner.
        run: |
          if command -v traceable-reqs >/dev/null; then
            ln -sf "$(command -v traceable-reqs)" ./traceable-reqs
          else
            gh release download --repo BigscreenVR/traceable-reqs \
              --pattern '*linux-x86_64' --output traceable-reqs
            chmod +x traceable-reqs
          fi
        env:
          # Cross-org private repo: the default repo-scoped GITHUB_TOKEN cannot
          # read BigscreenVR/traceable-reqs. Supply a PAT with read access to it
          # as the TRACEABLE_REQS_TOKEN repo secret. See README/CI notes.
          GH_TOKEN: ${{ secrets.TRACEABLE_REQS_TOKEN }}
      - name: Coverage gate (hard — fails PR on uncovered activated req)
        run: ./traceable-reqs check --json
      - name: Quality audit (advisory)
        run: ./traceable-reqs lint || true

+      # ---- M6-D2 installer-script rung ----

+      # Runs the real install.sh / install.ps1 against a STAGED release

+      # artifact (the just-built workspace binary laid out asset-name +

+      # binary's `spt --version`. Env-gated: the normal sweep silent-skips

+      # the same test when SPT_INSTALL_E2E is unset. Hermetic — temp HOME on

+      # Linux, SPT_INSTALL_NO_PATH on Windows (never writes the runner's

+      - name: Installer E2E (staged-release rung)

+        run: cargo test -p spt --test install_e2e -- --nocapture


Now traceability: scan root + activations + doc tags + DEFERRED row.


Edit

[scan]
roots = ["src", "tests", "docs", "crates"]

[scan]
roots = ["src", "tests", "docs", "crates", "installer"]


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

[scan]
roots = ["src", "tests", "docs", "crates"]

[scan]
roots = ["src", "tests", "docs", "crates", "installer"]

# installed CLI at M0 (the binary isn't installed yet).

-roots = ["src", "tests", "docs", "crates"]

+roots = ["src", "tests", "docs", "crates", "installer"]

# Default policy applied at activation: doc + impl + unit. Networking/lifecycle


id = "REQ-INSTALL-1"
title = "Two install paths; signed one-line script; OS-service registration"
required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2 — script halves; OS-service stays deferred)

id = "REQ-INSTALL-1"
title = "Two install paths; signed one-line script; OS-service registration"
required_stages = ["doc", "impl", "int"]  # activated M6-D2: installer/install.sh + install.ps1 — the standalone one-line script half (latest-release lookup → platform asset fetch → sha256 verify → per-OS install root → user PATH → absolute-path print); the harness-bootstrapped half (path a) calls into the same script by design (CONTEXT §Installation). int = install_e2e.rs staged-release rung on both runners. OS-service registration EXCLUDED → docs/DEFERRED.md (daemon auto-start covers dev-stage); "signed" = sha256 at first fetch, full ed25519 is `spt update`'s job (grill decision 3)


id = "REQ-INSTALL-1"
title = "Two install paths; signed one-line script; OS-service registration"
required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2 — script halves; OS-service stays deferred)

id = "REQ-INSTALL-1"
title = "Two install paths; signed one-line script; OS-service registration"
required_stages = ["doc", "impl", "int"]  # activated M6-D2: installer/install.sh + install.ps1 — the standalone one-line script half (latest-release lookup → platform asset fetch → sha256 verify → per-OS install root → user PATH → absolute-path print); the harness-bootstrapped half (path a) calls into the same script by design (CONTEXT §Installation). int = install_e2e.rs staged-release rung on both runners. OS-service registration EXCLUDED → docs/DEFERRED.md (daemon auto-start covers dev-stage); "signed" = sha256 at first fetch, full ed25519 is `spt update`'s job (grill decision 3)

-required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2 — script halves; OS-service stays deferred)

+required_stages = ["doc", "impl", "int"]  # activated M6-D2: installer/install.sh + install.ps1 — the standalone one-line script half (latest-release lookup → platform asset fetch → sha256 verify → per-OS install root → user PATH → absolute-path print); the harness-bootstrapped half (path a) calls into the same script by design (CONTEXT §Installation). int = install_e2e.rs staged-release rung on both runners. OS-service registration EXCLUDED → docs/DEFERRED.md (daemon auto-start covers dev-stage); "signed" = sha256 at first fetch, full ed25519 is `spt update`'s job (grill decision 3)

title = "Marketplace-repackaging-friendly install"


id = "REQ-INSTALL-2"
title = "Marketplace-repackaging-friendly install"
required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2)

id = "REQ-INSTALL-2"
title = "Marketplace-repackaging-friendly install"
required_stages = ["doc"]  # doc activated M6-D2: the relocatable-binary + minimal non-OS-entangled install-logic stance documented (CONTEXT §Installation tag) — the scripts are that stance (single static binary, no OS service, user-scope PATH only); actual marketplace repackaging stays doc-stage per M6-PLAN §Out


id = "REQ-INSTALL-2"
title = "Marketplace-repackaging-friendly install"
required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2)

id = "REQ-INSTALL-2"
title = "Marketplace-repackaging-friendly install"
required_stages = ["doc"]  # doc activated M6-D2: the relocatable-binary + minimal non-OS-entangled install-logic stance documented (CONTEXT §Installation tag) — the scripts are that stance (single static binary, no OS service, user-scope PATH only); actual marketplace repackaging stays doc-stage per M6-PLAN §Out

-required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2)

+required_stages = ["doc"]  # doc activated M6-D2: the relocatable-binary + minimal non-OS-entangled install-logic stance documented (CONTEXT §Installation tag) — the scripts are that stance (single static binary, no OS service, user-scope PATH only); actual marketplace repackaging stays doc-stage per M6-PLAN §Out

title = "Idempotent + interactive-optional first run"


id = "REQ-INSTALL-3"
title = "Idempotent + interactive-optional first run"
required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2)

id = "REQ-INSTALL-3"
title = "Idempotent + interactive-optional first run"
required_stages = ["impl", "int"]  # activated M6-D2: idempotent re-run is construction — binary replaced atomically, PATH registration added at most once (marker-guarded .profile block / HKCU entry-set check); interactive-optional = the scripts never prompt at all (REQ-INSTALL-5's stronger non-interactive contract subsumes it; first-run identity gen + daemon start were already unattended). int = install_e2e re-run rung (second run green, single PATH block)


id = "REQ-INSTALL-3"
title = "Idempotent + interactive-optional first run"
required_stages = []  # rule 5: activates at M6 start (M6-PLAN.md D2)

id = "REQ-INSTALL-3"
title = "Idempotent + interactive-optional first run"
required_stages = ["impl", "int"]  # activated M6-D2: idempotent re-run is construction — binary replaced atomically, PATH registration added at most once (marker-guarded .profile block / HKCU entry-set check); interactive-optional = the scripts never prompt at all (REQ-INSTALL-5's stronger non-interactive contract subsumes it; first-run identity gen + daemon start were already unattended). int = install_e2e re-run rung (second run green, single PATH block)

+required_stages = ["impl", "int"]  # activated M6-D2: idempotent re-run is construction — binary replaced atomically, PATH registration added at most once (marker-guarded .profile block / HKCU entry-set check); interactive-optional = the scripts never prompt at all (REQ-INSTALL-5's stronger non-interactive contract subsumes it; first-run identity gen + daemon start were already unattended). int = install_e2e re-run rung (second run green, single PATH block)

title = "Adapter registration lifecycle: spt adapter add (--github, manifest-first, install-is-first-update) + soft-deregister remove + optional manifest uninstall template; node-local registered-adapter set self-update ripples over"


id = "REQ-INSTALL-5"
title = "Non-interactive install path: the canonical one-liner doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration"
required_stages = []  # activates at M6 start (D2)

id = "REQ-INSTALL-5"
title = "Non-interactive install path: the canonical one-liner doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration"
required_stages = ["impl", "int"]  # activated M6-D2: install.sh/install.ps1 non-interactive by construction (zero prompts; env knobs only — pipeable `curl|sh` / `irm|iex` cannot take args); SPT_INSTALL_ASSET_BASE is the CI/air-gap fetch override; sha256 verified against the release SHA256SUMS, mismatch refuses before placement. int = install_e2e on both runners incl. the tamper-refusal negative rung


id = "REQ-INSTALL-5"
title = "Non-interactive install path: the canonical one-liner doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration"
required_stages = []  # activates at M6 start (D2)

id = "REQ-INSTALL-5"
title = "Non-interactive install path: the canonical one-liner doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration"
required_stages = ["impl", "int"]  # activated M6-D2: install.sh/install.ps1 non-interactive by construction (zero prompts; env knobs only — pipeable `curl|sh` / `irm|iex` cannot take args); SPT_INSTALL_ASSET_BASE is the CI/air-gap fetch override; sha256 verified against the release SHA256SUMS, mismatch refuses before placement. int = install_e2e on both runners incl. the tamper-refusal negative rung

-required_stages = []  # activates at M6 start (D2)

+required_stages = ["impl", "int"]  # activated M6-D2: install.sh/install.ps1 non-interactive by construction (zero prompts; env knobs only — pipeable `curl|sh` / `irm|iex` cannot take args); SPT_INSTALL_ASSET_BASE is the CI/air-gap fetch override; sha256 verified against the release SHA256SUMS, mismatch refuses before placement. int = install_e2e on both runners incl. the tamper-refusal negative rung

title = "spt-releases publish-target repo: README public face, licensing split, Pages docs at the permanent lapse-proof canonical URL (ADR-0014)"


M6-D1: spt-releases repo + public face (README, licenses)

Create SaberMage/spt-releases private via gh; author README.md per grill decision 7; LICENSE-MIT + LICENSE-BINARY; Pages config deferred to D7 flip. Activate REQ-REL-1 doc stage.

Creating spt-releases repo + public face

M6-D2: installer scripts (install.sh / install.ps1)

Non-interactive: latest-release lookup → platform asset fetch → sha256 verify → per-OS install root → user PATH → print absolute path. Idempotent. CI rung against staged artifact + spt --version. Activate REQ-INSTALL-1/2/3/5.

M6-D3: manifest JSON Schema (schemars) + drift gate

schemars derives on spt-runtime manifest types; emit manifest.schema.json at stable site-root path + release asset; CI regenerate+diff gate. Activate REQ-DOCS-4 (partial), REQ-DOCS-5.

M6-D4: docs-site Tier 1 content (mdBook)

mdBook skeleton + Starlight-inspired theme; mental model; human quickstart; adapter quickstart (mock-adapter); harness-contract vertical complete; shell getting-started (spt-shell-notify); stubs for remaining verticals. Activate REQ-DOCS-1/2/3.

M6-D5: agent-export + generation plumbing

llms.txt + llms-full.txt; raw .md alongside HTML; CLI ref from clap; rustdoc → Pages; source-repo CI publish job → spt-releases Pages branch; drift gates. REQ-REL-1 impl, REQ-DOCS-4/5 complete.

M6-D6: release pipeline + signing ceremony

Workspace → 0.1.0; tag-triggered build matrix both runners; cross-repo release via fine-grained token; key ceremony per ADR-0015 (primary + recovery, embed both pubkeys in VerifyPolicy trusted set, seed backups); manual SignedRelease signing. Activate REQ-REL-2/3.

M6-D7: v0.1.0 ship + flip public + acceptance run

Tag v0.1.0; ceremony; publish release; flip spt-releases public; Pages live; acceptance proof per M6-PLAN (clean-machine install, quickstarts, llms.txt + schema fetch, staged v0.1.1 verify, recovery-key verify).


## Installation

spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter.

**Two install paths, (b) is the primitive:**

## Installation

<!-- [doc->REQ-INSTALL-1] the two-paths model + the one-line script half (v0.1 phasing below; OS-service leg = docs/DEFERRED.md) -->
<!-- [doc->REQ-INSTALL-2] the marketplace-repackaging stance: relocatable binary + minimal, non-OS-entangled install logic -->
spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter.

**Two install paths, (b) is the primitive:**


The file C:\Users\decid\Documents\projects\spt-core\CONTEXT.md has been updated successfully. (file state is current in your context — no need to Read it back)

## Installation

spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter.

**Two install paths, (b) is the primitive:**

## Installation

<!-- [doc->REQ-INSTALL-1] the two-paths model + the one-line script half (v0.1 phasing below; OS-service leg = docs/DEFERRED.md) -->
<!-- [doc->REQ-INSTALL-2] the marketplace-repackaging stance: relocatable binary + minimal, non-OS-entangled install logic -->
spt-core is per-machine and harness-independent, so it installs *before* and *independent of* any adapter.

**Two install paths, (b) is the primitive:**

+<!-- [doc->REQ-INSTALL-1] the two-paths model + the one-line script half (v0.1 phasing below; OS-service leg = docs/DEFERRED.md) -->

+<!-- [doc->REQ-INSTALL-2] the marketplace-repackaging stance: relocatable binary + minimal, non-OS-entangled install logic -->


| REQ-INSTALL-4 int evidence (`spt adapter add --github` against a real repo) | M5 closeout (D9b rule-5) | Needs a real clone target; impl/unit cover manifest-first validation + registration. The standalone `SaberMage/spt-shell-notify` repo now makes this trivially possible | First milestone that touches adapter lifecycle again — one E2E cloning the notify repo |

| REQ-INSTALL-4 int evidence (`spt adapter add --github` against a real repo) | M5 closeout (D9b rule-5) | Needs a real clone target; impl/unit cover manifest-first validation + registration. The standalone `SaberMage/spt-shell-notify` repo now makes this trivially possible | First milestone that touches adapter lifecycle again — one E2E cloning the notify repo |
| OS-service registration at install (REQ-INSTALL-1's third leg: systemd user service / Windows service or scheduled task for the always-on guarantee) | installer v0.1 (M6-D2, grill decision 3) | Daemon auto-start on any `spt` invocation covers dev-stage use; the minimal non-interactive script stays non-OS-entangled (also serves REQ-INSTALL-2's repackaging stance). Honest gap: a node is unreachable after reboot until something invokes `spt` | First always-on deployment need (headless/server node, or the GUI milestone's background expectations) |


The file C:\Users\decid\Documents\projects\spt-core\docs\DEFERRED.md has been updated successfully. (file state is current in your context — no need to Read it back)

| REQ-INSTALL-4 int evidence (`spt adapter add --github` against a real repo) | M5 closeout (D9b rule-5) | Needs a real clone target; impl/unit cover manifest-first validation + registration. The standalone `SaberMage/spt-shell-notify` repo now makes this trivially possible | First milestone that touches adapter lifecycle again — one E2E cloning the notify repo |
| OS-service registration at install (REQ-INSTALL-1's third leg: systemd user service / Windows service or scheduled task for the always-on guarantee) | installer v0.1 (M6-D2, grill decision 3) | Daemon auto-start on any `spt` invocation covers dev-stage use; the minimal non-interactive script stays non-OS-entangled (also serves REQ-INSTALL-2's repackaging stance). Honest gap: a node is unreachable after reboot until something invokes `spt` | First always-on deployment need (headless/server node, or the GUI milestone's background expectations) |

# Deferred Features

Intended features explicitly cut from v1 scope but committed for later. Not backlog noise — these are decided directions parked for sequencing reasons. Keep this list short and real.

| Feature | Cut from | Why deferred | Trigger to revisit |
|---|---|---|---|
| Scrollback on-disk spillover | terminal wrapper v1 | In-memory ring covers the common case; spillover adds a persistence/rotation story | First long-running session that overflows the ring usefully, or any "scroll back further than the buffer" user need |
| Sidecar adapter process (long-running, wire-protocol) | harness contract v1 | Manifest + `spt.exe` subcommand surface covers v1 harnesses; sidecar only earns its keep for streaming / in-memory cross-event state | A harness outgrows manifest+hooks (needs streaming or stateful coordination) |
| Manifest `include` / composition (option C) | manifest v1 | Single flat manifest per harness is enough until a harness wants to override only part of a section | First sub-project that needs to inherit spt-core defaults but override one section |
| Concrete Shell types (GameRobot, in-session-inject) — **OS-notification shell carved out: ships M5** as the dogfood proof (user decision 2026-06-04, M5-PLAN §Scope decisions) | Shell concept v1 | Model is locked; concrete surfaces are large, platform-specific, and not needed for core | First real use case for a driven surface (e.g. the 2D-world experiment) |
| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one MRA API (`spt-daemon::presence`). A future PresenceChannel's dispatch/bind/thread styles consume exactly this datum plus the broker PresenceLog seam (REQ-EP-4); nothing about the endpoint type needs a new gossip channel. | Once Shells exist and the subnet registry is stable |
| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |
| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant shape ships M5). <!-- [doc->REQ-INST-6] --> **M5-D5b note:** remote `spt suspend/wake <id@node>` shipped (the rest-op wire family, remote-drive trust class) — it *moves existing instances between rest states only*; the remote-**fork** arm (creating an instance on a node that has none) stays here, riding instantiate-anywhere's consent gate. A wake addressed to a node with no local instance refuses with `WAKE_NO_REACHABLE_INSTANCE` naming this deferral. | Revisit-trigger met (remote-drive shipped M4); held back by user decision — capability drops into the M5 consent framework when picked up |
| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |
| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |
| Concrete Shell adapters (e.g. GameRobot) + the shell binaries themselves | Shell concept v1 | The Shell *model*, manifest schema, perch layout, and `api` surface are now specified; concrete shells are large, platform-specific, and not needed for core | First real driven-surface use case |
| REQ-CONSENT int evidence (interactive escalation E2E) | M5 closeout (D9b rule-5) | The escalation prompt''s end-to-end leg needs a real harness session answering a real consent prompt — loopback-faking it would prove nothing; the grant store + gate + CLI are impl/unit-proven | The downstream rebuilt spt plugin (v1 acceptance) — its first gated spawn IS the int evidence |
| REQ-INSTALL-4 int evidence (`spt adapter add --github` against a real repo) | M5 closeout (D9b rule-5) | Needs a real clone target; impl/unit cover manifest-first validation + registration. The standalone `SaberMage/spt-shell-notify` repo now makes this trivially possible | First milestone that touches adapter lifecycle again — one E2E cloning the notify repo |

+| OS-service registration at install (REQ-INSTALL-1's third leg: systemd user service / Windows service or scheduled task for the always-on guarantee) | installer v0.1 (M6-D2, grill decision 3) | Daemon auto-start on any `spt` invocation covers dev-stage use; the minimal non-interactive script stays non-OS-entangled (also serves REQ-INSTALL-2's repackaging stance). Honest gap: a node is unreachable after reboot until something invokes `spt` | First always-on deployment need (headless/server node, or the GUI milestone's background expectations) |


PowerShell

traceable-reqs check > $null 2>&1; "exit=$LASTEXITCODE"; traceable-reqs check 2>&1 | Select-String "REQ-INSTALL-[1235]|requirements:"

Traceability check after D2 activations


Error: Exit code 1
exit=1

139 requirements: 135 complete, 4 incomplete, 5 findings
  [--] REQ-INSTALL-1  required: [doc, impl, int]  stages: -doc -impl -unit +int
  [--] REQ-INSTALL-2  required: [doc]  stages: -doc -impl -unit -int
  [--] REQ-INSTALL-3  required: [impl, int]  stages: -doc -impl -unit +int
  [--] REQ-INSTALL-5  required: [impl, int]  stages: -doc -impl -unit +int
  [must] missing_stage REQ-INSTALL-1 stage=doc — Requirement is missing documentation evidence
  [must] missing_stage REQ-INSTALL-1 stage=impl — Requirement is missing implementation evidence
  [must] missing_stage REQ-INSTALL-2 stage=doc — Requirement is missing documentation evidence
  [must] missing_stage REQ-INSTALL-3 stage=impl — Requirement is missing implementation evidence
  [must] missing_stage REQ-INSTALL-5 stage=impl — Requirement is missing implementation evidence


traceable-reqs check --help 2>&1; traceable-reqs --help 2>&1 | Select-Object -First 30

traceable-reqs CLI help


Usage: traceable-reqs.exe check [OPTIONS]

Options:
      --json
      --type <TYPE>
      --fail-on <LEVEL>  Lowest priority that fails check. Findings below this level still print but do not change the exit code. Default: must [default: must]
  -h, --help             Print help
Trace requirements via [stage->REQ-ID] tags

Usage: traceable-reqs.exe <COMMAND>

Commands:
  list
  trace
  check
  lint
  review
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version


16:6. **Scan roots stay honest.** `[scan].roots` includes every evidence location (`src`, `tests`, `docs`, and later `.github/workflows`, `scripts`). Audit roots whenever a new evidence dir appears — a missing root makes evidence *silently* vanish from the trace.
25:angle-bracket the stage (`[<impl>->...]`) only so the scanner treats them as


traceable-reqs trace REQ-INSTALL-1 2>&1 | Select-Object -First 15; traceable-reqs trace REQ-INSTALL-5 2>&1 | Select-Object -First 10

Trace where evidence was found


REQ-INSTALL-1 — Two install paths; signed one-line script; OS-service registration
  required stages: [doc, impl, int]
  [--] doc
  [--] impl
  [--] unit (not required)
  [OK] int
        crates/spt/tests/install_e2e.rs:16
  findings:
    [must] missing_stage REQ-INSTALL-1 stage=doc — Requirement is missing documentation evidence
    [must] missing_stage REQ-INSTALL-1 stage=impl — Requirement is missing implementation evidence
REQ-INSTALL-5 — Non-interactive install path: the canonical one-liner doubles as every adapter's pack-in on-demand install (no second mechanism); sha256-verified fetch; user-PATH registration
  required stages: [impl, int]
  [--] doc (not required)
  [--] impl
  [--] unit (not required)
  [OK] int
        crates/spt/tests/install_e2e.rs:17
  findings:
    [must] missing_stage REQ-INSTALL-5 stage=impl — Requirement is missing implementation evidence


"# probe [impl->REQ-INSTALL-5]" | Set-Content C:\Users\decid\Documents\projects\spt-core\installer\probe.md; traceable-reqs trace REQ-INSTALL-5 2>&1 | Select-String "impl|probe|install"; Remove-Item C:\Users\decid\Documents\projects\spt-core\installer\probe.md

Probe whether installer/ root + .md extension is scanned


REQ-INSTALL-5 — Non-interactive install path: the canonical one-liner doubles as every adapter's pack-in on-demand install (no second
mechanism); sha256-verified fetch; user-PATH registration
  required stages: [impl, int]
  [OK] impl
        installer/probe.md:1
        crates/spt/tests/install_e2e.rs:17


1	# Traceability development contract
2
3	> How spt-core uses [`traceable-reqs`](https://github.com/BigscreenVR/traceable-reqs) to keep every requirement traced from doc → impl → test. The manifest is `traceable-reqs.toml` (seeded from PRD `R-*` + KNOWN-HAZARDS invariants). This contract makes the trace pay off instead of rotting.
4
5	## Why
6
7	The whole point of the rebuild is to *not* re-introduce the sister project's bugs and to ship the PRD's scope verifiably. A requirement that isn't traced to real evidence (a doc, an implementation, a test) is a requirement you're *hoping* is met. This contract converts that hope into a build that fails when evidence is missing.
8
9	## The contract (rules)
10
11	1. **The manifest is the authoritative requirement registry.** Every PRD `R-*` requirement and every KNOWN-HAZARDS invariant exists in `traceable-reqs.toml` as a `REQ-*` id. No work without a REQ; no REQ without intent to satisfy. The PRD holds the prose; the manifest holds the id + `required_stages`.
12	2. **Tag in the same change as the evidence.** When you write the function / test / doc-section that satisfies a stage, add its `[<stage>-><REQ-ID>]` tag in *that same commit*. Never "tag later" — this single rule is what stops the trace from drifting.
13	3. **Evidence-proximate tags, not file-tops.** A tag sits on or immediately above the real function, test, doc section, config entry, or workflow step that proves the stage. One tag = one piece of real evidence. Tags at file tops to satisfy coverage are noise and usually wrong.
14	4. **KNOWN-HAZARDS are first-class requirements.** Each invariant is a `REQ-HAZARD-*` requiring `unit` (and `int` where cross-process/cross-node). A hazard cannot be "covered" without a test tagging it — the anti-regression promise becomes mechanical (PRD success criterion #6).
15	5. **Activation, not premature failure.** Every requirement starts `required_stages = []` (inactive) so `check` stays green pre-code. A **milestone (M0–M5) activates** the requirements it delivers by setting their real `required_stages`. Deferred items (`docs/DEFERRED.md`) stay `[]` until promoted.
16	6. **Scan roots stay honest.** `[scan].roots` includes every evidence location (`src`, `tests`, `docs`, and later `.github/workflows`, `scripts`). Audit roots whenever a new evidence dir appears — a missing root makes evidence *silently* vanish from the trace.
17
18	### Stages
19
20	`doc` (prose/design/rustdoc) · `impl` (production code) · `unit` (unit test) · `int` (integration / e2e / cross-node). Default activation policy: `["doc","impl","unit"]`; networking/lifecycle/cross-node reqs add `int`.
21
22	### Tag examples
23
24	Real tags are `[<stage>->REQ-ID]` with a bare stage word. The examples below
25	angle-bracket the stage (`[<impl>->...]`) only so the scanner treats them as
26	illustrations, not live evidence — write a bare stage word in real code.
27
28	```rust
29	// [<impl>->REQ-PAIR-1]
30	fn spake2_pair(...) { ... }
31	```
32	```rust
33	// [<unit>->REQ-HAZARD-ENVELOPE-DECODE-ORDER]
34	#[test] fn decodes_amp_last() { ... }
35	```
36	```markdown
37	<!-- [<doc>->REQ-NET-2] -->
38	## Relay dependency
39	```
40
41	## Enforcement (four layers — defense in depth)
42
43	1. **CI gate (hard backstop).** `traceable-reqs check --json` runs on every PR/push; exit-1 (`missing_stage` / `undeclared_id` / `parse_error` / `manifest_error`) **fails the build**. A PR can't merge if it leaves an *activated* req's required stage uncovered. Runs alongside the DOCS-STRATEGY CI gates.
44	2. **Milestone activation gate.** Starting a milestone flips its reqs from `[]` to the real policy; the milestone **is not done until `check` is green for its reqs**. Coverage grows *with* the project instead of failing on day one.
45	3. **Agent-facing rule.** The repo `CLAUDE.md` instructs every dev-agent: tag `[<stage>-><REQ-ID>]` in the same change as the evidence, and run `traceable-reqs check` before declaring done. Since spt-core is built *with* agents, the contract must be machine-followable — agent discipline + CI as the net.
46	4. **Quality audit (anti-box-ticking).** `check` proves a tag *exists*; `traceable-reqs review` + `lint` audit whether tags sit near *real* evidence and whether titles are meaningful. Run advisory in CI (or periodically). Presence = hard gate; quality = audited.
47
48	Optional fast-feedback: a pre-push git hook running `traceable-reqs check` locally.
49
50	## CI snippet (wire at M0, once there is a Rust project + CI)
51
52	```yaml
53	# .github/workflows/traceability.yml
54	name: traceability
55	on: [push, pull_request]
56	jobs:
57	  check:
58	    runs-on: ubuntu-latest
59	    steps:
60	      - uses: actions/checkout@v4
61	      - name: Install traceable-reqs
62	        run: gh release download --repo BigscreenVR/traceable-reqs --pattern '*linux-x86_64' --output traceable-reqs && chmod +x traceable-reqs
63	        env: { GH_TOKEN: ${{ github.token }} }
64	      - name: Coverage gate (hard)
65	        run: ./traceable-reqs check --json
66	      - name: Quality audit (advisory)
67	        run: ./traceable-reqs lint || true
68	```
69
70	## Lifecycle
71
72	- **Now (planning):** manifest seeded, all reqs inactive. `check` is green (nothing required yet).
73	- **M0:** install the CLI, validate the seed against it, wire the CI workflow, **activate M0 reqs** (`REQ-ARCH-*`, `REQ-HAZARD-*` for the commodity layer), tag as code lands.
74	- **M1–M5:** each milestone activates + covers its requirements before it's called done.
75

# Traceability development contract

> How spt-core uses [`traceable-reqs`](https://github.com/BigscreenVR/traceable-reqs) to keep every requirement traced from doc → impl → test. The manifest is `traceable-reqs.toml` (seeded from PRD `R-*` + KNOWN-HAZARDS invariants). This contract makes the trace pay off instead of rotting.

## Why

The whole point of the rebuild is to *not* re-introduce the sister project's bugs and to ship the PRD's scope verifiably. A requirement that isn't traced to real evidence (a doc, an implementation, a test) is a requirement you're *hoping* is met. This contract converts that hope into a build that fails when evidence is missing.

## The contract (rules)

1. **The manifest is the authoritative requirement registry.** Every PRD `R-*` requirement and every KNOWN-HAZARDS invariant exists in `traceable-reqs.toml` as a `REQ-*` id. No work without a REQ; no REQ without intent to satisfy. The PRD holds the prose; the manifest holds the id + `required_stages`.
2. **Tag in the same change as the evidence.** When you write the function / test / doc-section that satisfies a stage, add its `[<stage>-><REQ-ID>]` tag in *that same commit*. Never "tag later" — this single rule is what stops the trace from drifting.
3. **Evidence-proximate tags, not file-tops.** A tag sits on or immediately above the real function, test, doc section, config entry, or workflow step that proves the stage. One tag = one piece of real evidence. Tags at file tops to satisfy coverage are noise and usually wrong.
4. **KNOWN-HAZARDS are first-class requirements.** Each invariant is a `REQ-HAZARD-*` requiring `unit` (and `int` where cross-process/cross-node). A hazard cannot be "covered" without a test tagging it — the anti-regression promise becomes mechanical (PRD success criterion #6).
5. **Activation, not premature failure.** Every requirement starts `required_stages = []` (inactive) so `check` stays green pre-code. A **milestone (M0–M5) activates** the requirements it delivers by setting their real `required_stages`. Deferred items (`docs/DEFERRED.md`) stay `[]` until promoted.
6. **Scan roots stay honest.** `[scan].roots` includes every evidence location (`src`, `tests`, `docs`, and later `.github/workflows`, `scripts`). Audit roots whenever a new evidence dir appears — a missing root makes evidence *silently* vanish from the trace.

### Stages

`doc` (prose/design/rustdoc) · `impl` (production code) · `unit` (unit test) · `int` (integration / e2e / cross-node). Default activation policy: `["doc","impl","unit"]`; networking/lifecycle/cross-node reqs add `int`.

### Tag examples

Real tags are `[<stage>->REQ-ID]` with a bare stage word. The examples below
angle-bracket the stage (`[<impl>->...]`) only so the scanner treats them as
illustrations, not live evidence — write a bare stage word in real code.

```rust
// [<impl>->REQ-PAIR-1]
fn spake2_pair(...) { ... }
```
```rust
// [<unit>->REQ-HAZARD-ENVELOPE-DECODE-ORDER]
#[test] fn decodes_amp_last() { ... }
```
```markdown
<!-- [<doc>->REQ-NET-2] -->
## Relay dependency
```

## Enforcement (four layers — defense in depth)

1. **CI gate (hard backstop).** `traceable-reqs check --json` runs on every PR/push; exit-1 (`missing_stage` / `undeclared_id` / `parse_error` / `manifest_error`) **fails the build**. A PR can't merge if it leaves an *activated* req's required stage uncovered. Runs alongside the DOCS-STRATEGY CI gates.
2. **Milestone activation gate.** Starting a milestone flips its reqs from `[]` to the real policy; the milestone **is not done until `check` is green for its reqs**. Coverage grows *with* the project instead of failing on day one.
3. **Agent-facing rule.** The repo `CLAUDE.md` instructs every dev-agent: tag `[<stage>-><REQ-ID>]` in the same change as the evidence, and run `traceable-reqs check` before declaring done. Since spt-core is built *with* agents, the contract must be machine-followable — agent discipline + CI as the net.
4. **Quality audit (anti-box-ticking).** `check` proves a tag *exists*; `traceable-reqs review` + `lint` audit whether tags sit near *real* evidence and whether titles are meaningful. Run advisory in CI (or periodically). Presence = hard gate; quality = audited.

Optional fast-feedback: a pre-push git hook running `traceable-reqs check` locally.

## CI snippet (wire at M0, once there is a Rust project + CI)

```yaml
# .github/workflows/traceability.yml
name: traceability
on: [push, pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install traceable-reqs
        run: gh release download --repo BigscreenVR/traceable-reqs --pattern '*linux-x86_64' --output traceable-reqs && chmod +x traceable-reqs
        env: { GH_TOKEN: ${{ github.token }} }
      - name: Coverage gate (hard)
        run: ./traceable-reqs check --json
      - name: Quality audit (advisory)
        run: ./traceable-reqs lint || true
```

## Lifecycle

- **Now (planning):** manifest seeded, all reqs inactive. `check` is green (nothing required yet).
- **M0:** install the CLI, validate the seed against it, wire the CI workflow, **activate M0 reqs** (`REQ-ARCH-*`, `REQ-HAZARD-*` for the commodity layer), tag as code lands.
- **M1–M5:** each milestone activates + covers its requirements before it's called done.


traceable-reqs --version; gh release view --repo BigscreenVR/traceable-reqs --json tagName,publishedAt -q '{tag: .tagName, at: .publishedAt}' 2>&1

Check installed vs latest traceable-reqs


traceable-reqs 0.1.1
{"at":"2026-06-02T11:00:16Z","tag":"v0.1.1"}


gh repo view BigscreenVR/traceable-reqs --json name,visibility,defaultBranchRef -q '{name:.name,vis:.visibility,branch:.defaultBranchRef.name}'; gh api repos/BigscreenVR/traceable-reqs/contents/src -q '.[].name' 2>&1

Inspect traceable-reqs repo layout


Error: Exit code 1
{"branch":"main","name":"traceable-reqs","vis":"PRIVATE"}
{"message":"Not Found","documentation_url":"https://docs.github.com/rest/repos/contents#get-repository-content","status":"404"}gh: Not Found (HTTP 404)


gh api repos/BigscreenVR/traceable-reqs/contents -q '.[].name' 2>&1; gh api "repos/BigscreenVR/traceable-reqs/git/trees/main?recursive=1" -q '.tree[].path' 2>&1 | Select-Object -First 40

List traceable-reqs repo tree


.github
.gitignore
AGENTS.md
CLAUDE.md
README.md
SKILL.md
SPEC.md
example
rust
.github
.github/workflows
.github/workflows/ci.yml
.github/workflows/release.yml
.gitignore
AGENTS.md
CLAUDE.md
README.md
SKILL.md
SPEC.md
example
example/README.md
example/docs
example/docs/audit.md
example/docs/login.md
example/docs/logout.md
example/expected.json
example/src
example/src/audit.cpp
example/src/auth.rs
example/src/typos.py
example/tests
example/tests/auth_test.py
example/tests/login_e2e.cpp
example/traceable-reqs.toml
rust
rust/Cargo.lock
rust/Cargo.toml
rust/src
rust/src/cli.rs
rust/src/git_diff.rs
rust/src/lib.rs
rust/src/lint.rs
rust/src/main.rs
rust/src/manifest.rs
rust/src/output.rs
rust/src/review.rs
rust/src/scan.rs
rust/src/trace.rs
rust/tests


gh api repos/BigscreenVR/traceable-reqs/contents/rust/src/scan.rs -q '.content' | %{ [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($_)) } | Select-String -Pattern "ext|EXTENSION|comment" -Context 2 | Select-Object -First 20

Inspect scan.rs extension handling


> //! File scanning and tag extraction.
//!
//!
   Walks the configured scan roots and recogniz
  es the bracketed stage/req-id
//! tokens defi
  ned by the spec. For Rust, C/C++, and Python
> the scanner only
//! considers comment region
> s (non-comment source text is ignored); Markd
  own
//! is scanned on every physical line. Ja
  vaScript/TypeScript use the same slash
//! co
  RSE]
//! [doc->REQ-TAG-GRAMMAR-REJECT]
//! [d
  oc->REQ-MARKDOWN-TAG-CANDIDATE]
//! [doc->REQ
> -COMMENT-SCOPE]
//! [doc->REQ-PY-DOCSTRING-SC
> AN]
//! [doc->REQ-BLOCK-COMMENT]

use std::co
  llections::{BTreeSet, HashSet};
use std::path
  ::{Path, PathBuf};
use walkdir::WalkDir;

use
  n(|n| n.to_str()) {
        if name == "CMake
  Lists.txt" {
            return Some(Lang::Cm
> ake);
        }
    }
    let ext = path.exte
  nsion()?.to_str()?.to_ascii_lowercase();

> Some(match ext.as_str() {
        "rs" => Lan
  g::Rust,
        "c" | "cc" | "cpp" | "cxx" |
   "c++" | "h" | "hh" | "hpp" | "hxx" | "h++" =
  lative(repo_root, entry.path());

  match std::fs::read_to_string(entry.path()) {
>
                Ok(text) => scan_text(&text,
   lang, &rel, allowed_stages, &mut output),

                Err(err) => output.scan_errors.
  o_string_lossy().into_owned())
        .colle
  ct::<Vec<_>>()
        .join("/")
}

fn scan_
> text(
    text: &str,
    lang: Lang,
    rel
  _path: &str,
    allowed_stages: &HashSet<Str
  ing>,
    output: &mut ScanOutput,
) {
    le
  t mut in_block = false;
    let mut python_tr
  iple: Option<&'static str> = None;
    for (i
> dx, line) in text.split('\n').enumerate() {

         let line = line.strip_suffix('\r').unw
  rap_or(line);
        let line_no = idx + 1;

          let regions = if lang == Lang::Python
   {
            // [impl->REQ-PY-DOCSTRING-SCA
> N]
            python_comment_regions(line, &
  mut python_triple)
        } else {

>    comment_regions(line, lang, &mut in_block)

        };
        if regions.is_empty() {

             continue;
        }
        let mu
                      }
                    }

                 }
            }
        }

> }
}

// [impl->REQ-COMMENT-SCOPE]
fn comment_
  regions<'a>(line: &'a str, lang: Lang, in_blo
  ck: &mut bool) -> Vec<&'a str> {
    match la
  dx) => vec![&line[idx..]],
            None =
  > Vec::new(),
        },
        Lang::Rust |
>  Lang::Cpp | Lang::JsTs => rust_cpp_comment_r
  egions(line, in_block),
    }
}

// [impl->RE
> Q-COMMENT-SCOPE]
// [impl->REQ-PY-DOCSTRING-S
> CAN]
fn python_comment_regions<'a>(line: &'a
  str, in_triple: &mut Option<&'static str>) ->
   Vec<&'a str> {
    let mut regions = Vec::ne
            break;
                }

    }
            continue;
        }

> let next_hash = line[pos..].find('#').map(|of
> f| (pos + off, "#"));
        let next_double
   = line[pos..].find("\"\"\"").map(|off| (pos
> + off, "\"\"\""));
        let next_single =
  line[pos..].find("'''").map(|off| (pos + off,
>  "'''"));
        let next = [next_hash, next


(Get-Command traceable-reqs).Source; ssh -o ConnectTimeout=5 -o BatchMode=yes decide@100.100.62.43 "hostname; which traceable-reqs; traceable-reqs --version" 2>&1

Locate local binary + probe SSH to gravity


Error: Exit code 255
C:\Users\decid\AppData\Local\Programs\traceable-reqs\traceable-reqs.exe
decide@100.100.62.43: Permission denied (publickey,password).


Get-Content $env:USERPROFILE\.ssh\config -ErrorAction SilentlyContinue; Get-ChildItem $env:USERPROFILE\.ssh 2>$null | Select-Object Name

Check SSH config for gravity host entry


Name
----
id_ed25519
id_ed25519.pub
known_hosts
known_hosts.old


Select-String -Path $env:USERPROFILE\.ssh\known_hosts -Pattern "100.100.62.43|gravity" | Select-Object -First 5; ssh -o ConnectTimeout=5 -o BatchMode=yes decidel@100.100.62.43 hostname 2>&1; ssh -o ConnectTimeout=5 -o BatchMode=yes brandon@100.100.62.43 hostname 2>&1

Probe gravity SSH usernames


Exit code 255
C:\Users\decid\.ssh\known_hosts:1:gravity-runner ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMVRpgM+/avN0UHvouSiE9r5qs3LQxDdnnyllFlMFKg
C:\Users\decid\.ssh\known_hosts:2:gravity-runner ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDD2h7oXxKhZ9kqrPzeKI8SzdVBC87iVjhog2ZekrZwsoP4CxvRG
sJI5qi4BWrzRsNgcvMhQFZi/GNueVq5wVZAiy+EhNg3ngXKtXDKTzOYIJJuTC9rsIMhVA3iGmtX6Q1X+zpIc2r9lr5nZzw/2dUoaEVdOJO2sD7HhxzAzGakUgjNADGnlxY1AvIMxV5
iQCGzbYNl8y61wJxquRpddyxqFt9EmiE7p1QvWjVWAfUi6f/R4YUtf64QcxpWzBo5KBvdBg2pRNdhF3zO91hmD80XjLZN3M2nyU+weyDVojFR389h5F3BblfHNqwUK58CQ4iH1e/jr
7XT93Qfry2GqldR5i6asNHuFp0P4iOO53Ym1ZFdha9muTa0Vsewpdi3h97AepFuPI64yjaD/RC3uNSwBpQHwGY3HRtV9P0asWJeNcVAaispCCKTOESvE7+AyorXdKdBh2yaHf3F4bF
c7RUSSDcWGBaBNrehFYu7c6kNqNj5Y061XEH7YviJz6ON2NM=
C:\Users\decid\.ssh\known_hosts:3:gravity-runner ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKaghWATovO8qN5A2
ZtYH+zzv035j/lZUfTZzt0SsfHcIcgeG53rSUTi/9Mnw7gAnF84T3DjarOGIOXnQRCZnQQ=
C:\Users\decid\.ssh\known_hosts:9:gravity-linux ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOddjgrprkl6b8PiWGSm05PmRGQVgsA2NzFXmAgYPa73
C:\Users\decid\.ssh\known_hosts:10:gravity-linux ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDhXwK3+n5sBaV2d2EFkyySl6QJjpvTN5k5n+Nre2xIT4yJdP6Zj
XYSZh5Iqt3YtfYOYo37ylYVvaGZHi118fAtMqOZlZw1i/8nu3fWM4QN5A+P6oMIm1MTfaLwyA/HapHp5EBSH4Ug3o47MZ3XoNN0xqOEUdLebyNsYXBvJ3UP12xhyd48r/1YjGUu4Uk
IOUwTIDEvI3kw2rAZybt9t/YUWfFF4TJWseD7pI1z7GVO60DgU+9xK5ExSYyMaxy0U1BH5YCFBD/t/VXgacIjUfZljj35e5p7CB3gVg7869GA2HOiXgN7J1bYuJDIk+nNkOCz84gSq
uEjeqUAM4M7qrTSs9sSTnOcowb+WfS5EPWeeVpCn9667jELDoGIPnCAQNBDLlXUvJaXeXajl0GonxAsmUSHgusrJzhi4FGBY0JFG1QqyPj2pbVjG1U8h0RITjJhB1QRHp+535G4o9f
4FfMNhsI9LDiw3rzP2acZ6KMwmLCfoRNRR/4yOqrq4JONBrM=
decidel@100.100.62.43: Permission denied (publickey,password).
brandon@100.100.62.43: Permission denied (publickey,password).


ssh -o ConnectTimeout=5 -o BatchMode=yes gravity-linux "hostname && whoami && which traceable-reqs && traceable-reqs --version" 2>&1

SSH gravity-linux with default user


Error: Exit code 255
decid@gravity-linux: Permission denied (publickey,password).


ssh -o ConnectTimeout=5 -o BatchMode=yes gravity-runner "hostname && whoami && traceable-reqs --version" 2>&1; tailscale status 2>&1 | Select-String "gravity"

SSH gravity-runner + tailscale roster


decid@gravity-runner: Permission denied (publickey,password,keyboard-interactive).

100.100.62.43   gravity-linux                 brandon@  linux    active; direct 104.174.122.7:36214, tx 11196 rx 10684
100.65.194.82   gravity-runner                brandon@  windows  active; direct 104.174.122.7:41642, tx 3020 rx 2684


git clone https://github.com/BigscreenVR/traceable-reqs C:\Users\decid\Documents\projects\traceable-reqs 2>&1 | Select-Object -Last 1

Clone traceable-reqs repo


Error: Exit code 128
fatal: destination path 'C:\Users\decid\Documents\projects\traceable-reqs' already exists and is not an empty directory.


cd C:\Users\decid\Documents\projects\traceable-reqs; git status; git log --oneline -3; git pull 2>&1 | Select-Object -Last 2

Inspect existing local traceable-reqs clone


On branch main
Your branch is behind 'origin/main' by 2 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.claude/

nothing added to commit but untracked files present (use "git add" to track)
a1670cf Support JavaScript and TypeScript scanning
4fbceb7 Trace new issue requirements
f96bc7b Implement remaining issue backlog
 rust/Cargo.toml          |  2 +-
 4 files changed, 40 insertions(+), 3 deletions(-)
Shell cwd was reset to C:\Users\decid\Documents\projects\spt-core