Skip to content
Draft
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
6 changes: 6 additions & 0 deletions deploy/compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ BUZZ_MEDIA_BASE_URL=https://buzz.example.com/media
BUZZ_MEDIA_SERVER_DOMAIN=buzz.example.com
BUZZ_CORS_ORIGINS=https://buzz.example.com

# Optional NIP-AB pairing sidecar. Start it with `--profile pairing`, then
# uncomment the TLS reverse-proxy path. Never advertise it before the sidecar
# and proxy route are healthy, and never publish its raw port externally.
# BUZZ_PAIRING_RELAY_URL=wss://buzz.example.com/pair
BUZZ_PAIR_RELAY_PORT=5000

# Production defaults. Closed relay mode requires RELAY_OWNER_PUBKEY and a stable relay key.
BUZZ_REQUIRE_AUTH_TOKEN=true
BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
Expand Down
15 changes: 15 additions & 0 deletions deploy/compose/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ services:
networks:
- buzz-net

# Optional stateless NIP-AB transport for pairing a device that is not yet a
# relay member. The host port is loopback-only; a TLS reverse proxy must
# expose only /pair. Enable explicitly with `--profile pairing`.
pairing-relay:
profiles: ["pairing"]
image: ${BUZZ_IMAGE:-ghcr.io/block/buzz:main}
entrypoint: ["/usr/local/bin/buzz-pair-relay"]
environment:
BUZZ_PAIR_RELAY_BIND_ADDR: 0.0.0.0:5000
ports:
- "127.0.0.1:${BUZZ_PAIR_RELAY_PORT:-5000}:5000"
restart: unless-stopped
networks:
- buzz-net

postgres:
image: postgres:17-alpine
environment:
Expand Down
17 changes: 17 additions & 0 deletions deploy/local/aeon-aspects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ proactive count-based session rotation are off, thread/DM context is bounded to
absolute cap. `bypassPermissions` is an explicit canary posture; it does not
prove that a future interactive approval workflow is preserved.

The checked-in AEON manifest uses the single canonical tailnet authority
`wss://architects-mac-mini.tail757661.ts.net`. Production workers, Desktop,
mobile, relay NIP-11 metadata, media URLs, and the durable community host map
must all use that same authority. The validator permits insecure WebSocket only
for the exact local development URL `ws://localhost:3000`; every non-local
deployment must use a root `wss://` URL with no credentials, path, query, or
fragment. Tailscale Funnel is not required and must remain off.

Mobile identity transfer uses Buzz's existing NIP-AB QR and SAS flow. Because
the production relay requires membership, the unpaired phone cannot use the
main relay for that handshake. Run the existing `buzz-pair-relay` Compose
profile on loopback and route only `/pair` to it through Tailscale Serve; set
`BUZZ_PAIRING_RELAY_URL` to the canonical `wss://` `/pair` URL advertised by
NIP-11. The encrypted pairing payload still carries the canonical HTTPS main
relay URL used by mobile for chat, media, and subsequent authenticated WebSocket
sessions.

`--trusted-inbound-envelope` copies one signature-verified triggering event
into the ACP request as `_meta.buzz.inboundEvent` (`schemaVersion: 1`). The
envelope carries the signed event ID, author, kind, exact channel ID, and exact
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<array>
<string>/Volumes/AEON/Projects/buzz/target/release/buzz-acp</string>
<string>--relay-url</string>
<string>ws://localhost:3000</string>
<string>wss://architects-mac-mini.tail757661.ts.net</string>
<string>--private-key-file</string>
<string>/Volumes/AEON/Projects/buzz-data/keys/fontis.sk</string>
<string>--expected-public-key</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<array>
<string>/Volumes/AEON/Projects/buzz/target/release/buzz-acp</string>
<string>--relay-url</string>
<string>ws://localhost:3000</string>
<string>wss://architects-mac-mini.tail757661.ts.net</string>
<string>--private-key-file</string>
<string>/Volumes/AEON/Projects/buzz-data/keys/mechanon.sk</string>
<string>--expected-public-key</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<array>
<string>/Volumes/AEON/Projects/buzz/target/release/buzz-acp</string>
<string>--relay-url</string>
<string>ws://localhost:3000</string>
<string>wss://architects-mac-mini.tail757661.ts.net</string>
<string>--private-key-file</string>
<string>/Volumes/AEON/Projects/buzz-data/keys/nexus.sk</string>
<string>--expected-public-key</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<array>
<string>/Volumes/AEON/Projects/buzz/target/release/buzz-acp</string>
<string>--relay-url</string>
<string>ws://localhost:3000</string>
<string>wss://architects-mac-mini.tail757661.ts.net</string>
<string>--private-key-file</string>
<string>/Volumes/AEON/Projects/buzz-data/keys/sapientis.sk</string>
<string>--expected-public-key</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<array>
<string>/Volumes/AEON/Projects/buzz/target/release/buzz-acp</string>
<string>--relay-url</string>
<string>ws://localhost:3000</string>
<string>wss://architects-mac-mini.tail757661.ts.net</string>
<string>--private-key-file</string>
<string>/Volumes/AEON/Projects/buzz-data/keys/viatica.sk</string>
<string>--expected-public-key</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<array>
<string>/Volumes/AEON/Projects/buzz/target/release/buzz-acp</string>
<string>--relay-url</string>
<string>ws://localhost:3000</string>
<string>wss://architects-mac-mini.tail757661.ts.net</string>
<string>--private-key-file</string>
<string>/Volumes/AEON/Projects/buzz-data/keys/voxis.sk</string>
<string>--expected-public-key</string>
Expand Down
33 changes: 32 additions & 1 deletion deploy/local/aeon-aspects/worker.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import fs from "node:fs";

