Write

# M4-D2-wire — pairing ALPN driver (JIT plan)

**Status:** not started. Crypto core (D2a–e) + rate-limit + trust store all shipped & CI-green
as of `1df7331`. This is the transport glue that runs the 4-message ceremony over a real
iroh bidi stream and activates **REQ-PAIR-1**'s wire/`int` evidence.

## Goal

Two not-yet-trusted nodes complete the TOTP-SPAKE2 ceremony over a dedicated **pre-trust
pairing ALPN**, exchange+confirm pubkeys, and each writes the peer into its `TrustStore`.
Acceptance (M4-PLAN): *two machines pair via one TOTP code; trust material stored; negative
tests pass.* Component-level (loopback, relays-disabled) like `endpoint.rs` tests; full WAN
two-host is D9.

## Pieces (build order)

1. **`SPT_PAIR_ALPN`** = `b"spt-core/pair/0"` — separate from `SPT_NET_ALPN` so the relay
   permits pre-trust contact without exposing the trusted message surface (see `pairing.rs`
   doc). Decide: dedicated pairing endpoint bound only to this ALPN during a ceremony window,
   vs. add the ALPN to `NetEndpoint` and branch on `Incoming`'s negotiated ALPN. Leaning
   **dedicated** (pre-trust accept loop must not touch the trusted path).
2. **Wire framing** for the 4 messages over one bidi stream. Reuse the length-prefixed
   convention from `transcript.rs`. Messages:
   - `Hello` (initiator→responder): `step:u64` + `initiator_pub:32` + SPAKE `msg_a`.
   - `Reply` (responder→initiator): SPAKE `msg_b` + `responder_tag:32`.
   - `Confirm` (initiator→responder): `initiator_tag:32`.
   - `Done`/ack (responder→initiator): success/failure status.
3. **Responder driver** (seed-holder): `ratelimit.begin(subnet, now)` → on Ok, look up
   `SubnetStore` seed+epoch for the subnet, enforce `accepts_step(local, peer_step)` (±1),
   compute code at the peer's announced step, build `PairingTranscript`, run
   `Responder::respond`→`confirm`. On `Ok` → `ratelimit.record_success` + `TrustStore.record`.
   On `PairError` → `ratelimit.record_failure`. Map outcomes to the `Done` status byte.
4. **Initiator driver** (joiner): build transcript (subnet-name + epoch + both pubkeys + step),
   `Initiator::start`→`finish`. On success → `TrustStore.record` (+ `SubnetStore.add_joined`
   if this is a join that learns the seed — **see open Q2**).
5. **Tests:** loopback happy-path (both pin trust + agree key), wrong-code (Confirm fail, no
   trust written, failure recorded), Busy (concurrent ceremony rejected), backoff after a
   failure. `int`-tag REQ-PAIR-1 on the loopback E2E.

## OPEN DESIGN QUESTIONS (resolve before coding — study ADR-0005 + CONTEXT "Pairing & trust")

- **Q1 — rendezvous vs iroh dial.** CONTEXT:481 says the relay routes pre-trust pairing by a
  rendezvous token `H(subnet-name ‖ epoch)`, NOT by NodeId — but iroh `connect()` dials a
  NodeId. How do two nodes that don't know each other's NodeId meet? Options: (a) relay-side
  rendezvous (needs relay support — likely D9/relay work; for now dial by a NodeId learned
  out-of-band/mDNS); (b) a discovery shim keyed by the token. **Likely defer true
  token-rendezvous to D9; component test dials by addr like `endpoint.rs`.** Confirm scope.
- **Q2 — does the joiner learn the epoch?** Joiner holds only the 6-digit code + subnet-name
  (psyche design fact). The transcript binds the epoch (responder-authoritative). If the
  rendezvous token = `H(name ‖ epoch)`, the joiner needs the epoch to even meet — contradiction
  with "code+name only." Resolve: either (a) responder announces epoch in `Reply` and the
  transcript is built/checked responder-first, or (b) epoch is implied by the current TOTP
  window. Re-read D2c's "epoch is the responder-authoritative value the ceremony binds."
- **Q3 — seed transfer on join.** After a successful join, does the new node receive the
  subnet seed over the (now-confirmed, encrypted) channel so it becomes a future seed-holder?
  CONTEXT: every trusted node holds the seed. The wire driver may need a 5th post-confirm step
  delivering the seed → `SubnetStore.add_joined(name, seed, epoch)`. Confirm whether this
  lands here or in D2f.

