Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions .machine_readable/6a2/ECOSYSTEM.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,32 @@ last-updated = "2026-04-11"

[project]
name = "burble"
purpose = "" # TODO: describe project purpose
role = "" # TODO: describe project role # e.g. ffi-infrastructure, cli-tool, library, service
purpose = "Self-hostable voice-first communications platform with P2P Claude-to-Claude AI data channel"
role = "service"

[position-in-ecosystem]
tier = "infrastructure" # 1 | 2 | infrastructure
tier = "1"

[related-projects]
# relationship types: sibling-standard, dependency, dependent, inspiration, potential-consumer
# - { name = "language-bridges", relationship = "sibling-standard" }
# - { name = "hypatia", relationship = "potential-consumer" }
projects = [
{ name = "VeriSimDB", relationship = "dependency", note = "Sole data store for users, tokens, room config, provenance" },
{ name = "proven", relationship = "dependency", note = "Formally verified crypto, password, UUID, email, path safety bridge" },
{ name = "standards/lol", relationship = "dependency", note = "Multilingual corpus for i18n support" },
{ name = "IDApTIK", relationship = "dependent", note = "Asymmetric co-op game consuming Burble voice bridge (VoiceBridge, spatial audio)" },
{ name = "PanLL", relationship = "dependent", note = "Panel workspace consuming BurbleEngine, PanelBus events, VoiceTag hooks" },
{ name = "game-server-admin", relationship = "dependent", note = "Consumes burble plexus/nexus for multi-server voice coordination" },
{ name = "gossamer", relationship = "sibling-standard", note = "Both use Zig FFI + Idris2 ABI pattern; gossamer provides webview runtime" },
{ name = "typed-wasm", relationship = "potential-consumer", note = "Could type-check Burble's WASM SNIF modules against typed-wasm's level system" },
{ name = "ephapax", relationship = "sibling-standard", note = "Client desktop shell (.eph files) uses Ephapax's dyadic type system" },
{ name = "Avow", relationship = "dependency", note = "Consent attestation framework — trust chain for room join/leave" },
{ name = "Vext", relationship = "dependency", note = "Hash chain + capability subsumption for extension sandboxing" },
]

[integration-points]
# External systems this project connects to
# - { system = "gitbot-fleet", direction = "outbound", protocol = "repository_dispatch" }
points = [
{ system = "signaling/relay.js", direction = "internal", protocol = "HTTP PUT/GET, ephemeral SDP, 60s TTL" },
{ system = "burble-ai-bridge.js", direction = "internal", protocol = "HTTP REST + WebSocket, port 6474/6475" },
{ system = "VeriSimDB", direction = "outbound", protocol = "HTTP REST + ClickHouse" },
{ system = "gitbot-fleet", direction = "outbound", protocol = "repository_dispatch (CI)" },
{ system = "hypatia", direction = "inbound", protocol = "Hypatia scan rules via .hypatia/ config" },
]
22 changes: 17 additions & 5 deletions .machine_readable/6a2/META.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@ version = "0.1.0"
last-updated = "2026-04-11"

[project-info]
type = "library" # TODO: update type (library|binary|service|website|monorepo) # library | binary | monorepo | service | website
languages = [] # e.g. ["rust", "zig", "idris2"]
type = "service"
languages = ["elixir", "zig", "idris2", "javascript", "rescript", "ephapax"]
languages-target = ["elixir", "zig", "idris2", "javascript", "affinescript", "ephapax"]
license = "PMPL-1.0-or-later"
author = "Jonathan D.A. Jewell (hyperpolymath)"

