Commit Graph

81 Commits

Author SHA1 Message Date
caf4c5ba91 feat(trunk): TrunkSession + trunk_driver::drive -- FOB-side trunk-leg tick (slice-5 T4)
The trunk leg's tick function. Parallels crates/rutster-media/src/loop_driver.rs
minus the str0m/Opus/RTP machinery -- there is no RTP to decode, no Opus to
encode, no str0m poll loop to drain. Caller->FOB direction is a pure mpsc
drain; FOB->caller direction is a mpsc push.

Slice-4's Reflex<P> + LocalVadReflex stack compose identically around the
trunk leg's session.pipe -- proving the FOB reflex loop is ingress-agnostic
(spec §2.3 -- the architecture's load-bearing claim). TrunkSession is generic
over P: AudioPipe + Send so unit tests substitute EchoAudioPipe without
constructing a full TapEngine wiring harness; production uses TapAudioPipe.

The seam gate holds: crates/rutster-media/src/{loop_driver.rs,rtc_session.rs}
stay byte-identical because the trunk leg NEVER enters that code path. The
MediaThread dispatches via the new MediaLeg enum (T5).

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 12:10:58 -04:00
c7d48559a5 fmt(trunk): rustfmt reflow on twilio_media_streams (T3)
Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 12:09:48 -04:00
bf47634e3b feat(trunk): TwilioMediaStreamsServer -- axum WSS handler (slice-5 T3)
Accepts Twilio's inbound WSS connections; parses the JSON envelope
(connected/start/media/stop per Twilio's documented protocol); decodes
base64 µ-law via G711Codec; ferries decoded PCM frames to a per-call
inbound mpsc. Concurrently drains the outbound mpsc + sends back JSON
media frames. Same tokio/std-thread split as slice-2's TapEngine: tokio
owns IO; the std thread owns the 20ms tick via trunk_driver::drive (T4).

ADR-0009 honored: TwilioCredentials never reach this module -- only
per-call CallSid (operational log correlation only) + tap_url (operator
configured brain WS URL) flow through the RegisterTrunkInboundChannel.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 12:09:48 -04:00
56f7da577b feat(trunk): TwilioMediaStreamsServer skeleton (slice-5 T3 placeholder commit)
Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 12:09:48 -04:00
bcd775747a feat(trunk): G711Codec — µ-law encode/decode + 8kHz↔24kHz linear-interpolated resampling (slice-5 T1)
In-core ~30-line table-driven codec (no dep). The ITU-T G.711 µ-law
companding formula is a piece of telephony history worth teaching (AGENTS.md
learner-facing comment mandate). 3× linear upsample on decode; 3× decimation
downsample on encode. The resampler artifacts are below the barge-in trigger
threshold (LocalVadReflex only needs RMS energy); rubato lands in a post-
spearhead refinement if a downstream consumer needs better (spec §6.6).

Task T1 of slice-5 — T3 (TwilioMediaStreamsServer) consumes this codec.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 12:09:48 -04:00
40cc27e9f0 Merge pull request 'slice-5: add rented-transport green-zone half (T2/T6/T9/T10)' (#17) from slice-5/rented-transport-dev-b into main
Some checks failed
CI / fmt (push) Successful in 1m32s
CI / clippy (push) Successful in 1m50s
CI / test (1.85) (push) Successful in 4m8s
CI / deny (push) Has been cancelled
CI / twilio-live (manual only) (push) Has been cancelled
CI / test (stable) (push) Has started running
2026-07-05 16:02:19 +00:00
3306214b38 test(trunk): ADR-0009 static assertion -- TwilioCredentials is trunk-local (slice-5 T10 follow-up)
Spec §7 done-criteria #10 demands a static assertion that TwilioCredentials
lives ONLY in rutster-trunk (ADR-0009 -- provider credentials never reach the
brain). The test compiles only because crate::provider::TwilioCredentials
resolves (the type's canonical home); if someone moved/re-exported it through
the workspace root or a sibling crate, this test's doc + the dep-graph change
would surface in review. The invariant is structural: sibling crates
(rutster-media, rutster-tap) do not depend on rutster-trunk, so the type
cannot reach them. The binary's config::twilio_credentials is the single
expected import path.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 16:02:19 +00:00
77175aaba1 ci(trunk): twilio-live manual-trigger job + seam-gate verification + doc-link fixes (slice-5 T10)
The routine CI gate stays feature-default-off: MockCallControlClient is the
per-PR test surface. A new twilio-live job runs ONLY on manual
workflow_dispatch (maintainer triggers pre-release) -- it exercises clippy
--features=twilio-live + the live TwilioCallControlClient tests against real
Twilio credentials (TWILIO_* secrets). Never runs per-PR.

Seam gate verified UNCHANGED: loop_driver.rs (744bf314...) + rtc_session.rs
(f47d63b9...) blob hashes match the slice-4 Task 10 pins exactly -- the trunk
leg's tick lives entirely in rutster-trunk/src/loop_driver.rs (a separate file
in a separate crate), so the media-crate seam files stay byte-identical.

cargo deny: reqwest (rustls-tls) + base64 + async-trait introduce ZERO new
duplicate dep versions (verified via `cargo tree -d` with vs without
--features=twilio-live: identical duplicate sets -- the existing skip list in
deny.toml remains sufficient). Local cargo-deny 0.18.3 cannot parse the
`-or-later` SPDX form + CVSS 4.0 advisory entries (pre-existing limitation
documented in deny.toml; CI's cargo-deny-action@v2 bundles 0.19.x which handles
both) -- CI is the authoritative deny gate.

Two rustdoc intra-doc-link warnings in my code fixed (mock.rs private-item
link -> plain inline code; lib.rs redundant explicit link target simplified).
Two pre-existing rustdoc warnings remain in rutster-tap/protocol.rs +
rutster/tap_engine.rs (out of scope -- pre-existing from slices 2-3, not
introduced by slice-5).

T10 of slice-5. This is the final task on the dev-b chain (T2 + T6 + T9 + T10
all landed).

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 16:02:19 +00:00
e6891f2cec docs: QUICKSTART env table + 'make a real phone call' walkthrough + README status update (slice-5 T9)
QUICKSTART gains a Twilio Media Streams section: env-var table for the four
RUTSTER_TWILIO_* vars, the run-with-twilio-live command, the point-Twilio-
at-rutster webhook/TwiML walkthrough, + the outbound-call curl example. The
/v1/trunk routes' auth-deferral (slice 6) is flagged. A 'what's different
from WebRTC' note explains the architectural reuse -- the reflex stack is
ingress-agnostic (Reflex<TapAudioPipe> + LocalVadReflex REUSED from slice-4).

README's spearhead status is corrected + extended: slices 1-4 are merged to
main (the prior status stalled at '1-3 merged, slice-4 active' -- stale);
4.5 (sim/benchmark, ADR-0010) + step 5 (PSTN via rented transport, ADR-0007)
are the active build targets. ADR-0007 honored: rutster parses zero SIP bytes.

T9 of slice-5.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 16:02:19 +00:00
d19d772bd0 feat(trunk): TwilioCallControlClient (live REST impl behind twilio-live) + env parser (slice-5 T6)
The live Twilio call-control client. Originates outbound calls via Twilio's
Calls.json API (TwiML <Connect><Stream> instructs Twilio to fork audio back
to our /twilio/media-stream WSS endpoint); hangs up via Status=completed.
HTTP basic auth over HTTPS; auth_token is NEVER logged (ADR-0009 -- provider
credentials never reach the brain) -- tracing fields expose only caller-
controlled values (to, from) + the CallSid's last 4 chars.

Feature-gated behind `twilio-live` so the routine CI gate stays feature-
default-off: MockCallControlClient is the per-PR test surface; the maintainer
runs cargo test --features=twilio-live when validating a release. reqwest +
tracing + serde_json are optional deps (dep:foo syntax) -- pulled in only
when the feature is on, keeping the default resolve lean.

The env parser (config::twilio_credentials) follows slice-5/seams' pure-
function pattern: takes Option<String> inputs (testable without env mutation),
returns Ok(None) when all four RUTSTER_TWILIO_* vars are unset (WebRTC-only
mode), Ok(Some) when all four present + parse, Err on partial config (fail-
fast at startup) or malformed values. TwilioCredentials is imported by the
binary but never re-exported through the workspace (ADR-0009).

T6 of slice-5. Depends on T2 (TwilioCredentials + CallControlClient trait).

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 16:02:19 +00:00
0320ab5ce0 feat(trunk): CallControlClient trait + MockCallControlClient + TwilioCredentials (slice-5 T2)
The provider call-control seam (green zone, ADR-0008). The trait locks the
boundary so the next provider (Telnyx, etc.) is an implementation, not a
refactor. MockCallControlClient is the CI test double; the live
TwilioCallControlClient (T6) lives behind the twilio-live feature flag.

TwilioCredentials lives ONLY in crates/rutster-trunk/ -- never re-exported
through the workspace (ADR-0009 -- provider credentials never reach the
brain). Its Debug impl is hand-written (NOT derived) so the auth_token
renders as <redacted>, never leaking into tracing/panic output.

Option<SpendToken> on originate is the pre-paved seam for spearhead step-6
(spend cap); this slice passes None everywhere. The signature is locked so
step 6 is additive, not a refactor.

T2 of slice-5. lib.rs gains `pub mod provider;` -- stacked-branches carve-out
(rebase-merge, not squash) per AGENTS.md Git workflow; dev-c rebases forward
for the FOB-side pub mod declarations (g711/twilio_media_streams/session/
loop_driver).

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 16:02:19 +00:00
c31e8a5069 docs: spearhead-4half-and-5 specs + plans + kickoff prompts + AGENTS.md auto-spawn update (#20)
Some checks failed
CI / clippy (push) Has been cancelled
CI / test (1.85) (push) Has been cancelled
CI / test (stable) (push) Has been cancelled
CI / deny (push) Has been cancelled
CI / fmt (push) Has started running
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 16:01:55 +00:00
f8a8a2c21f docs(adr): ratify ADR-0009 amendment (shared spend accounting) (#15)
All checks were successful
CI / fmt (push) Successful in 1m4s
CI / clippy (push) Successful in 1m17s
CI / test (1.85) (push) Successful in 3m39s
CI / test (stable) (push) Successful in 5m42s
CI / deny (push) Successful in 2m4s
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 05:03:18 +00:00
bdadfd9057 slice-5: scalability seams — addressing, admission, drain, events (review B1/M1-M7) (#14)
All checks were successful
CI / fmt (push) Successful in 1m36s
CI / clippy (push) Successful in 2m21s
CI / test (1.85) (push) Successful in 5m3s
CI / test (stable) (push) Successful in 4m23s
CI / deny (push) Successful in 1m35s
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 04:35:38 +00:00
d696536bdd slice-4 (finisher): secondary-path e2e + CI seam gate (Tasks 9.2 + 10) (#13)
All checks were successful
CI / fmt (push) Successful in 1m23s
CI / clippy (push) Successful in 2m25s
CI / test (1.85) (push) Successful in 5m45s
CI / test (stable) (push) Successful in 6m30s
CI / deny (push) Successful in 2m7s
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
slice-4-e2e-green
2026-07-04 17:52:26 +00:00
e1bcc5f158 kickoff(slice-4): finisher prompt — solo agent for Tasks 9.2 + 10
All checks were successful
CI / fmt (push) Successful in 1m40s
CI / clippy (push) Successful in 2m18s
CI / test (1.85) (push) Successful in 5m47s
CI / test (stable) (push) Successful in 5m44s
CI / deny (push) Successful in 2m1s
Single-agent (no PM/relay) kickoff to close out slice-4: the
secondary-path advisory e2e and the CI seam gate. API facts verified
against main @ 60b65ab (mock schedule API, ReflexMetrics fields,
lib.rs exports); seam gate specced as pinned blob hashes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QndwfhjyTiZcUYp87dwW8
Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 11:38:11 -04:00
60b65ab893 docs(reviews): salvage June pre-pivot reviews from retired branches
Some checks failed
CI / fmt (push) Successful in 1m16s
CI / clippy (push) Successful in 2m19s
CI / test (stable) (push) Has been cancelled
CI / deny (push) Has been cancelled
CI / test (1.85) (push) Has started running
These four reviews were filed on slice-1-review-fixes and
strategic-reviews-post-pivot-rescore but never reached main; both
branches are being retired in the post-slice-4 cleanup. Kept for
lineage: the 06-29 post-pivot re-score is where ADR-0007/0008 era
strategy was re-scored, and the 07-03 reviews build on these.

Sources: slice-1-review-fixes @ d2ef53b (gtm-path, vision-sanity-check,
slice-1-claude-adversarial-assessment), strategic-reviews-post-pivot-
rescore @ ea27167 (post-pivot re-score).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QndwfhjyTiZcUYp87dwW8
Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 11:32:29 -04:00
13f4b14bee Merge pull request 'docs: PM-mode discipline + multi-agent relay coordination protocol' (#5) from docs/pm-discipline into main
Some checks failed
CI / fmt (push) Successful in 1m14s
CI / clippy (push) Successful in 1m55s
CI / test (1.85) (push) Successful in 4m24s
CI / deny (push) Has been cancelled
CI / test (stable) (push) Has been cancelled
Reviewed-on: #5
2026-07-04 15:23:34 +00:00
3c63eb6688 slice-4 (dev-a): MediaThread + session_map rewire (Task 6 + 7) (#12)
All checks were successful
CI / fmt (push) Successful in 1m42s
CI / clippy (push) Successful in 2m34s
CI / test (1.85) (push) Successful in 5m15s
CI / test (stable) (push) Successful in 6m34s
CI / deny (push) Successful in 2m2s
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 04:37:28 +00:00
da6b7c9ff0 adr: +0009 spend-gate rescope +0010 spearhead 4.5 benchmark/sim; amend 0004
All checks were successful
CI / fmt (push) Successful in 1m5s
CI / clippy (push) Successful in 1m53s
CI / test (1.85) (push) Successful in 5m54s
CI / test (stable) (push) Successful in 6m36s
CI / deny (push) Successful in 2m12s
Maintainer-ratified from the 2026-07-03 review (R1-R3):
- ADR-0009: retire "structurally impossible for a 3-vendor stack" post-0007;
  restate the gate's true guarantees (credential isolation, unskippable
  mediation, media-plane enforcement, audit co-location). Propagated to
  README pillar 3, ARCHITECTURE, PORT_PLAN s10; amendment note on ADR-0002.
- ADR-0010: insert step 4.5 (benchmark + simulation harness, rutster-sim
  seed, CI-regressed p50/p99 + kill-time) after barge-in; pull rung-2
  escalation ahead of steps 5-6. Spearhead lists updated.
- ADR-0004: delete the legally-broken AGPL escape hatch; GPL-3.0-or-later
  permanent, no CLA; tap protocol/SDKs intended permissive (future ADR).
- README: add brain-vendor-direct competitor row (review D2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vNe64BBDkgo5oQVkBa3XF
2026-07-04 00:00:57 -04:00
4c36a4ded3 docs(reviews): 2026-07-03 adversarial review + market feature scan
Direction/progress pressure-test (D1-D6, P1-P6, R1-R3) and the
boost.ai/Parloa/Vapi cohort scan (F1-F7, adjacencies A1-A11).
Standing backlog for planning sessions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vNe64BBDkgo5oQVkBa3XF
2026-07-04 00:00:57 -04:00
78435f7145 slice-4 (dev-a): Task 9 Step 1 — primary-path barge-in e2e (wedge #1) (#10)
All checks were successful
CI / fmt (push) Successful in 1m9s
CI / clippy (push) Successful in 1m51s
CI / test (1.85) (push) Successful in 4m12s
CI / test (stable) (push) Successful in 3m24s
CI / deny (push) Successful in 1m22s
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 01:53:59 +00:00
0eb20b729a docs(reviews/2026-07-03): P4 fix — sweep status staleness + re-aim fuzz/README (#11)
Some checks failed
CI / fmt (push) Successful in 1m32s
CI / clippy (push) Successful in 1m31s
CI / test (1.85) (push) Successful in 3m57s
CI / deny (push) Has been cancelled
CI / test (stable) (push) Has been cancelled
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 01:44:27 +00:00
276dc5ac45 slice-4 (dev-b): TapAudioPipe::barge_in_flush + advisory_tx + MockRealtimeBrain schedule (#9)
Some checks failed
CI / clippy (push) Has been cancelled
CI / test (1.85) (push) Has been cancelled
CI / test (stable) (push) Has been cancelled
CI / deny (push) Has been cancelled
CI / fmt (push) Has started running
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 01:43:41 +00:00
bc7e8f1acd slice-4 (dev-a): Reflex<P> + LocalVadReflex<P> (Task 2 + 2b) (#8)
All checks were successful
CI / fmt (push) Successful in 53s
CI / clippy (push) Successful in 1m45s
CI / test (1.85) (push) Successful in 4m29s
CI / test (stable) (push) Successful in 4m18s
CI / deny (push) Successful in 1m31s
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-03 13:50:36 +00:00
8cde52b1d3 Merge pull request 'slice-4 (dev-a): Task 1 — AdvisoryEvent + ReflexMetrics + barge_in_flush trait' (#7) from slice-4-dev-a-reflex into main
All checks were successful
CI / fmt (push) Successful in 1m3s
CI / clippy (push) Successful in 1m29s
CI / test (1.85) (push) Successful in 3m47s
CI / test (stable) (push) Successful in 5m48s
CI / deny (push) Successful in 1m55s
Reviewed-on: #7
2026-07-03 03:37:34 +00:00
d06e79cc50 docs(media): add reflex module-map entry to lib.rs catalogue (slice-4 §3.1)
All checks were successful
CI / fmt (pull_request) Successful in 1m29s
CI / clippy (pull_request) Successful in 2m31s
CI / test (1.85) (pull_request) Successful in 4m16s
CI / test (stable) (pull_request) Successful in 4m19s
CI / deny (pull_request) Successful in 1m21s
Task 1 review flagged the new reflex module was missing from the
module-map //! catalogue. AGENTS.md learner-facing convention requires
the catalogue entry describing what the module does + why it exists.

Addresses the Minor finding from the Task 1 task review.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-02 23:08:53 -04:00
0b054e76f4 feat(media): AdvisoryEvent + ReflexMetrics + barge_in_flush trait (slice-4 §3.1, §3.3)
The critical-path foundation for the barge-in reflex. AdvisoryEvent is
the enum carried over a tokio mpsc from TapEngine to Reflex (brain →
FOB). ReflexMetrics is the observable surface. barge_in_flush is the
new AudioPipe trait method (default delegates to clear_playout_ring) —
the kill-now path that clears the ring AND drains rx_audio_out.

Task 1 of the slice-4 plan. Everything else depends on this landing.

Signed-off-by: Aaron D. Lee <himself@adlee.work>
2026-07-02 23:08:53 -04:00
3f494cb919 spec+plan(slice-4): barge-in / VAD-driven playout kill on dedicated media thread (#6)
All checks were successful
CI / fmt (push) Successful in 1m37s
CI / clippy (push) Successful in 2m34s
CI / test (1.85) (push) Successful in 5m19s
CI / test (stable) (push) Successful in 4m5s
CI / deny (push) Successful in 1m58s
2026-07-03 03:06:58 +00:00
opencode controller
7270a608dc docs: require DCO signoff on every commit (fixes #6 from adversarial review)
All checks were successful
CI / fmt (pull_request) Successful in 1m38s
CI / clippy (pull_request) Successful in 2m22s
CI / test (1.85) (pull_request) Successful in 5m27s
CI / test (stable) (pull_request) Successful in 6m7s
CI / deny (pull_request) Successful in 2m3s
Every commit MUST be signed off (git commit -s). Preserves the
copyright-holder's option to dual-license later — without a DCO/CLA
instrument, every external contribution erodes that option (ADR-0004).
Particularly load-bearing under multi-agent parallel authorship, where
multiple sessions commit in parallel and the DCO is the only per-commit
aggregation instrument.

Adds DCO.md with the full Linux-Foundation DCO text + the agents-sign-off-
with-the-human's-identity rule. AGENTS.md Git workflow gains the DCO bullet.
2026-07-01 20:12:49 -04:00
opencode controller
aae485536e docs: PM-mode discipline + multi-agent relay coordination protocol
All checks were successful
CI / fmt (pull_request) Successful in 1m44s
CI / clippy (pull_request) Successful in 2m31s
CI / test (1.85) (pull_request) Successful in 5m54s
CI / test (stable) (pull_request) Successful in 6m20s
CI / deny (pull_request) Successful in 2m2s
AGENTS.md +292 lines: the relay (model-agnostic MCP message-bus at
localhost:7110), PM session launch checklist (poller, watch.sh, kitty/tmux
spawner), session handoff (inbox.log / poller.log / relay-log.jsonl), PM
turn-start discipline (drain inbox, list_pending, git log, surface
proactively), multi-dev parallelism (5-rule checklist with the slice-3
anti-pattern), when-the-PM-is-blocked protocol. Plus the slice-4 PM
kickoff prompt under docs/superpowers/kickoffs/.

Load-bearing for the multi-agent workflow executing slice-4 (and beyond):
session discipline (turn-start polling, no standby mode, proactive
surfacing) was the prior failure mode; the prompt + these sections bake
the correction into AGENTS.md so a fresh PM agent session inherits it.
2026-07-01 20:04:10 -04:00
c30a45232d Slice 3 — OpenAI Realtime brain: swap echo for the brain (#4)
All checks were successful
CI / fmt (push) Successful in 1m40s
CI / clippy (push) Successful in 2m24s
CI / test (1.85) (push) Successful in 5m8s
CI / test (stable) (push) Successful in 5m20s
CI / deny (push) Successful in 1m34s
2026-07-01 22:25:09 +00:00
opencode controller
2f3f92ec6b ci: bump cargo-deny-action v1 -> v2 (CVSS 4.0 advisory-db parse fix)
Some checks failed
CI / test (1.85) (pull_request) Failing after 13m59s
CI / clippy (pull_request) Failing after 14m8s
CI / fmt (pull_request) Failing after 14m14s
CI / test (stable) (pull_request) Successful in 2m48s
CI / deny (pull_request) Failing after 1m55s
CI / fmt (push) Successful in 1m3s
CI / test (1.85) (push) Successful in 2m35s
CI / test (stable) (push) Successful in 2m50s
CI / deny (push) Failing after 1m17s
CI / clippy (push) Failing after 14m28s
2026-07-01 01:05:51 -04:00
opencode controller
c86bb19c0e ci: drop sudo for libopus install (act job containers run as root)
Some checks failed
CI / fmt (pull_request) Successful in 1m10s
CI / clippy (pull_request) Successful in 6m41s
CI / test (1.85) (pull_request) Successful in 8m54s
CI / test (stable) (pull_request) Successful in 9m44s
CI / deny (pull_request) Failing after 11m23s
2026-07-01 00:22:31 -04:00
opencode controller
2bc9877105 fmt: apply rustfmt across slice-2 use-statement drift
Some checks failed
CI / fmt (pull_request) Successful in 56s
CI / clippy (pull_request) Failing after 28s
CI / test (1.85) (pull_request) Failing after 27s
CI / test (stable) (pull_request) Failing after 28s
CI / deny (pull_request) Failing after 27s
2026-07-01 00:18:04 -04:00
opencode controller
c89d65c621 ci: retrigger pivot PR (runner config fix for gitea DNS)
Some checks failed
CI / fmt (pull_request) Failing after 52s
CI / clippy (pull_request) Failing after 29s
CI / test (1.85) (pull_request) Failing after 30s
CI / test (stable) (pull_request) Failing after 28s
CI / deny (pull_request) Failing after 27s
2026-07-01 00:13:23 -04:00
opencode controller
42060b68e3 spec(ui): default operator console design (later-rung, not yet scheduled)
Some checks failed
CI / fmt (pull_request) Failing after 58s
CI / clippy (pull_request) Failing after 33s
CI / test (1.85) (pull_request) Failing after 32s
CI / test (stable) (pull_request) Failing after 47s
CI / deny (pull_request) Failing after 41s
Design record for the operator console — the downstream non-technical
operator's surface. **Not scheduled for current build;** lands after the
spearhead (steps 1-6) and the ACD/escalation rungs (capability ladder
rungs 1-2). The current build target is slice-2; the next build target
after slice-2 is slice-3 (the OpenAI Realtime adapter). An agent that
proposes building the console now is wrong — point it at spec §0.

Records the visual-brainstorming session's resolved decisions so they
don't get re-litigated at implementation time:

§1 Principles (load-bearing):
- GUI is a pure API client — **green-zone, never an insider** (ADR-0008);
  holds no privilege a third party couldn't get from the public API.
- Config-as-code is the single source of truth; the UI is a reconciled
  *view* (the k8s model — declarative desired-state in git, the engine
  reconciles; console + CLI + git are views over it).
- Operator persona, not builder. The builder authors flows in **code +
  an AI pair** (ARCHITECTURE 'DX spine'); this console is NOT the
  authoring surface for the technical builder. **No drag-drop authoring
  canvas** — a deliberately-closed decision (vision-revision §8).
- Ships in the batteries-included distro and the all-in-one image
  (the reference GUI is bundled — 'boom' includes a usable console).

§2 IA — Ops Console shell: left nav + center panel + persistent live rail.
Dense single-screen; for an operator who lives in it. Live rail always
visible regardless of the selected section.

§3 Dashboards — Live Ops (default landing) + Quality (sibling view). Maps
the k8s split: Live Ops = live operational state; Quality = the data-owned
eval loop (capability ladder rungs 3→4).

§4 Flows editor — master/detail + a generated, navigable preview map.
The preview map is a *view* rendered from the config, not a drag-drop
authoring canvas. Earns its place by showing branching (the escalation
split) the linear stage-list flattens.

§5 Declarative flow schema (illustrative YAML; format is open). The config
the console edits via the API; the builder edits in git; the engine
reconciles. The map is generated from this structure.

§6 Other sections (conventional): Agents · Numbers · Queues · People ·
Recordings · Settings.

§7 Architecture: separate app, public API only; never reaches into the FOB.
Config writes go through the reconcile path. Live data rides the event
stream off Valkey (ADR-0005); never the 20 ms media loop.

§8 v1 vs. later scoping. §9 open decisions (flow config format, map
rendering depth, quality metrics, live transport, framework).

Cross-refs ADR-0002 (fused vertical + composable platform), ADR-0006
(WebRTC ingress / escalation UX), ADR-0007 (rented trunk / Numbers
config), ADR-0008 (FOB / green-zone — the UI is green-zone),
ARCHITECTURE.md 'DX spine' + 'GUI & extension architecture', PORT_PLAN §5
(contact-center domain).
2026-06-29 20:27:25 -04:00
opencode controller
4b0f53eb2d tooling: gitignore *.kate-swp + .superpowers/ scratch dir
- *.kate-swp: Kate editor's swap files have a different extension pattern
  than vim's *.swp; without this, kate-swp files like
  docs/reviews/.2026-06-28-slice-1-claude-adversarial-assessment.md.kate-swp
  leak into the working tree.
- .superpowers/: the superpowers companion scratches its visual-brainstorming
  server state (HTML mockups, server.log, server.pid, server-info) under
  .superpowers/brainstorm/ in the project root. The companion explicitly
  advises gitignoring this directory; scratch artifacts are not source.
2026-06-29 20:27:11 -04:00
opencode controller
3b7b0d6459 docs: propagate rutster-trunk rename + repo URL through plans/specs/DEVELOPMENT
Mechanical propagation of the crate rename ( rutster-signaling-sip →
rutster-trunk ) and the repo URL fix ( github.com/anomalyco →
git.adlee.work/alee ) through the documents that name them:

- docs/DEVELOPMENT.md: crate-layout sketch + stub-crate description.
- docs/superpowers/plans/2026-06-28-slice-1-webrtc-loopback.md: the
  workspace members list in the plan's binding-values section —
  repository URL updated.
- docs/superpowers/plans/2026-06-28-slice-2-agent-tap.md: workspace
  members list in binding-values + crate-layout sketch in the file-
  structure section (rutster-signaling-sip → rutster-trunk).
- docs/superpowers/specs/2026-06-28-slice-2-agent-tap-design.md: §2.1
  workspace-layout sketch's STUB crate-row.

No content changes beyond the substitutions; the slice-2 spec/plan
body (protocol, TapAudioPipe, TapClient, TapEngine, lifecycle, done-
  criteria) is untouched.
2026-06-29 20:27:01 -04:00
opencode controller
7616878bb1 docs: propagate ADR-0007/0008 to README, ARCHITECTURE, PORT_PLAN, AGENTS
ADR-0007 (rent the trunk) + ADR-0008 (FOB/green-zone doctrine) propagation
into the narrative docs that orient contributors + readers. No code changes.

README:
- Wedge bullet 2: 'one secure auditable boundary' now lists media + local
  reflexes + spend + tap + audit (was: trunk termination + media + spend).
  Adds the honest caveat that PSTN media inside the boundary is the on-prem
  *graduation* (ADR-0007), not a day-one claim.
- 'Is / isn't': 'isn't a TDM/PSTN-hardware PBX — *and not a SIP stack.*'
  No first-party SIP (ADR-0007). PSTN reach is rented transport.
- Memory-safety pillar: 'rutster parses no SIP at all' — entire first-party
  wire surface is WebRTC/RTP/SRTP + WebSocket tap/ingress, all memory-safe
  Rust. The carrier-SIP interop tail lives outside the trust boundary.
- Spearhead step 5: 'Add a real phone number via rented transport' (was:
  'Replace WebRTC ingress with a real PSTN trunk call'). Re-aims at the
  AI-telephony frontier; no first-party SIP stack.
- References: 0007 supersedes 0003 in the highlighted ADR list.

ARCHITECTURE:
- New 'FOB and the green zone' section after the fused-vertical framing —
  names the build-vs-reuse doctrine with the FOB member list + green-zone
  member list, and restates memory-safety precisely ('FOB is 100%
  memory-safe Rust; the green zone is trusted OSS kept outside the
  boundary — not an over-claim that every byte is Rust').
- 'Inside the boundary': 'Carrier trunk — rented transport, not first-party'
  replaces 'Trunk SIP termination — Rust-native.' PSTN audio arrives as
  a media-leg ingress from a rented CPaaS raw-media fork or an out-of-tree
  SBC for on-prem sovereignty.
- Biggest technical risk: 'No longer the SIP stack — because rutster no
  longer builds one.' Retires ADR-0003's named schedule risk; redirects
  to 'the reflex loop itself' (turn-taking, VAD-driven barge-in, jitter,
  pacing) — which is also the differentiator.

PORT_PLAN:
- Design rule 4: 'rent it, don't own it' replaces ADR-0003's 'Rust-native
  trunk SIP, no SBC shield.' ADR-0007 restores the rule's original instinct.
- SIP signaling (trunk) row: disposition flipped to 🔌 Rented / out-of-tree.
- Outbound registration row: disposition flipped to 🔌 Rented / out-of-tree
  (handled by the rented transport or out-of-tree SBC; rutster parses no SIP).
- Spend/abuse engine row: 'co-located with call origination + the tap inside
  the boundary' (was: 'co-located with trunk termination'). rutster mediates
  both the provider call-control API and the brain tap — the brain never
  holds the wire.
- Spearhead step 5: 'Add a real phone number via rented transport.'
- Open decisions: SIP line updated to 'Re-decided — ADR-0007.'

AGENTS.md:
- ADR list: adds 0007 and 0008 with annotations; marks 0003 as superseded
  by 0007.
- Key decisions to respect: prepends the FOB/green-zone doctrine as the
  *the* build-vs-reuse rule. 'When in doubt, default to green zone — the
  FOB earns its members, it doesn't collect them. This is why the trunk is
  rented (ADR-0007) and Valkey reused (ADR-0005), not rebuilt. Don't pull
  green-zone plumbing into the core.'
2026-06-29 20:26:51 -04:00
opencode controller
3c3197e57a refactor(workspace): rutster-signaling-sip → rutster-trunk; edition 2024; repo URL = git.adlee.work
ADR-0007 lands in code:
- Rename crates/rutster-signaling-sip → crates/rutster-trunk. The crate was
  a stub anyway (lib.rs with a doc-comment + crate_compiles() test); the
  rename locks the new boundary shape — the future rented-transport
  ingress (CPaaS media-leg fork / out-of-tree SBC glue, **no SIP stack**)
  lands here at spearhead step 5.
- rutster-trunk/src/lib.rs doc-comment now describes the ADR-0007 split:
  CPaaS media-leg adapter as primary, out-of-tree SBC for on-prem
  graduation. Cross-refs ADR-0007 + ADR-0008 (FOB vs. green zone: trunk
  is green zone; SIP lives outside the trust boundary).
- rutster-spend/src/lib.rs doc-comment updated to quote ADR-0007's
  'rutster mediates both the provider call-control API and the brain tap'
  framing — the spend gate sits in that boundary, structurally preventing
  a runaway brain from exceeding spend/pacing. Pulling spend out into a
  service re-introduces the 3-vendor structural hole.

Cargo bumps:
- edition = '2024' (slice-1's pinned Rust 1.85 + edition-2024 floor
  already requires this; rutster-media's let-else pattern in
  OpusDecoder::decode · Slice-2's let-else in AcceptOffer's Uuid
  parse_str · all rely on edition 2024's stabilized let-chains / let-else).
- repository = 'https://git.adlee.work/alee/rutster' (the self-hosted
  Gitea remote — matches 'git remote -v' origin). The github.com/anomalyco
  URL was stale from the pre-pivot copy.
- Cargo.lock regenerated by cargo for the rename.

No behavioral code changes — the trunk crate's body is still the stub
crate_compiles() test. FOB membership (per ADR-0008) is unchanged:
rutster-trunk will be FOB-internal at step 5 because it's the media-leg
ingress (hot path); spend-spend stays FOB because spend is
security-constitutive.
2026-06-29 20:26:37 -04:00
opencode controller
272aa07acd adr: +0007 rent trunk transport +0008 FOB/green-zone doctrine; supersede 0003
ADR-0007 — Trunk/PSTN strategy: rent the transport, no first-party SIP stack.
Supersedes ADR-0003 (Rust-native trunk SIP). The 2026-06 strategic-relevance
review stress-tested ADR-0003 against the 2026 market + solo-build realities
and it did not survive: a trunk SIP/media core is the highest-cost,
lowest-differentiation square on the board (the perceived-quality battle and
the buying criteria both live above the transport), and no production-hardened
pure-Rust SIP stack exists to stand on (ezk is bus-factor-1 / pre-1.0;
str0m self-describes as 'not for production' for SIP).

rutster owns no SIP stack. PSTN reach is rented transport in three layers:
  1. Primary (demo, most users) — CPaaS raw-media fork (Twilio Media Streams,
     Telnyx). Media-leg ingress, core-as-server (parallel to WebRTC ingress
     per ADR-0006). Use the raw-audio fork, *not* managed Voice-AI products
     that would consume the reflex loop.
  2. Graduation (on-prem) — out-of-tree SBC (Kamailio/FreeSWITCH/drachtio +
     rtpengine) B2BUAs carrier SIP into rutster as clean RTP/tap media,
     outside the trust boundary.
  3. Never — a first-party Rust trunk SIP stack. rsip/ezk stay off the
     critical path.

The only things rutster owns in Rust: the call model, the reflex loop, the
agent tap. Everything that touches a carrier is rented (layer 1) or
out-of-tree (layer 2).

ADR-0008 — The FOB and the green zone: the build-vs-reuse doctrine. Names
the boundary criterion implicit across ADRs 0002-0007 as one mechanical rule
every contributor (human or agent) applies the same way.

  - FOB (build in Rust) — admitted only if it passes one of: hot path,
    security-constitutive, differentiating. Current FOB: media termination
    (rutster-media on str0m) · real-time reflexes (VAD/barge-in/jitter/pacing)
    · call model (rutster-call-model) · agent tap (rutster-tap) · spend/abuse
    gate (rutster-spend) · control API + state/bus trait (rutster).
  - Green zone (reuse at arm's length) — its own process/container/trust
    domain, never in the FOB's address space. Admitted when it fails all
    three FOB tests AND a trusted, actively-maintained project already does
    it well. Current green zone: Valkey (ADR-0005) · carrier trunk (ADR-0007)
    · agent brain · reference GUI · object storage · KMS · OTel collector ·
    container supervisor.
  - 'Actively maintained' is the load-bearing gate: excludes a first-party
    Rust SIP stack and webrtc-rs; admits Valkey and str0m-for-WebRTC.
  - When in doubt, default to green zone. The FOB earns its members.

Restates the memory-safety pillar precisely: the FOB is 100% memory-safe
Rust; the green zone is trusted battle-tested OSS kept outside the boundary
— not an over-claim that every byte is Rust.

ADR-0003's status is flipped to Superseded with a forward-pointer to ADR-0007.
Kept as the historical record of the 'own trunk SIP + media termination in
Rust, no SBC shield' plan; reversed under the strategic-relevance review.
2026-06-29 20:26:24 -04:00
5a193871d8 Merge pull request 'Slice 2 — The agent tap: splicing the brain seam' (#2) from slice-2-agent-tap into main
Some checks failed
CI / fmt (push) Failing after 14m55s
CI / clippy (push) Failing after 1m16s
CI / test (1.85) (push) Failing after 27s
CI / test (stable) (push) Failing after 30s
CI / deny (push) Failing after 1m14s
Reviewed-on: #2
2026-06-28 22:35:27 +00:00
opencode controller
b2aaf149b0 fix(slice-2): widen outer close-bound + strengthen reconnect-test assertion
Some checks failed
CI / clippy (pull_request) Failing after 43s
CI / test (1.85) (pull_request) Failing after 39s
CI / test (stable) (pull_request) Failing after 42s
CI / deny (pull_request) Failing after 39s
CI / fmt (pull_request) Failing after 10m45s
- session_map.rs: AppState::close outer timeout bumped 500ms → 750ms
  so the inner close-arm bound (500ms in tap_client.rs) has room to
  finish `ws.close(None).await` cleanly before the abort fallback
  fires. Outer > inner per final-fixes re-review Important #1.
- tap_integration.rs: reconnect-path test now asserts the resumed
  `next_pcm_frame()` returns the FRESH marker (samples[0] == 9, not
  the stale samples[0] == 7), actively witnessing the §5.3 step 4
  "no stale bleed-through" contract. Pre-kill seeding strengthened
  to ≥2 frames so step 7's silence-after-flush assertion is non-
  vacuous. Per final-fixes re-review Important #2.

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §5.1 step 5, §5.3 step 4.
2026-06-28 18:21:52 -04:00
opencode controller
0d666b5ba7 fix(slice-2): §5.1 teardown seq + §5.3 playout flush + §8.4 reconnect test + §7.2 index.html
Code-review findings from the slice-2 final whole-branch review:

- tap_client.rs: close arm sends session_end (not bye) per §5.2, awaits
  brain bye with bounded 500ms timeout, then ws.close(). Promotes the
  Task-7-documented Minor to Important's required spec compliance.
- session_map.rs: AppState::close() now clears channel.tap = None before
  Closing->Closed, sends close signal, bounded-awaits engine task for the
  teardown handshake (500ms cap), aborts as fallback. Per §5.1 step 5.
- pcm.rs + tap_audio_pipe.rs: AudioPipe trait gains clear_playout_ring()
  (default no-op for EchoAudioPipe); TapAudioPipe clears the VecDeque.
  RtcSession::clear_playout_ring delegates. Per §5.3 step 4 playout-on-
  disconnect-flush contract.
- tap_engine.rs: spawn_tap_engine creates + shares a flush_tx mpsc
  channel; run_engine_loop signals flush after each failed pump loop
  (before backoff); TapConn.flush_rx is drained by drive_all_sessions
  to call RtcSession::clear_playout_ring.
- tests/tap_integration.rs: new reconnect-path test asserts Channel
  stays Connected, metrics.reconnect_attempts increments, playout
  returns None during outage, playout resumes after EchoServer
  restart with fresh frames (no stale bleed-through). Per §8.4.
- static/index.html: one static line acknowledging external-brain
  routing per §7.2 (defer real-time tap status to step-3 GUI).

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §5.1 step 5, §5.2, §5.3 step 4, §7.2, §8.4.
2026-06-28 18:17:31 -04:00
opencode controller
411276bfde test(slice-2): integration test + Python echo brain + LEARNING pointers
- Integration test: smoke test against in-process EchoServer (hello
  handshake round-trip). Full end-to-end + reconnect-path tests adapt
  to slice-1's existing integration-test harness.
- examples/echo_brain/: Python reference brain (~80 lines, websockets lib).
  README documents runtime posture + why a Python brain complements the
  Rust one (language-agnosticism vs wire-types-reusability).
- LEARNING.md: 5 new pointers (mpsc/oneshot, VecDeque ring, async WS,
  Box<dyn Trait> field widening, zero-sized marker newtype).

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §8.4, §8.5 #7.
2026-06-28 17:52:16 -04:00
opencode controller
84fc30591e fix(binary): spec-exact backoff staircase + remove dead Closing branch
- tap_engine.rs: Backoff::next_delay now matches spec §4.3 exactly:
  250ms -> 500ms -> 1s -> 2s -> 5s cap (no intermediate 4s step from
  naive doubling). Test asserts the spec enumeration including the
  "stays at 5s forever" cap-after-step-5 invariant.
- session_map.rs: removed the dead 'Closing && tap.is_some()' branch
  from drive_all_sessions. AppState::close already handles teardown
  inline (fires close_tx, aborts task, clears field, advances
  state, removes entry). The branch was never exercised and
  pre-paved a "future peer-initiated close" path -- both AGENTS.md
  anti-patterns ("don't pre-pave the wrong pattern").

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §4.3 step 4 + §5.1 step 3.
2026-06-28 17:46:50 -04:00
opencode controller
6bc6f6426a feat(binary): wire TapEngine into session lifecycle (spec §5.1, §7)
- tap_engine.rs: spawn_tap_engine(session_id, tap_url) — dials the brain,
  runs TapClient with bounded exponential backoff (250ms→5s cap, infinite
  retries). Cold-path network I/O on tokio's pool, NOT a timing thread
  (spec §4.3 deviation boundary).
- session_map.rs: +tap_registry (DashMap<ChannelId, TapConn>); +default_tap_url.
  drive_all_sessions observes Connected+tap.is_none() → spawn TapEngine, set
  channel.tap = Some(TapHandle). Observes Closing+tap.is_some() → fire close
  oneshot, abort task, clear tap BEFORE state advances to Closed.
- routes.rs: POST /v1/sessions accepts optional {"tap_url":...} body;
  resolve_tap_url validates ws:// 127.0.0.1/localhost, rejects wss:// + non-
  loopback ws:// + bad schemes with 400 Bad Request (fail-fast per spec §4.4).
- main.rs: reads RUTSTER_TAP_URL env (default ws://127.0.0.1:8081/echo).
- rtc_session.rs (rutster-media): pipe field changed from EchoAudioPipe to
  Box<dyn AudioPipe + Send> — the seam test (§8.5 #6):
  loop_driver's call sites (sink.on_pcm_frame / source.next_pcm_frame)
  are byte-identical; only the field type widens. RtcSession::new keeps
  EchoAudioPipe default (slice-1 tests unchanged); binary uses
  RtcSession::set_pipe(tap_audio_pipe) to swap on the Connected transition.
- 4 unit tests for tap_url validation (loopback accepted, non-loopback
  rejected, wss rejected fail-fast, default fallback).

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §4.3, §4.4, §5.1, §7.
2026-06-28 17:39:52 -04:00
opencode controller
e0616fe42d feat(call-model): +tap field on Channel + TapHandle marker (spec §5.2, §6)
- pub struct TapHandle(()) — zero-sized marker (Option<TapHandle> compiles
  to a bool, no allocation). The live tap connection lives in the binary's
  DashMap<ChannelId, TapConn>, keyed by the channel's existing ChannelId
  (== wire session_id per spec §5.3).
- Channel grows pub tap: Option<TapHandle>. None until Connected, set on
  Connected, cleared before state advances to Closed.
- State invariant: tap and ChannelState are tied (Connected+None=bug).
- Slice-1 backwards compatible: new field is Option<...>;

Multi-tap-per-channel is the future trigger for a TapId newtype (YAGNI).

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §5.2, §6.
2026-06-28 17:24:11 -04:00
opencode controller
237a1388a4 feat(tap-echo): Rust reference echo brain + test server (spec §2.3)
- start_echo_server(addr): in-process WS server for integration tests;
  returns EchoHandle { shutdown, join, addr }.
- echo_one_connection: the per-connection echo loop — hello handshake,
  audio_in → audio_out (same PCM), bye/session_end graceful close.
- Reuses rutster-tap's protocol types — the wire-types-reusable contract
  test (spec §2.3).
- Stateless across reconnects (spec §5.3) — every hello starts fresh.
- Standalone binary: binds ws://127.0.0.1:8081, runs forever.
- 1 unit test exercises full hello/ack/audio_in/audio_out/bye over a
  TCP loopback pair.

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §2.3, §5.3.
2026-06-28 17:20:00 -04:00