## Conventions (carried)
- NO `cargo fmt` (no CI gate; touch only edited files).
- Tag `[impl->REQ-PAIR-1]` on the driver, `[unit->REQ-PAIR-1]` on tests; `int` stage at D9.
- `spt-net` is async (tokio); mirror `endpoint.rs` test style (loopback, RelayPolicy::Disabled).
- Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.

## After D2-wire
D2f (multi-subnet ceremony inputs: code+name, rendezvous token, create-new names up front —
REQ-PAIR-4/5) → D2g (elevation-gated `spt pair show-totp` — REQ-PAIR-3/6) → D3 registry.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\M4-D2-WIRE-PLAN.md (file state is current in your context — no need to Read it back)

# M4-D2-wire — pairing ALPN driver (JIT plan)

**Status:** not started. Crypto core (D2a–e) + rate-limit + trust store all shipped & CI-green
as of `1df7331`. This is the transport glue that runs the 4-message ceremony over a real
iroh bidi stream and activates **REQ-PAIR-1**'s wire/`int` evidence.

## Goal

Two not-yet-trusted nodes complete the TOTP-SPAKE2 ceremony over a dedicated **pre-trust
pairing ALPN**, exchange+confirm pubkeys, and each writes the peer into its `TrustStore`.
Acceptance (M4-PLAN): *two machines pair via one TOTP code; trust material stored; negative
tests pass.* Component-level (loopback, relays-disabled) like `endpoint.rs` tests; full WAN
two-host is D9.

## Pieces (build order)

1. **`SPT_PAIR_ALPN`** = `b"spt-core/pair/0"` — separate from `SPT_NET_ALPN` so the relay
   permits pre-trust contact without exposing the trusted message surface (see `pairing.rs`
   doc). Decide: dedicated pairing endpoint bound only to this ALPN during a ceremony window,
   vs. add the ALPN to `NetEndpoint` and branch on `Incoming`'s negotiated ALPN. Leaning
   **dedicated** (pre-trust accept loop must not touch the trusted path).
2. **Wire framing** for the 4 messages over one bidi stream. Reuse the length-prefixed
   convention from `transcript.rs`. Messages:
   - `Hello` (initiator→responder): `step:u64` + `initiator_pub:32` + SPAKE `msg_a`.
   - `Reply` (responder→initiator): SPAKE `msg_b` + `responder_tag:32`.
   - `Confirm` (initiator→responder): `initiator_tag:32`.
   - `Done`/ack (responder→initiator): success/failure status.
3. **Responder driver** (seed-holder): `ratelimit.begin(subnet, now)` → on Ok, look up
   `SubnetStore` seed+epoch for the subnet, enforce `accepts_step(local, peer_step)` (±1),
   compute code at the peer's announced step, build `PairingTranscript`, run
   `Responder::respond`→`confirm`. On `Ok` → `ratelimit.record_success` + `TrustStore.record`.
   On `PairError` → `ratelimit.record_failure`. Map outcomes to the `Done` status byte.
4. **Initiator driver** (joiner): build transcript (subnet-name + epoch + both pubkeys + step),
   `Initiator::start`→`finish`. On success → `TrustStore.record` (+ `SubnetStore.add_joined`
   if this is a join that learns the seed — **see open Q2**).
5. **Tests:** loopback happy-path (both pin trust + agree key), wrong-code (Confirm fail, no
   trust written, failure recorded), Busy (concurrent ceremony rejected), backoff after a
   failure. `int`-tag REQ-PAIR-1 on the loopback E2E.

## OPEN DESIGN QUESTIONS (resolve before coding — study ADR-0005 + CONTEXT "Pairing & trust")

- **Q1 — rendezvous vs iroh dial.** CONTEXT:481 says the relay routes pre-trust pairing by a
  rendezvous token `H(subnet-name ‖ epoch)`, NOT by NodeId — but iroh `connect()` dials a
  NodeId. How do two nodes that don't know each other's NodeId meet? Options: (a) relay-side
  rendezvous (needs relay support — likely D9/relay work; for now dial by a NodeId learned
  out-of-band/mDNS); (b) a discovery shim keyed by the token. **Likely defer true
  token-rendezvous to D9; component test dials by addr like `endpoint.rs`.** Confirm scope.
