Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
525231e
feat(relay): add bounded same-socket presence transport
Sep 12, 2026
0bcab08
feat(presence): add session directory, activity renewal and visible U…
Sep 12, 2026
8b30192
fix(presence): preserve snapshot spacing through admission delay
Sep 12, 2026
8b67780
Merge main's Agent Activity alongside presence
Sep 12, 2026
867708f
Merge membership activity while preserving presence demand
Sep 12, 2026
d0514a5
fix(presence): isolate optional admission from foreground work
Sep 12, 2026
3aa265a
Merge image-scroll repair while preserving presence demand
Sep 12, 2026
7e4d9f0
test(presence): exercise read-state publication in conversation journey
Sep 12, 2026
e26fccb
fix(presence): distinguish away without relying on color
Sep 13, 2026
65ce2ba
test: reproduce presence publication retirement ambiguity
Sep 13, 2026
11a7987
fix(presence): preserve owner disposal settlement evidence
Sep 13, 2026
a658e2f
test(presence): measure verified sends independently of browser ackno…
Sep 13, 2026
8cddc80
Merge main into presence with settled startup test baselines
Sep 13, 2026
657aacf
test(presence): model unconfirmed send reconciliation in controls
Sep 13, 2026
479e0d7
test: isolate fixture caches and remove duplicate quota runs
Sep 13, 2026
db81369
fix(presence): require explicit complete transport support
Sep 13, 2026
9496ddb
Merge reviewed broker transport and main into presence feature
Sep 13, 2026
32ac3ab
test: settle startup catch-up before live recovery fault
Sep 13, 2026
9a50c15
Merge repaired presence transport and preserve notification integration
Sep 13, 2026
83a8b0c
test(browser): account only proven retired presence publication 404s
Sep 14, 2026
4425fa6
Merge presence transport review fixes and document lifecycle evidence
Sep 14, 2026
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
193 changes: 190 additions & 3 deletions dev/relay-broker-fixture.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ test("actual fixture fails an unclassified 503 even with no console event", asyn
fixture(async (app) => {
await publication(app, false);
}),
).rejects.toThrow("Unclassified presence publication 503");
).rejects.toThrow("Unclassified presence publication 404/503");
});

test("same endpoint disposal console cannot hide a separate unclassified response", async () => {
Expand All @@ -186,7 +186,7 @@ test("same endpoint disposal console cannot hide a separate unclassified respons
console503(page, await publication(app, true));
await publication(app, false);
}),
).rejects.toThrow("Unclassified presence publication 503");
).rejects.toThrow("Unclassified presence publication 404/503");
});

test("one classified response permits one console diagnostic", async () => {
Expand Down Expand Up @@ -232,10 +232,197 @@ test.each([
req.emit("end");
res.end(body);
expect(() => evidence.assertPublications()).toThrow(
"Unclassified presence publication 503",
"Unclassified presence publication 404/503",
);
});

async function retiredPublication(app, community = "primary") {
const headers = { Origin: app.origin, "Content-Type": "application/json" };
const controller = new AbortController();
let streamId;
try {
const response = await fetch(`${app.origin}/api/relay/primary/stream`, {
method: "POST",
headers,
body: JSON.stringify({ channels: [] }),
signal: controller.signal,
});
expect(response.status).toBe(200);
streamId = response.headers.get("x-buzz-live-id");
} finally {
controller.abort();
}
await until(() =>
app.report.brokerRequests.some(
(record) => record.url.endsWith("/stream") && record.close,
),
);
const endpoint = `${app.origin}/api/relay/${community}/stream-presence-publish`;
const response = await fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({ streamId, status: "online" }),
signal: AbortSignal.timeout(3000),
});
expect(response.status).toBe(404);
expect(await response.json()).toEqual({
error: "Live stream no longer available",
});
expect(app.report.presencePublications).toEqual([]);
return { endpoint, streamId };
}

const console404 = (page, url) =>
page.emit("console", {
type: () => "error",
text: () =>
"Failed to load resource: the server responded with a status of 404 (Not Found)",
location: () => ({ url }),
});

test.each([false, true])(
"actual fixture accounts an already-retired publication 404 (console %s)",
async (console) => {
await fixture(async (app, page) => {
const { endpoint, streamId } = await retiredPublication(app);
expect(app.report.presencePublicationResponses).toEqual([
expect.objectContaining({
url: endpoint,
streamId,
status: 404,
disposed: false,
retired: true,
body: { error: "Live stream no longer available" },
}),
]);
if (console) console404(page, endpoint);
});
},
);

test("retirement in another community cannot classify a real publication 404", async () => {
await expect(
fixture(async (app) => {
await retiredPublication(app, "secondary");
}),
).rejects.toThrow("Unclassified presence publication 404/503");
});

test.each([404, 503])(
"retired 404 console cannot hide an unclassified %s at the same endpoint",
async (status) => {
await expect(
fixture(async (app, page) => {
const { endpoint } = await retiredPublication(app);
console404(page, endpoint);
if (status === 503) await publication(app, false);
else {
const response = await fetch(endpoint, {
method: "POST",
headers: { Origin: app.origin, "Content-Type": "application/json" },
body: JSON.stringify({
streamId: "0".repeat(32),
status: "online",
}),
});
expect(response.status).toBe(404);
await response.text();
}
}),
).rejects.toThrow("Unclassified presence publication 404/503");
},
);

test.each(["duplicate", "wrong status", "wrong endpoint"])(
"retired publication accounting rejects %s console evidence",
async (failure) => {
await expect(
fixture(async (app, page) => {
const { endpoint } = await retiredPublication(app);
if (failure === "duplicate") {
console404(page, endpoint);
console404(page, endpoint);
} else if (failure === "wrong status") console503(page, endpoint);
else console404(page, `${endpoint}/other`);
}),
).rejects.toThrow();
},
);