const AEON_PRODUCTION_RELAY_URL = "wss://architects-mac-mini.tail757661.ts.net";

export function loadJson(path) {
return JSON.parse(fs.readFileSync(path, "utf8"));
}
Expand All @@ -9,7 +11,36 @@ export function validateManifest(manifest, identityMap) {
const warnings = [];
if (manifest.enabled !== false) errors.push("package must be disabled by default");
if (manifest.workers?.length !== 6) errors.push("exactly six Aspect workers are required");
if (manifest.buzz?.relayUrl !== "ws://localhost:3000") errors.push("Buzz relay must use localhost");
try {
const configuredRelayUrl = manifest.buzz?.relayUrl;
const relayUrl = new URL(configuredRelayUrl);
const isLoopbackDevelopment =
relayUrl.protocol === "ws:" &&
relayUrl.hostname === "localhost" &&
relayUrl.port === "3000";
const isSecureProduction =
relayUrl.protocol === "wss:" &&
configuredRelayUrl.startsWith("wss://") &&
configuredRelayUrl === AEON_PRODUCTION_RELAY_URL;
const hasCanonicalRoot =
relayUrl.username === "" &&
relayUrl.password === "" &&
relayUrl.pathname === "/" &&
relayUrl.search === "" &&
relayUrl.hash === "";
const isCanonicalSpelling =
configuredRelayUrl === relayUrl.origin ||
configuredRelayUrl === `${relayUrl.origin}/`;
if (
(!isLoopbackDevelopment && !isSecureProduction) ||
!hasCanonicalRoot ||
!isCanonicalSpelling
) {
errors.push("Buzz relay must use ws://localhost:3000 for development or a canonical root wss:// URL");
}
} catch {
errors.push("Buzz relay URL is invalid");
}
if (manifest.posture?.memory !== false) errors.push("Buzz memory injection must be disabled");
if (manifest.posture?.basePrompt !== false) errors.push("Buzz base prompt must be disabled");
if (manifest.posture?.respondTo !== "owner-only") errors.push("respondTo must be owner-only");
Expand Down
29 changes: 29 additions & 0 deletions deploy/local/aeon-aspects/worker.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,35 @@ test("six-worker manifest matches the synthetic identity-map contract", () => {
assert.match(result.warnings[0], /avatar metadata is absent/);
});

test("worker relay authority accepts canonical secure production WSS and fails closed elsewhere", () => {
assert.equal(manifest.buzz.relayUrl, "wss://architects-mac-mini.tail757661.ts.net");
for (const relayUrl of [
"wss://relay.example.com/path",
"wss://relay.example.com",
"wss://relay.example.com?tenant=aeon",
"wss://user@relay.example.com",
"ws://relay.example.com",
"https://relay.example.com",
"wss:relay.example.com",
"wss:/relay.example.com",
"wss://relay.example.com:443",
"not-a-url",
]) {
const result = validateManifest(
{ ...manifest, buzz: { ...manifest.buzz, relayUrl } },
identityMap,
);
assert.equal(result.ok, false, relayUrl);
assert.match(result.errors.join("\n"), /Buzz relay/);
}

const development = validateManifest(
{ ...manifest, buzz: { ...manifest.buzz, relayUrl: "ws://localhost:3000" } },
identityMap,
);
assert.equal(development.ok, true, development.errors.join("\n"));
});

test("every rendered worker is disabled and binds an existing fixed session", () => {
for (const worker of manifest.workers) {
const rendered = renderWorker(manifest, identityMap, worker.aspect, "/owned/gateway.token");
Expand Down
2 changes: 1 addition & 1 deletion deploy/local/aeon-aspects/workers.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"provenance": "meta+receipt"
},
"buzz": {
"relayUrl": "ws://localhost:3000",
"relayUrl": "wss://architects-mac-mini.tail757661.ts.net",
"architectPubkey": "73ac8798fd9cedcc5d24645d7ed49332d94a28f2c937f4c1b638f92bf6e8e91f",
"conciliumChannelId": "4ac1cee0-2238-483f-b25b-f99ec17eec46"
},
Expand Down