[architecture-decisions]
# ADR format: status = proposed | accepted | deprecated | superseded | rejected
# - { id = "ADR-001", title = "Use Zig for FFI", status = "accepted", date = "2026-02-14" }
decisions = [
{ id = "ADR-001", title = "Elixir/Phoenix for control plane — OTP fault isolation", status = "accepted", date = "2025-12-01" },
{ id = "ADR-002", title = "Zig for FFI coprocessor — SIMD audio + zero-cost C ABI", status = "accepted", date = "2025-12-15" },
{ id = "ADR-003", title = "Idris2 for ABI proofs — dependent types prove correctness", status = "accepted", date = "2026-01-10" },
{ id = "ADR-004", title = "WebRTC for media — browser-native, E2EE Insertable Streams", status = "accepted", date = "2026-01-20" },
{ id = "ADR-005", title = "P2P mode first-class — zero-server voice + AI data channel", status = "accepted", date = "2026-02-01" },
{ id = "ADR-006", title = "V-lang banned estate-wide — replaced by Zig FFI", status = "accepted", date = "2026-04-10" },
{ id = "ADR-007", title = "ReScript → AffineScript client migration", status = "accepted", date = "2026-04-16" },
{ id = "ADR-008", title = "Server-side Opus not implemented — SFU-opaque E2EE model", status = "accepted", date = "2026-04-16" },
{ id = "ADR-009", title = "P2P AI bridge is primary Claude-to-Claude channel (not server-side Burble.LLM)", status = "accepted", date = "2026-04-16" },
]

[development-practices]
build-tool = "just"
Expand Down Expand Up @@ -50,4 +60,6 @@ drift-risk-example = "single exception broadening into policy violation (e.g. Re
effects-evidence = "benchmark execution/results and maintainer status dialogue/review"

[design-rationale]
# Key design decisions and their reasoning
sfu-not-mcu = "Server forwards encrypted RTP opaquely (SFU), never decodes audio (MCU). This preserves E2EE: clients Opus-encode in the browser; server cannot eavesdrop. Trade-off: no server-side mixing, transcoding, or recording of decoded audio."
p2p-first = "P2P mode (copy-paste signaling or relay at signaling/relay.js on port 6476) requires zero infrastructure. Server mode adds rooms, permissions, presence, recording — but P2P is the floor, not the ceiling."
ai-bridge-architecture = "Claude-to-Claude messages travel: curl POST /send → Deno bridge (:6474 HTTP) → bridge WS (:6475) → p2p-voice.html → WebRTC DataChannel → remote page → remote bridge WS → remote bridge queue → remote curl GET /recv. This keeps the AI channel inside the same encrypted WebRTC session as voice."
17 changes: 12 additions & 5 deletions .machine_readable/6a2/NEUROSYM.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ scan-depth = "standard" # quick | standard | deep
report-format = "logtalk"

[symbolic-rules]
# Custom symbolic rules for this project
# - { name = "no-unsafe-ffi", pattern = "believe_me|unsafeCoerce", severity = "critical" }
rules = [
{ name = "no-believe-me", pattern = "believe_me", severity = "critical", scope = "*.idr" },
{ name = "no-assert-total", pattern = "assert_total", severity = "critical", scope = "*.idr" },
{ name = "no-v-lang-files", pattern = "\\.v$", severity = "critical", scope = "**", note = "V-lang banned 2026-04-16" },
{ name = "no-new-rescript", pattern = "\\.res$|\\.resi$", severity = "warning", scope = "**", note = "ReScript migration to AffineScript in progress; new .res files banned" },
{ name = "no-opus-pretence", pattern = "opus_encode|opus_decode", severity = "warning", scope = "*.ex", note = "Backend.audio_encode is PCM framing, not Opus. Use opus_transcode/4 explicitly." },
{ name = "stub-nif-returns-error", pattern = "simulated response", severity = "critical", scope = "*.ex", note = "Burble.LLM.process_query must not return simulated strings in production" },
{ name = "no-system-time-outside-ptp", pattern = "System\\.system_time", severity = "warning", scope = "*.ex", note = "Should go through Burble.Timing.PTP.now/0 for clock-source awareness" },
{ name = "tflite-model-path-validated", pattern = "nif_neural_init_model", severity = "warning", scope = "*.ex", note = "Model path not validated; model file not in priv/" },
]