// The real fixture tests above prove production wiring. These controlled response
// boundaries cover impossible/malformed evidence without altering broker behavior.
test.each([
{ name: "current stream", retirement: "never" },
{ name: "unknown stream", requestId: "b".repeat(32) },
{ name: "malformed stream ID", requestId: "bad", streamId: "bad" },
{ name: "different relay", streamPath: "/api/relay/secondary/stream" },
{ name: "retirement during upload", retirement: "after arrival" },
{ name: "retirement after response", retirement: "after response" },
{ name: "missing body", body: undefined },
{ name: "malformed JSON", body: "not-json" },
{ name: "wrong error", body: JSON.stringify({ error: "other" }) },
{
name: "extra field",
body: JSON.stringify({
error: "Live stream no longer available",
accepted: true,
}),
},
{ name: "truncated response", truncated: true },
])("publication 404 fails closed for $name", (options) => {
const report = { brokerRequests: [] };
const evidence = brokerEvidence(report, new Set());
const streamId = options.streamId ?? "a".repeat(32);
const request = (url) => {
const req = new EventEmitter();
req.url = url;
req.headers = { host: "127.0.0.1:1234" };
return req;
};
const response = () => {
const res = new EventEmitter();
res.statusCode = 200;
res.writeHead = () => {};
res.end = () => {};
res.getHeader = () => undefined;
return res;
};
const stream = response();
evidence.middleware(
request(options.streamPath ?? "/api/relay/primary/stream"),
stream,
() => {},
);
stream.writeHead(200, { "X-Buzz-Live-ID": streamId });
const retirement = options.retirement ?? "before arrival";
if (retirement === "before arrival") stream.emit("close");
const req = request("/api/relay/primary/stream-presence-publish");
const res = response();
evidence.middleware(req, res, () => {});
if (retirement === "after arrival") stream.emit("close");
req.emit("data", JSON.stringify({ streamId: options.requestId ?? streamId }));
req.emit("end");
res.statusCode = 404;
if (options.truncated) res.emit("close");
else
res.end(
Object.hasOwn(options, "body")
? options.body
: JSON.stringify({ error: "Live stream no longer available" }),
);
if (retirement === "after response") stream.emit("close");
expect(report.presencePublicationResponses).toHaveLength(1);
expect(() => evidence.assertPublications()).toThrow(
"Unclassified presence publication 404/503",
);
expect(
evidence.consoleFilter()(
"Failed to load resource: the server responded with a status of 404 (Not Found)",
"http://127.0.0.1:1234/api/relay/primary/stream-presence-publish",
),
).toBe(false);
});

test("passive completion evidence preserves Server-Timing and distinguishes an unfinished close", async () => {
const report = { brokerRequests: [] };
const evidence = brokerEvidence(report, new Set());
Expand Down
34 changes: 33 additions & 1 deletion docs/browser-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ bin/pnpm test:browser tests/browser/layout.spec.mjs --no-deps
bin/pnpm test:browser --no-deps --workers=1
```

The default local gate runs `channel-opening.spec.mjs` and `scroll.spec.mjs` first,
The default local gate runs `channel-opening.spec.mjs`, `scroll.spec.mjs`,
`presence-contention.spec.mjs`, and `presence-control.spec.mjs` first,
one browser/worker at a time, through the `chromium-measurements` →
`webkit-measurements` dependency chain. Only then may functional journeys run
with two workers. This preserves timing/heap samples without unrelated browser
Expand Down Expand Up @@ -198,6 +199,37 @@ The separate `channel-opening.test.ts` exercises catch-up ownership and terminal
retry states through the production session. A held-response reproducer establishes
a failure mechanism; it does not on its own identify a live incident's cause.

## Presence contention controls

`presence-contention.spec.mjs` forces a real signed presence conflict through the
session directory, verified reader, and production broker, then holds the upstream
snapshot. Immediately afterward it submits the actual composer or opens a cold
channel. The foreground host request must arrive within **200ms** of snapshot
start (inside the old 500ms residual pacing window); broker admission must be
under **100ms**, with host-arrival-to-upstream under **150ms**. These generous
regression ceilings detect the inherited pacing interval, not universal zero-cost
service. Send keeps the snapshot pending; cold navigation can abort it but must
not inherit its consumed credit. Optimistic message text is not a send receipt.

`presence-control.spec.mjs` repeats both journeys with only the directory and
publisher disabled in a test build. It preserves normal reader, broker, signer,
outbox, cache and connection behavior and asserts that no presence traffic occurs.
Both files run serially in each measurement engine. Evidence separates runner-clock
host/upstream timestamps, observed browser request events, browser resource timing,
Server-Timing admission/auth/network, and the exported production read/write profiler.
These clocks must not be subtracted across domains.

The opt-in policy relay numerically enforces audited reference defaults: shared
API **300/min**, WS REQ/EVENT **50/5s**, plus **60 EVENT/min**. Counters are shared
across sockets for the same community/viewer; HTTP publications share the API
counter with snapshots. `dev/policy-relay.test.mjs` runs once under Vitest (no browser) and deliberately exceeds each budget
and requires correlated rejection, community isolation, and window expiry. This
positive control prevents an empty refusal list from masquerading as enforcement.
The short browser journeys do not saturate the client's maximum envelope or prove
capacity isolation; colocated transport tests cover those contracts. Other devices,
lower deployed quotas, CPU contention, SQL/Redis cost, and native GUI remain outside
this offline model.

## DM label recovery

`dm-labels.spec.mjs` builds the actual page and uses the production broker with
Expand Down
Loading
Loading