- **Q2 — does the joiner learn the epoch?** Joiner holds only the 6-digit code + subnet-name
  (psyche design fact). The transcript binds the epoch (responder-authoritative). If the
  rendezvous token = `H(name ‖ epoch)`, the joiner needs the epoch to even meet — contradiction
  with "code+name only." Resolve: either (a) responder announces epoch in `Reply` and the
  transcript is built/checked responder-first, or (b) epoch is implied by the current TOTP
  window. Re-read D2c's "epoch is the responder-authoritative value the ceremony binds."
- **Q3 — seed transfer on join.** After a successful join, does the new node receive the
  subnet seed over the (now-confirmed, encrypted) channel so it becomes a future seed-holder?
  CONTEXT: every trusted node holds the seed. The wire driver may need a 5th post-confirm step
  delivering the seed → `SubnetStore.add_joined(name, seed, epoch)`. Confirm whether this
  lands here or in D2f.

## Conventions (carried)
- NO `cargo fmt` (no CI gate; touch only edited files).
- Tag `[impl->REQ-PAIR-1]` on the driver, `[unit->REQ-PAIR-1]` on tests; `int` stage at D9.
- `spt-net` is async (tokio); mirror `endpoint.rs` test style (loopback, RelayPolicy::Disabled).
- Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.

## After D2-wire
D2f (multi-subnet ceremony inputs: code+name, rendezvous token, create-new names up front —
REQ-PAIR-4/5) → D2g (elevation-gated `spt pair show-totp` — REQ-PAIR-3/6) → D3 registry.


NOTE FROM THE USER: proceed withnextsteps.


Check D2e CI watch status.


Bash

cat "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bb9dxi3fj.output" 2>/dev/null | grep -vE "Node.js 20|github.blog|FORCE|ACTIONS_ALLOW|windows-2025|deprecation|^$" | tail -12; echo "---"; gh run list --limit 1 --json status,conclusion,headSha -q '.[0]'

Check D2e CI state


run=26909965205
---
{"conclusion":"","headSha":"1df7331dfe1cc86c16b0958184b8700ff7ed6586","status":"in_progress"}


D2e CI still compiling on 1df7331 (background watch bb9dxi3fj will notify). Commit the JIT plan so it survives a context clear. Check plan-doc tracking first.


git ls-files --error-unmatch M4-PLAN.md >/dev/null 2>&1 && echo "M4-PLAN tracked" || echo "M4-PLAN untracked"; git check-ignore M4-D2-WIRE-PLAN.md && echo "wire-plan IGNORED" || echo "wire-plan not ignored"

Check plan-doc tracking + ignore status


M4-PLAN tracked
wire-plan not ignored


git add M4-D2-WIRE-PLAN.md && git commit -q -F - <<'EOF'
docs(m4): JIT plan for D2-wire pairing ALPN driver

Captures the build order (pairing ALPN, 4-msg framing, responder/initiator
drivers, loopback tests) and the three open design questions to resolve
before coding: rendezvous-token vs iroh NodeId dial (Q1), whether the joiner
learns the epoch (Q2), and seed transfer on join (Q3). Picks up after the
D2a-e crypto core + rate-limit + trust store landed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
git log --oneline -3

Commit JIT plan