[neural-config]
# Neural pattern detection settings
# confidence-threshold = 0.85
# model = "hypatia-v2"
confidence-threshold = 0.85
model = "hypatia-v2"
58 changes: 40 additions & 18 deletions .machine_readable/6a2/PLAYBOOK.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,49 @@ version = "0.1.0"
last-updated = "2026-04-11"

[deployment]
# method = "gitops" # gitops | manual | ci-triggered
# target = "container" # container | binary | library | wasm
method = "manual"
target = "container"
container-runtime = "podman"
container-base = "Chainguard"
compose-file = "containers/compose.toml"
notes = "P2P mode needs no deployment — just open p2p-voice.html + run bridge. Server mode uses `just up` (podman-compose) or `just server` (dev)."

[p2p-quickstart]
steps = [
"1. deno run --allow-net --allow-env client/web/burble-ai-bridge.js &",
"2. Open client/web/p2p-voice.html in browser",
"3. Exchange room codes or use signaling relay (just relay on port 6476)",
"4. Verify green 'bridge online' dot in AI Channel card",
"5. Test: curl http://localhost:6474/status",
]

[incident-response]
# 1. Check .machine_readable/STATE.a2ml for current status
# 2. Review recent commits and CI results
# 3. Run `just validate` to check compliance
# 4. Run `just security` to audit for vulnerabilities
steps = [
"1. Check .machine_readable/6a2/STATE.a2ml for current status + known blockers",
"2. Review recent commits and CI results (git log -10, gh run list)",
"3. Run `just test` (Elixir + Zig tests)",
"4. Run `just scan` (panic-attacker static analysis)",
"5. For AI bridge issues: check browser console for [Burble AI] errors",
"6. For audio issues: check server logs for [PTP] [MediaEngine] [Pipeline] prefixes",
]

[release-process]
# 1. Update version in STATE.a2ml, META.a2ml, Justfile
# 2. Run `just release-preflight` (validate + quality + security + maint-hard-pass)
# 3. Optional local permission hardening: `just perms-snapshot && just perms-lock`
# 4. Tag and push
# 5. Restore local permissions if needed: `just perms-restore`
# 6. Run `just container-push` if applicable
steps = [
"1. Update version in STATE.a2ml, META.a2ml, Justfile",
"2. Run `just test` — all green",
"3. Run `deno test --allow-net --allow-env --allow-run client/web/tests/` — AI bridge tests green",
"4. Run `just scan` — panic-attacker clean",
"5. Update CHANGELOG.md",
"6. Tag: git tag -a v<version> -m 'Release <version>'",
"7. Push: git push origin main --tags",
"8. Container: `just container-build && just container-push` if applicable",
]

[maintenance-operations]
# Baseline audit:
# just maint-audit
# Hard release gate:
# just maint-hard-pass
# Permission audit:
# just perms-audit
operations = [
{ name = "full-test", command = "just test", frequency = "every commit" },
{ name = "ai-bridge-test", command = "deno test --allow-net --allow-env --allow-run client/web/tests/", frequency = "every commit touching client/web/" },
{ name = "static-scan", command = "just scan", frequency = "pre-release" },
{ name = "proof-check", command = "cd src && idris2 --check ABI.idr", frequency = "when src/Burble/ABI/*.idr changes" },
{ name = "zig-ffi-test", command = "just test-ffi", frequency = "when ffi/zig/ changes" },
]
37 changes: 20 additions & 17 deletions .machine_readable/INTENT.contractile
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,41 @@

; === Purpose (what this repo IS) ===
(purpose
"{{ONE_PARAGRAPH_PURPOSE}}"
"Burble is a self-hostable, privacy-first voice communications platform. It delivers sub-10ms latency WebRTC audio with an Elixir/Phoenix control plane, Zig SIMD coprocessor NIFs, and a P2P mode that requires no server at all. The AI data channel lets two Claude Code instances exchange structured JSON over the same encrypted WebRTC link used for voice, enabling pair-programming across machines."
)

