Files
archy/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-03-PLAN.md
T

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
13-aiui-functional-conversational-node-control-and-content-surf 03 execute 1
core/archipelago/examples/routstr_probe.rs
.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/COVERAGE.md
.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-ROUTSTR-FINDINGS.md
true
AIUI-01
truths artifacts key_links
Routstr's wire contract is recorded from a live observation or its unavailability is recorded — the Routstr client in 13-13 is never written against docs alone (RESEARCH Open Question 3)
COVERAGE.md's three `INTEGRATE — UNCONFIRMED` rows are either confirmed against a live provider or explicitly downgraded with a reason
The probe is an `examples/` binary, not a shipped code path — nothing in this plan changes the archipelago daemon
path provides contains
core/archipelago/examples/routstr_probe.rs Live Nostr kind-38421 subscribe + provider capability probe, run by hand 38421
path provides
.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-ROUTSTR-FINDINGS.md Observed event shape, header spelling, arguments encoding, price/model fields — or a recorded no-provider-found
from to via pattern
core/archipelago/examples/routstr_probe.rs core/archipelago/src/nostr_discovery.rs reuses the Tor-proxy-aware build_nostr_client pattern rather than constructing a second client build_nostr_client|Client::new
Answer RESEARCH Open Question 3 before it becomes a rewrite. `13-RESEARCH.md` rates the Routstr protocol **MEDIUM** confidence — every claim about kind `38421`, the `Authorization: Bearer cashuA…` vs `X-Cashu:` header spelling, and OpenAI-compat's JSON-string-encoded `tool_calls[].function.arguments` is cited from `docs.routstr.com` and has never been run against a live provider. `13-PATTERNS.md` records "no analog — first OpenAI-compatible client in this codebase."

Writing backends/routstr.rs (13-13) against docs alone is how a young, actively-developed external project turns into a debugging session inside a security-sensitive agent loop.

Purpose: a cheap, early, throwaway-safe probe that either confirms the contract or records honestly that no live provider was reachable — so 13-13 starts from a fact, and COVERAGE.md stops carrying three unconfirmed rows.

Output: an examples/ probe binary, 13-ROUTSTR-FINDINGS.md, and a rewritten COVERAGE.md.

<flagged_assumptions> None in this plan. </flagged_assumptions>

<artifacts_this_phase_produces> Symbols created by this plan:

  • core/archipelago/examples/routstr_probe.rs: fn main, async fn discover_providers, async fn probe_capabilities, const ROUTSTR_KIND: u16 = 38421, const DEFAULT_RELAYS
  • New file .planning/phases/13-.../13-ROUTSTR-FINDINGS.md

No daemon source file, no Cargo.toml dependency, and no RPC method is added by this plan. </artifacts_this_phase_produces>

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/STATE.md @.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-RESEARCH.md @.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/COVERAGE.md Task 1: Probe a live Routstr provider over Nostr and HTTP core/archipelago/examples/routstr_probe.rs - `core/archipelago/src/nostr_discovery.rs` — `build_nostr_client` and how this codebase subscribes with a filter through the Tor proxy. **Reuse this shape; do not construct a second, un-Tor-aware nostr-sdk client.** - `core/archipelago/Cargo.toml` lines 83-90 — `reqwest` 0.11 (`json`,`socks`,`rustls-tls`,`stream`) and `nostr-sdk` 0.44 (`nip04`,`nip44`) are already present. An `examples/` target links the package's dependencies, so **no `Cargo.toml` change is needed and none may be made.** - `.planning/phases/13-.../13-RESEARCH.md` "Routstr chat-completions call shape" and the "Sources / Secondary (MEDIUM confidence)" block — the exact claims under test. - `.planning/phases/13-.../COVERAGE.md` — the three rows marked `INTEGRATE — UNCONFIRMED` are this probe's checklist. Create `core/archipelago/examples/routstr_probe.rs` — a standalone throwaway probe, run by hand with `cd core && cargo run --example routstr_probe`. It is an example, not a test and not a daemon path: nothing it does is shipped.