warning: in the working copy of 'M4-D2-WIRE-PLAN.md', LF will be replaced by CRLF the next time Git touches it
f404235 docs(m4): JIT plan for D2-wire pairing ALPN driver
1df7331 feat(store): M4-D2e trust store TOFU + warn-on-change (REQ-PAIR-2)
b0bdaab feat(net): M4-D2d subnet-global pairing rate limit (#11)


Wait for D2e CI (1df7331) to go green via the background watch before clearing. Push the doc commit (durability for psyche sync; doc-only = trivially green).


Push doc commit


To https://github.com/SaberMage/spt-core.git
   1df7331..f404235  dev-freeform -> dev-freeform


Two requirements shipped this run, both pushed:

- **D2d** `b0bdaab` — `spt-net::pairing::ratelimit` (REQ-HAZARD-PAIR-RATE-LIMIT). One in-flight ceremony/subnet + shared failure counter + exp backoff (1s→3600s cap, abandoned-ceremony reclaim). CI **green**. 7 unit tests.
- **D2e** `1df7331` — `spt-store::trust::TrustStore` (REQ-PAIR-2). TOFU pin per-subnet + warn-on-change (`KeyChanged`/`repin`) + `revoke`. 9 unit tests. CI **in progress** (background watch armed).

Clippy clean, traceability exit 0 on both. JIT plan for the next big task (D2-wire ALPN driver — `f404235`) written with the 3 open design questions surfaced.

Waiting on D2e CI green (background watch `bb9dxi3fj` will notify). Once green → commune status to Psyche + self-clear for fresh context on D2-wire. Holding until then — won't start the design-heavy wire driver at the tail of a long session.


<task-notification>
<task-id>bb9dxi3fj</task-id>
<tool-use-id>toolu_017TTawi7TopofTwjASjzDYn</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bb9dxi3fj.output</output-file>
<status>completed</status>
<summary>Background command "Watch CI on 1df7331" completed (exit code 0)</summary>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


D2e CI green (exit 0). Both shipped commits confirmed green. Commune status to Psyche, then self-clear.


grep -vE "Node.js 20|github.blog|FORCE|ACTIONS_ALLOW|windows-2025|deprecation|^$" "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bb9dxi3fj.output" 2>/dev/null | tail -8

Confirm D2e CI checks


run=26909965205
X Canceling since a higher priority waiting request for ci-refs/heads/dev-freeform exists
test (windows-latest): .github#1
test (windows-latest): .github#2
X The operation was canceled.
test (windows-latest): .github#12
test (windows-latest): .github#1


D2e run was **canceled** (concurrency — my doc push f404235 superseded it). f404235 contains all D2e code + the doc, so its run is the authoritative check. Watch it.


RID=$(gh run list --limit 1 --json databaseId,headSha -q '.[0].databaseId'); SHA=$(gh run list --limit 1 --json headSha -q '.[0].headSha'); echo "run=$RID sha=$SHA"; gh run watch "$RID" --exit-status 2>&1 | grep -vE "Node.js 20|github.blog|FORCE|ACTIONS_ALLOW|windows-2025|deprecation|^$" | tail -12

Watch latest CI run (f404235)


Command running in background with ID: bjiqvstd7. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bjiqvstd7.output. You will be notified when it completes. To check interim output, use Read on that file path.


Watching f404235 CI (background `bjiqvstd7`) — that run validates the D2e code (the cancelled 1df7331 run was superseded by my doc push). Holding for green before commune + self-clear. Will resume on notification.


estimate the remaining time and schedule a /new-alarm for yourself to draw a line and troubleshoot if it hasn't concluded by then


CI time estimate: ubuntu test ~3-4min, but **windows-latest is the long pole** (~8-10min; the doc-only run earlier clocked 10m). f404235 pushed ~1min ago → expect green ~8-10min out. Set a backstop alarm at +12min.


$OWL new-alarm doyle "+12m" -- "CI backstop: f404235 (D2d+D2e+wire-plan) run should be GREEN by now. If still in_progress/failed: gh run list --limit 3; troubleshoot. If green already + D2-wire not started: you likely self-cleared — ignore." 2>&1 | tail -5

Set +12min CI backstop alarm


➜ NEW-ALARM-SCHEDULED (fires at epoch 1780518319)


Alarm set (fires ~12min out, epoch 1780518319) as the troubleshoot line.

Now holding for whichever fires first:
- **CI green** (background `bjiqvstd7`) → commune + self-clear for D2-wire.
- **Alarm** (+12min) → if run still stuck/failed, troubleshoot f404235.

Both paths covered. Waiting.


<task-notification>
<task-id>bjiqvstd7</task-id>
<tool-use-id>toolu_01QKLfYSJ4EFS5RDUSdHmPn3</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bjiqvstd7.output</output-file>
<status>completed</status>
<summary>Background command "Watch latest CI run (f404235)" completed (exit code 0)</summary>
</task-notification>


CI watch exit 0 — but verify genuine success (last exit 0 was a cancellation). Confirm f404235 conclusion.


grep -vE "Node.js 20|github.blog|FORCE|ACTIONS_ALLOW|windows-2025|deprecation|^$" "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\b494f5e6-5b4d-4145-aaaa-5585e1168377\tasks\bjiqvstd7.output" 2>/dev/null | tail -10; echo "===AUTHORITATIVE==="; gh run list --limit 3 --json headSha,status,conclusion,databaseId -q '.[] | "\(.databaseId) \(.headSha[0:7]) \(.status) \(.conclusion)"'

Verify f404235 genuine conclusion


  ✓ Build
  ✓ Test
  ✓ Clippy (deny warnings)
  ✓ Post Cache cargo
  ✓ Post Run actions/checkout@v4
  ✓ Complete job
ANNOTATIONS
traceability: .github#2
test (windows-latest): .github#5
test (windows-latest): .github#1
===AUTHORITATIVE===
26910084684 f404235 completed success
26909965205 1df7331 completed cancelled
26909693469 b0bdaab completed success


Confirmed genuine green: **f404235 completed success** (Build/Test/Clippy ✓). 1df7331 cancelled (superseded; its code lives in green f404235). b0bdaab success. All clean.

Commune status delta to Psyche.


Write

# Commune — D2d + D2e shipped, D2-wire next

**M4 (spt-net) D2 pairing — crypto core + rate-limit + trust store all DONE & CI-green.**

Shipped this session (pushed, CI green on f404235):
- **D2d** `b0bdaab` — `spt-net::pairing::ratelimit::PairingRateLimiter` (REQ-HAZARD-PAIR-RATE-LIMIT,
  #11). One in-flight ceremony per subnet (Busy), shared consecutive-failure counter, exp backoff
  1s→cap 3600s, abandoned-ceremony reclaim after 60s (charged as failure). Per-subnet isolated,
  time injected (no wall clock). The ±1 TOTP window is JUSTIFIED not dropped: serialization + backoff
  caps guess rate. 7 unit tests. Activated [impl,unit].
- **D2e** `1df7331` — `spt-store::trust::TrustStore` at identity/trust/peers.json (REQ-PAIR-2).
  TOFU pin per-(subnet,pubkey) post-ceremony → Pinned/AlreadyTrusted; warn-on-change returns
  KeyChanged{existing} WITHOUT mutating (repin applies an accepted rotation); is_trusted = connect
  gate; revoke = trust-delete (pair w/ seed rotation #10). Mirrors SubnetStore (atomic, degrade-to-empty).
  9 unit tests. Added perch::trust_file(). Activated [impl,unit].
- `f404235` — JIT plan M4-D2-WIRE-PLAN.md for the next task.

KEY DESIGN FACT (D2e): iroh NodeId == Ed25519 pubkey, so an unknown key is just an untrusted
node, NOT a "changed" one. Warn-on-change is anchored on an optional (subnet,user,label) handle —
DORMANT in v1 until labels are assigned (deferred to GUI/D9). Honest residual; mechanism tested now.

CI GOTCHA (cost a cancelled run): pushing a follow-up commit CANCELS the prior commit's in-flight
CI (concurrency group ci-refs/heads/dev-freeform). 1df7331's run was cancelled by the f404235 push;
its code is covered by green f404235. Watch the LATEST run's sha, not an older one.

NEXT — **D2-wire pairing ALPN driver** (activates REQ-PAIR-1 wire/int evidence). See
M4-D2-WIRE-PLAN.md. Big design-heavy task (why I cleared for fresh context). Build order: SPT_PAIR_ALPN
(pre-trust, separate from SPT_NET_ALPN) → 4-msg bidi framing (length-prefixed like transcript.rs) →
responder driver (ratelimit.begin → seed lookup + ±1 step → Responder::respond/confirm → trust.record)
→ initiator driver → loopback tests. THREE OPEN QUESTIONS to resolve first (in the plan doc):
Q1 rendezvous-token H(name‖epoch) vs iroh NodeId dial (likely defer true rendezvous to D9, dial by addr
for component test); Q2 does joiner learn the epoch (it holds only code+name — contradiction with
H(name‖epoch) rendezvous); Q3 seed transfer on join (new node must become a seed-holder).
Then D2f (multi-subnet inputs, REQ-PAIR-4/5) → D2g (elevation-gated show-totp CLI, REQ-PAIR-3/6) → D3 registry.

Conventions: NO cargo fmt (no CI gate, pollutes drift). spt-net is the async crate (tokio loopback
tests, RelayPolicy::Disabled). Tag evidence same commit. traceable-reqs check before done.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\doyle-commune.md (file state is current in your context — no need to Read it back)

# Commune — D2d + D2e shipped, D2-wire next

**M4 (spt-net) D2 pairing — crypto core + rate-limit + trust store all DONE & CI-green.**

Shipped this session (pushed, CI green on f404235):
- **D2d** `b0bdaab` — `spt-net::pairing::ratelimit::PairingRateLimiter` (REQ-HAZARD-PAIR-RATE-LIMIT,
  #11). One in-flight ceremony per subnet (Busy), shared consecutive-failure counter, exp backoff
  1s→cap 3600s, abandoned-ceremony reclaim after 60s (charged as failure). Per-subnet isolated,
  time injected (no wall clock). The ±1 TOTP window is JUSTIFIED not dropped: serialization + backoff
  caps guess rate. 7 unit tests. Activated [impl,unit].
- **D2e** `1df7331` — `spt-store::trust::TrustStore` at identity/trust/peers.json (REQ-PAIR-2).
  TOFU pin per-(subnet,pubkey) post-ceremony → Pinned/AlreadyTrusted; warn-on-change returns
  KeyChanged{existing} WITHOUT mutating (repin applies an accepted rotation); is_trusted = connect
  gate; revoke = trust-delete (pair w/ seed rotation #10). Mirrors SubnetStore (atomic, degrade-to-empty).
  9 unit tests. Added perch::trust_file(). Activated [impl,unit].
- `f404235` — JIT plan M4-D2-WIRE-PLAN.md for the next task.

KEY DESIGN FACT (D2e): iroh NodeId == Ed25519 pubkey, so an unknown key is just an untrusted
node, NOT a "changed" one. Warn-on-change is anchored on an optional (subnet,user,label) handle —
DORMANT in v1 until labels are assigned (deferred to GUI/D9). Honest residual; mechanism tested now.

CI GOTCHA (cost a cancelled run): pushing a follow-up commit CANCELS the prior commit's in-flight
CI (concurrency group ci-refs/heads/dev-freeform). 1df7331's run was cancelled by the f404235 push;
its code is covered by green f404235. Watch the LATEST run's sha, not an older one.

NEXT — **D2-wire pairing ALPN driver** (activates REQ-PAIR-1 wire/int evidence). See
M4-D2-WIRE-PLAN.md. Big design-heavy task (why I cleared for fresh context). Build order: SPT_PAIR_ALPN
(pre-trust, separate from SPT_NET_ALPN) → 4-msg bidi framing (length-prefixed like transcript.rs) →
responder driver (ratelimit.begin → seed lookup + ±1 step → Responder::respond/confirm → trust.record)
→ initiator driver → loopback tests. THREE OPEN QUESTIONS to resolve first (in the plan doc):
Q1 rendezvous-token H(name‖epoch) vs iroh NodeId dial (likely defer true rendezvous to D9, dial by addr
for component test); Q2 does joiner learn the epoch (it holds only code+name — contradiction with
H(name‖epoch) rendezvous); Q3 seed transfer on join (new node must become a seed-holder).
Then D2f (multi-subnet inputs, REQ-PAIR-4/5) → D2g (elevation-gated show-totp CLI, REQ-PAIR-3/6) → D3 registry.

Conventions: NO cargo fmt (no CI gate, pollutes drift). spt-net is the async crate (tokio loopback
tests, RelayPolicy::Disabled). Tag evidence same commit. traceable-reqs check before done.


Commune dropped (Psyche ingests + auto-deletes). Now self-clear with a resume note for D2-wire.


Skill

Resume M4-D2-wire pairing ALPN driver. D2d (ratelimit) + D2e (trust store) shipped & CI-green (f404235). Read M4-D2-WIRE-PLAN.md FIRST — resolve its 3 open design questions (Q1 rendezvous vs iroh dial, Q2 joiner epoch, Q3 seed transfer) against ADR-0005 + CONTEXT.md "Pairing & trust" before coding. Then build: SPT_PAIR_ALPN → 4-msg bidi framing → responder/initiator drivers → loopback tests → activate REQ-PAIR-1 wire evidence. NO cargo fmt. traceable-reqs check before done. There is a +12min CI backstop alarm pending — if it fires, CI is already green; ignore it.


Launching skill: self-clear


Base directory for this skill: C:\Users\decid\.claude\skills\self-clear

# /self-clear

Wipes THIS agent's conversation (`/clear`), then ~10s later TYPES a self-note
as a fresh user prompt so the agent wakes on a clean context and proceeds.

## Run it

One script does everything. Just run:

```powershell
& "C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1"
```

Expect: `SELF-CLEAR armed: id=<agent_id> winpid=<n> hwnd=<n> — /clear queued; self-note typed in ~10s`

Then stop — end your turn promptly so the queued `/clear` fires.

## What the script does

1. `owl whoami` → resolves this session's `agent_id` (context-independent, survives the wipe). No perch → it aborts WITHOUT clearing and tells you to `/spt:ready` or `/spt:live` first.
2. Walks the parent chain (`pwsh → claude.exe → … → WindowsTerminal`) to the ancestor that owns the window, foregrounds it.
3. Types `/clear` + Enter — **queued**; only fires when this turn ends.
4. Spawns a **detached** waker: waits 10s (past turn-end + wipe), re-foregrounds the terminal via the arm-time `hwnd`/`winpid`, then **types** `NOTE FROM THE USER: proceed with next steps.` + Enter as a fresh user prompt.

The 10s delay + detachment are deliberate: the prompt must be typed **after**
`/clear` takes effect, so it lands on the clean context and starts a new turn.

Why typed, not `owl send`: after `/clear` the session sits idle awaiting user
input. A spooled owl message can't self-start a turn — it surfaces only during an
already-active turn (PreToolUse hook) or in a live Monitor stream (whose buffer
`/clear` wipes). So a self-addressed owl note never woke the agent. A typed prompt
deterministically starts a turn AND is the re-seed.

## Caveats

- Window focus brings the whole terminal forward; it can't target a specific tab. Clean when the agent's tab is active.
- The note is typed via SendKeys, so the terminal must stay the foreground-able window of `winpid`. Don't close/replace the terminal in the ~10s gap.
- SendKeys reserves `+ ^ % ~ ( ) { } [ ]`; the default note text uses none. If you customize the note, escape those chars.
- If your turn runs longer than ~10s after the script returns, the prompt could be typed before the wipe completes. Stop promptly after running.

ARGUMENTS: Resume M4-D2-wire pairing ALPN driver. D2d (ratelimit) + D2e (trust store) shipped & CI-green (f404235). Read M4-D2-WIRE-PLAN.md FIRST — resolve its 3 open design questions (Q1 rendezvous vs iroh dial, Q2 joiner epoch, Q3 seed transfer) against ADR-0005 + CONTEXT.md "Pairing & trust" before coding. Then build: SPT_PAIR_ALPN → 4-msg bidi framing → responder/initiator drivers → loopback tests → activate REQ-PAIR-1 wire evidence. NO cargo fmt. traceable-reqs check before done. There is a +12min CI backstop alarm pending — if it fires, CI is already green; ignore it.


Check if the script accepts a custom note arg (to seed the D2-wire pointer).


Bash

grep -n -iE "param|args|NOTE FROM THE USER|\[string\]|note" "C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1" 2>/dev/null | head -20

Check self-clear script for note param


8:#      TYPES the self-note as a fresh user prompt + Enter.
14:# Why TYPE the note (not `owl send`): after /clear the session sits IDLE awaiting user
61:# --- 4. Detached delayed waker (10s) — re-foregrounds + TYPES the note as a fresh prompt ---
63:$note = 'NOTE FROM THE USER: proceed with next steps.'
81:`$ws.SendKeys('$note')
88:"SELF-CLEAR armed: id=$id winpid=$winpid hwnd=$hwnd — /clear queued; self-note typed in ~10s"


No note param (line 63 hardcoded; my detailed note has SendKeys-reserved chars anyway). Fine — commune + M4-D2-WIRE-PLAN.md carry the full detail; the fixed wake note + psyche context resumes it. Run it.


Run self-clear


Error: Exit code 2
/usr/bin/bash: eval: line 4: syntax error near unexpected token `&'
/usr/bin/bash: eval: line 4: `& "C:\Users\decid\.claude\skills\self-clear\scripts\self-clear.ps1"'


/clear


Bash tool — use PowerShell tool.


PowerShell

Run self-clear via PowerShell


SELF-CLEAR armed: id=doyle winpid=29328 hwnd=133338 — /clear queued; self-note typed in ~10s


Armed (id=doyle, /clear queued, self-note in ~10s). Ending turn now so the wipe fires.

D2d + D2e shipped & green. Resume picks up D2-wire from M4-D2-WIRE-PLAN.md.