; === Anti-Purpose (what this repo is NOT — prevents scope creep) ===
(anti-purpose
"{{ONE_PARAGRAPH_ANTI_PURPOSE}}"
; Examples:
; "This is NOT a general-purpose database — it solves one specific problem."
; "This is NOT a framework — it is a library with a focused API."
; "This does NOT handle authentication — that is delegated to [other repo]."
"Burble is NOT a general-purpose messaging platform, NOT a video conferencing tool, NOT a Discord/Slack clone. It does NOT handle text-first chat as a primary concern (text is an adjunct to voice). It does NOT require accounts, telemetry, or centralised infrastructure in P2P mode. It is NOT only for able-bodied users with modern hardware — accessibility is a structural requirement (see ADJUST.contractile)."
)

; === Key Architectural Decisions That Must Not Be Reversed ===
(architectural-invariants
; *REMINDER: List the foundational decisions*
; ("Idris2 for ABI definitions — dependent types prove interface correctness")
; ("Zig for FFI — zero-cost C ABI compatibility")
; ("Elixir for supervision — OTP fault tolerance")
("Elixir/Phoenix for the control plane — OTP supervision, fault isolation, hot code upgrade")
("Zig for the FFI coprocessor — zero-cost C ABI, SIMD audio, no runtime GC")
("Idris2 for ABI proofs — dependent types prove interface correctness at compile time")
("WebRTC for media transport — browser-compatible, E2EE via Insertable Streams")
("P2P mode as first-class — must work with zero server infrastructure")
("AffineScript for the web client (migration from ReScript in progress) — resource-aware type safety")
("No V-lang anywhere — removed 2026-04-16, replaced by Zig FFI")
("No TypeScript, no Python, no Go, no npm — Deno for JS runtime")
)

; === Sensitive Areas (if in doubt, ask) ===
(ask-before-touching
; *REMINDER: List areas where LLMs should check before modifying*
; "src/abi/ — formal proofs, changes require re-verification"
; "ffi/zig/ — C ABI boundary, changes affect all language bindings"
; ".machine_readable/ — checkpoint files, format is specified"
"src/Burble/ABI/ — formal proofs; changes require re-verification"
"ffi/zig/src/coprocessor/ — C ABI boundary; changes affect all language bindings"
"client/web/burble-ai-bridge.js — P2P Claude-to-Claude channel; the family-use critical path"
"client/web/p2p-voice.html — WebRTC signaling + AI channel setup; hard to test without two browsers"
".machine_readable/ — checkpoint files; format is specified by the 6a2 standard"
"server/lib/burble/media/ — SFU media engine; changes affect all connected peers"
"server/lib/burble/timing/ptp.ex — IEEE 1588 PTP; incorrect changes cause audible artefacts"
)

; === Ecosystem Position ===
(ecosystem
(belongs-to "{{MONOREPO_OR_STANDALONE}}")
(depends-on ("{{DEP1}}" "{{DEP2}}"))
(depended-on-by ("{{CONSUMER1}}" "{{CONSUMER2}}"))
(belongs-to "standalone (hyperpolymath estate)")
(depends-on ("VeriSimDB" "proven" "Avow" "Vext" "standards/lol"))
(depended-on-by ("IDApTIK (voice bridge)" "PanLL (voice engine)" "game-server-admin (plexus/nexus)"))
)
)
112 changes: 112 additions & 0 deletions client/web/tests/ai_bridge_roundtrip_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,115 @@ Deno.test({
sanitizeResources: false,
sanitizeOps: false,
});

// ---------------------------------------------------------------------------
// Multi-message ordering: 100 messages burst, verify all arrive in order
// ---------------------------------------------------------------------------