discover_providers subscribes to the relays cited in RESEARCH (wss://relay.damus.io, wss://relay.nostr.band, wss://nos.lol) with a filter on kind 38421, waits up to 30 seconds, and prints every matching event verbatim: full tag list, full content, pubkey, created_at. Do not parse into a typed struct — the whole point is to see what is actually published rather than what a struct expects. Also run a second subscription with no kind filter but a #d tag filter on routstr-provider, in case the kind number in the docs has drifted; print anything it finds.

probe_capabilities takes the first discovered provider endpoint (or a --endpoint argv override so the probe is still useful when discovery finds nothing) and issues three unauthenticated GETs — /v1/models, /, and the provider's advertised info path if one appears in the event — printing status code and body for each. It must NOT send a Cashu token: this probe spends no money. If a 402 or a 401 body describes the expected payment header, print that body verbatim — that response is the single most valuable artifact this probe can capture, because it is the provider naming its own header spelling.

Print a final summary block answering exactly five questions in plain text: (1) was a live kind-38421 event observed? (2) what are its tag names and content keys? (3) what field carries the model list and what field carries the price? (4) what payment header does the provider name in a 401/402 body? (5) does /v1/models respond, and does its shape match OpenAI's?

Handle "no provider found" as a first-class outcome, not an error: print NO LIVE PROVIDER OBSERVED and exit 0. A probe that panics when the ecosystem is quiet teaches nothing. cd core && CARGO_INCREMENTAL=0 cargo build --example routstr_probe 2>&1 | tail -5 cd core && timeout 180 cargo run --example routstr_probe 2>&1 | tail -40 cd core && git diff --exit-code -- archipelago/Cargo.toml <acceptance_criteria>

  • cd core && cargo build --example routstr_probe exits 0
  • grep -q "38421" core/archipelago/examples/routstr_probe.rs
  • grep -c "cashu" core/archipelago/examples/routstr_probe.rs may be > 0 only in printed/parsing code — grep -ci 'build_payment_token\|auto_pay_token' core/archipelago/examples/routstr_probe.rs returns 0 (the probe spends nothing)
  • cd core && git diff --exit-code -- archipelago/Cargo.toml exits 0 — no dependency was added
  • cargo run --example routstr_probe exits 0 and its output ends with a summary block that either answers all five questions or states NO LIVE PROVIDER OBSERVED </acceptance_criteria> An examples/ file is deletable at any time and links no shipped code. The probe builds, runs to completion, spends nothing, and prints either a live event's real shape or an explicit no-provider-observed result.
Task 2: Record the findings and rewrite the coverage matrix from them .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-ROUTSTR-FINDINGS.md, .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/COVERAGE.md - `.planning/phases/13-.../COVERAGE.md` (full file) — specifically the three `INTEGRATE — UNCONFIRMED` rows and the closing `## Gate` section, which names this task as the gate 13-13 waits on. - The raw probe output from Task 1. - `.planning/phases/13-.../13-RESEARCH.md` Assumptions Log entry **A2**, which is the assumption this task retires or upholds. Write `13-ROUTSTR-FINDINGS.md` containing the probe's verbatim output (trimmed to the relevant events and bodies), the date and the relay set used, and a short table with one row per RESEARCH claim under test: the claim as cited, the observed value, and a verdict of `CONFIRMED`, `DIFFERS` (with the real value) or `NOT OBSERVED`. Cover at minimum: event kind number, `d` tag value, the content keys carrying `endpoints`/`models`/`pricing`, the payment header spelling, and whether `tool_calls[].function.arguments` arrives as a JSON-encoded string.

Then rewrite COVERAGE.md's matrix from those findings, not from the docs:

  • Every row that the probe confirmed loses its — UNCONFIRMED suffix.
  • Every row the probe found to differ is corrected to the observed reality.
  • Every row the probe could not observe is downgraded to OPT-OUT with the one-line reason not observable — no live provider reachable on <date>, or kept as INTEGRATE only if 13-13's first task is changed to a checkpoint:decision. Say which, explicitly, in the ## Gate section.
  • Do not leave a row marked INTEGRATE on confidence this plan did not obtain. An opt-out without a reason, or an integrate without evidence, is exactly the un-decided hole the coverage gate exists to close.

Update RESEARCH assumption A2's risk line in 13-ROUTSTR-FINDINGS.md (not by editing RESEARCH.md) to state whether A2 held.

Commit both files with docs(13): routstr protocol findings + coverage matrix from live probe and push per CLAUDE.md.

test -f .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-ROUTSTR-FINDINGS.md grep -c 'UNCONFIRMED' .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/COVERAGE.md | grep -qx 0 awk -F'|' '/OPT-OUT/ {if (length($4) < 12) {print "MISSING REASON:" $0; exit 1}}' .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/COVERAGE.md - `13-ROUTSTR-FINDINGS.md` exists and contains a verdict table where every row's verdict is one of `CONFIRMED`, `DIFFERS` or `NOT OBSERVED` - `grep -c 'UNCONFIRMED' COVERAGE.md` returns 0 — every row now carries either evidence or an explicit downgrade - Every `OPT-OUT` row in COVERAGE.md has a non-empty reason cell (the `awk` gate above exits 0) - COVERAGE.md's `## Gate` section states in one sentence whether 13-13 may proceed directly or must open with a `checkpoint:decision` - Both files are committed and pushed COVERAGE.md contains zero unconfirmed integrations and zero reasonless opt-outs, and 13-13's entry condition is stated as a fact rather than a hope.

<threat_model>

Trust Boundaries

Boundary Description
dev workstation → public Nostr relays Outbound WebSocket; relay operators see the subscription
dev workstation → an unknown third-party Routstr endpoint Outbound HTTP to an endpoint discovered from an untrusted, self-published Nostr event

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-13-16 Spoofing A hostile actor publishes a fake kind-38421 event advertising a malicious endpoint medium mitigate The probe treats every discovered endpoint as untrusted data: it only issues unauthenticated GETs, sends no token, no key and no node identity, and prints rather than parses. Provider trust selection is 13-13's problem, gated by D-05's budget cap
T-13-17 Denial of Service (financial) Probe accidentally spends ecash low mitigate The probe never calls auto_pay_token/build_payment_token; asserted by an acceptance grep. No wallet code is linked into the example's call graph
T-13-18 Information Disclosure Probe leaks node identity to relays or providers low mitigate Run from a dev workstation, not a node; the probe generates an ephemeral key for the subscription and sends no node-identifying header
T-13-19 Tampering Findings recorded from docs rather than observation, defeating the plan's purpose medium mitigate 13-ROUTSTR-FINDINGS.md must carry verbatim probe output; the verdict vocabulary forces NOT OBSERVED rather than an optimistic CONFIRMED
T-13-SC Tampering npm/pip/cargo installs high mitigate Zero packages added — asserted by git diff --exit-code -- archipelago/Cargo.toml. No install task, so no legitimacy checkpoint is required
</threat_model>
- `cd core && cargo build --example routstr_probe` exits 0 - `cd core && git diff --exit-code -- archipelago/Cargo.toml` exits 0 - `grep -c UNCONFIRMED COVERAGE.md` == 0 - `13-ROUTSTR-FINDINGS.md` exists with a verdict per RESEARCH claim

<success_criteria> 13-13 can be executed against an observed protocol or an explicitly recorded absence, and COVERAGE.md is a subtraction record backed by evidence rather than by documentation. </success_criteria>

Create `.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-03-SUMMARY.md` when done