Deno.test({
name: "AI Bridge ordering: 100-message burst A → B arrives in order, no drops",
async fn() {
const bridgeA = await startBridge(BRIDGE_A_HTTP);
const bridgeB = await startBridge(BRIDGE_B_HTTP);
let pageA, pageB;

try {
pageA = await connectMockPage(BRIDGE_A_WS);
pageB = await connectMockPage(BRIDGE_B_WS);
crossWire(pageA, pageB);
await new Promise((r) => setTimeout(r, 200));

const COUNT = 100;

// Send 100 messages as fast as possible from A.
for (let i = 0; i < COUNT; i++) {
const resp = await fetch(`http://127.0.0.1:${BRIDGE_A_HTTP}/send`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: "burst", seq: i }),
});
assert.strictEqual(resp.status, 200, `send ${i} should succeed`);
}

// Give a moment for async relay to settle.
await new Promise((r) => setTimeout(r, 500));

// Drain all messages from B.
const resp = await fetch(`http://127.0.0.1:${BRIDGE_B_HTTP}/recv`);
const data = await resp.json();

assert.strictEqual(data.count, COUNT, `should receive all ${COUNT} messages`);

// Verify ordering: seq should be 0, 1, 2, ... 99.
for (let i = 0; i < COUNT; i++) {
assert.strictEqual(data.messages[i].seq, i, `message ${i} should have seq=${i}`);
}
} finally {
try { pageA?.ws.close(); } catch (_) {}
try { pageB?.ws.close(); } catch (_) {}
stopBridge(bridgeA);
stopBridge(bridgeB);
await new Promise((r) => setTimeout(r, 100));
}
},
sanitizeResources: false,
sanitizeOps: false,
});

// ---------------------------------------------------------------------------
// Reconnect-resume: drop bridge WS mid-session, verify queue survives
// ---------------------------------------------------------------------------

Deno.test({
name: "AI Bridge reconnect: messages queued during WS drop are preserved on reconnect",
async fn() {
const bridge = await startBridge(BRIDGE_A_HTTP);
let page;

try {
// Connect mock page.
page = await connectMockPage(BRIDGE_A_WS);
await new Promise((r) => setTimeout(r, 200));

// Simulate a received message being queued.
page.simulateRemoteDelivery({ type: "before-drop", n: 1 });
await new Promise((r) => setTimeout(r, 100));

// Verify it's queued.
let resp = await fetch(`http://127.0.0.1:${BRIDGE_A_HTTP}/status`);
let status = await resp.json();
assert.strictEqual(status.queued, 1, "should have 1 queued message");

// Now close the page WS (simulates network drop).
page.ws.close();
await new Promise((r) => setTimeout(r, 200));

// Status should show disconnected, but queued message should survive.
resp = await fetch(`http://127.0.0.1:${BRIDGE_A_HTTP}/status`);
status = await resp.json();
assert.strictEqual(status.connected, false, "should be disconnected after WS close");
assert.strictEqual(status.queued, 1, "queued message should survive WS drop");

// Reconnect a new mock page.
page = await connectMockPage(BRIDGE_A_WS);
await new Promise((r) => setTimeout(r, 200));

// Status should show connected again.
resp = await fetch(`http://127.0.0.1:${BRIDGE_A_HTTP}/status`);
status = await resp.json();
assert.strictEqual(status.connected, true, "should be connected after reconnect");

// The original queued message should still be there (it was in the HTTP queue, not the WS).
const recvResp = await fetch(`http://127.0.0.1:${BRIDGE_A_HTTP}/recv`);
const recvData = await recvResp.json();
assert.strictEqual(recvData.count, 1, "original message should still be drainable");
assert.strictEqual(recvData.messages[0].type, "before-drop");
} finally {
try { page?.ws.close(); } catch (_) {}
stopBridge(bridge);
await new Promise((r) => setTimeout(r, 100));
}
},
sanitizeResources: false,
sanitizeOps: false,
});
Loading
Loading