feat: relay world snapshot and voxel payloads

Allow world.* methods through the relay, validate their payloads, and
track per-session world revisions in the session store.
This commit is contained in:
2026-10-23 10:44:42 +08:00
parent 6ccb102d38
commit c82f15e8b6
8 changed files with 1064 additions and 46 deletions
+93 -29
View File
@@ -278,36 +278,75 @@ test('tab_complete dispatch rejects ranges beyond request text and accepts exact
} finally {await f.close();}
});
test('bot_event is rejected while bot_log checks permission, scope and sequence', async () => {
test('status lifecycle events fan out to snapshot readers with scope, permission and sequence checks', async () => {
const f = await fixture();
try {
const node = await f.node(); const web = await f.web();
const limited = await f.web(randomUUID(), ['bot.subscribe']);
await ack(web.socket, 'relay_request', request('bot.subscribe', {events: ['status_update', 'log']}));
await ack(limited.socket, 'relay_request', request('bot.subscribe', {events: ['status_update']}));
let limited_events = 0; limited.socket.on('bot_event', () => limited_events++);
const node = await f.node();
const web = await f.web(randomUUID(), ['bot.snapshot']);
const log_reader = await f.web(randomUUID(), ['bot.logs']);
let log_reader_events = 0;
log_reader.socket.on('bot_event', () => log_reader_events++);
const stream = randomUUID();
const event = {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id, context_generation: null as string | null,
stream_id: stream, seq: 1, emitted_at: Date.now(), name: 'status_update', payload: {status: 'offline', context_exists: false}};
let web_events = 0; web.socket.on('bot_event', () => web_events++);
code(await ack(node, 'bot_event', event), 'FORBIDDEN');
code(await ack(web.socket, 'bot_event', {...event, seq: 2}), 'FORBIDDEN');
code(await ack(web.socket, 'forward_open_session', {}), 'FORBIDDEN');
node.emit('bot_event', {...event, seq: 3});
node.on('relay_request', (_value, reply) => reply(snapshot(f.s.bot_id)));
assert.equal((await ack(web.socket, 'relay_request', request('bot.snapshot'))).status, 'success');
assert.equal(web_events, 0);
const log = {...event, name: 'log', payload: {records: [] as never[]}};
let limited_logs = 0; limited.socket.on('bot_log', () => limited_logs++);
const delivered = received(web.socket, 'bot_log');
const context_generation = randomUUID();
const event = (seq: number, status: string, context_exists: boolean) => ({
forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
context_generation: context_exists ? context_generation : null,
stream_id: stream, seq, emitted_at: Date.now(), name: 'status_update',
payload: {status, context_exists},
});
for (const [seq, status, context_exists] of [[1, 'online', true], [2, 'disconnecting', true],
[3, 'offline', true], [4, 'offline', false]] as const) {
const expected = event(seq, status, context_exists);
const delivered = received(web.socket, 'bot_event');
assert.deepEqual(await ack(node, 'bot_event', expected), {status: 'success', data: {accepted: true}});
assert.deepEqual((await delivered)[0], expected);
}
assert.equal(log_reader_events, 0);
code(await ack(web.socket, 'bot_event', event(5, 'offline', false)), 'FORBIDDEN');
for (const [name, payload] of [['bot_error', {message: 'unexpected'}], ['message', {text: 'hello', position: 'chat'}],
['log', {records: []}], ['arbitrary', {}], ['status_update', {status: 'invalid', context_exists: false}],
['status_update', {status: 'offline', context_exists: false, extra: true}]] as const) {
code(await ack(node, 'bot_event', {...event(5, 'offline', false), name, payload}), 'INVALID_REQUEST');
}
code(await ack(node, 'bot_event', event(5, 'online', false)), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', {...event(5, 'offline', true), context_generation: null}), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', {...event(5, 'offline', false), seq: -1}), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', {...event(5, 'offline', false), extra: true}), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', event(4, 'offline', false)), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', {...event(5, 'offline', false), bot_id: f.s.bot_id + 1}), 'FORBIDDEN');
code(await ack(node, 'bot_event', {...event(5, 'offline', false), forward_session_id: randomUUID()}), 'FORBIDDEN');
const next = event(5, 'offline', false);
const delivered = received(web.socket, 'bot_event');
node.emit('bot_event', next);
assert.deepEqual((await delivered)[0], next);
const log = {...next, seq: 6, name: 'log', payload: {records: [] as never[]}};
let limited_logs = 0; web.socket.on('bot_log', () => limited_logs++);
const log_delivered = received(log_reader.socket, 'bot_log');
assert.equal((await ack(node, 'bot_log', log)).status, 'success');
assert.deepEqual((await delivered)[0], log);
code(await ack(node, 'bot_log', log), 'INVALID_REQUEST');
code(await ack(web.socket, 'bot_log', {...log, seq: 2}), 'FORBIDDEN');
code(await ack(node, 'bot_log', {...log, seq: 2, bot_id: f.s.bot_id + 1}), 'FORBIDDEN');
code(await ack(node, 'bot_log', {...event, seq: 2}), 'INVALID_REQUEST');
assert.deepEqual((await log_delivered)[0], log);
code(await ack(node, 'bot_log', {...log, seq: 5}), 'INVALID_REQUEST');
code(await ack(web.socket, 'bot_log', {...log, seq: 6}), 'FORBIDDEN');
code(await ack(node, 'bot_log', {...log, seq: 6, bot_id: f.s.bot_id + 1}), 'FORBIDDEN');
code(await ack(node, 'bot_log', {...next, seq: 6}), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', event(6, 'offline', false)), 'INVALID_REQUEST');
code(await ack(node, 'bot_event', event(5, 'offline', false)), 'INVALID_REQUEST');
const update = {forward_session_id: next.forward_session_id, bot_id: next.bot_id,
stream_id: stream, context_generation, emitted_at: next.emitted_at, seq: 7,
data: {name: 'health', value: 20}};
assert.equal((await ack(node, 'info_update', update)).status, 'success');
code(await ack(node, 'bot_event', event(7, 'offline', false)), 'INVALID_REQUEST');
const replacement = {...event(0, 'offline', false), stream_id: randomUUID()};
assert.equal((await ack(node, 'bot_event', replacement)).status, 'success');
code(await ack(node, 'bot_event', event(8, 'offline', false)), 'RESYNC_REQUIRED');
assert.equal(limited_logs, 0);
assert.equal(limited_events, 0);
assert.equal(log_reader_events, 0);
const restricted = await f.node(['bot.logs']);
code(await ack(restricted, 'bot_event', event(8, 'offline', false)), 'FORBIDDEN');
const not_ready = await f.node(['bot.snapshot'], false);
code(await ack(not_ready, 'bot_event', event(8, 'offline', false)), 'NODE_OFFLINE');
} finally {await f.close();}
});
@@ -434,6 +473,29 @@ test('info_update auto-pushes exact envelopes and shares log stream and sequence
} finally {await f.close();}
});
test('lifecycle packets use reliable bounded buffering and share the Node event rate', async () => {
const f = await fixture({max_buffered_packets: 2, events_per_second: 3});
try {
const node = await f.node(['bot.snapshot', 'bot.logs']);
const web = await f.web(randomUUID(), ['bot.snapshot']);
const server_web = f.namespace.sockets.get(web.socket.id!)!;
const transport = server_web.conn as unknown as {writeBuffer: unknown[]};
server_web.conn.transport.writable = false;
const event = {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
context_generation: null as string | null, stream_id: randomUUID(), seq: 1, emitted_at: Date.now(),
name: 'status_update', payload: {status: 'offline', context_exists: false}};
for (let seq = 1; seq <= 3; seq++) {
assert.equal((await ack(node, 'bot_event', {...event, seq})).status, 'success');
if (seq === 1) assert.equal(transport.writeBuffer.length, 1);
}
assert.equal(server_web.connected, false);
assert.ok(transport.writeBuffer.length <= 4);
const closed = received(node, 'forward_session_closed');
node.emit('bot_log', {...event, seq: 4, name: 'log', payload: {records: []}});
assert.equal((await closed)[0].code, 'RATE_LIMITED');
} finally {await f.close();}
});
test('info_update enforces readiness and Node and Web token permissions independently of subscriptions', async () => {
const f = await fixture();
try {
@@ -619,9 +681,9 @@ test('slow-client status buffer overflow and forbidden event floods require expl
// Notification is emitted before disconnect; packet delivery depends on Engine.IO's buffer flush.
assert.equal(server_web.connected, false);
await Promise.race([closed, delay(30)]);
code(await ack(replacement, 'bot_event', event), 'FORBIDDEN');
code(await ack(replacement, 'bot_event', event), 'NODE_OFFLINE');
const node_closed = received(replacement, 'forward_session_closed');
for (let seq = 2; seq <= 10; seq++) replacement.emit('bot_event', {...event, seq});
for (let seq = 2; seq <= 10; seq++) replacement.emit('unknown', {...event, seq});
assert.equal((await node_closed)[0].code, 'RATE_LIMITED');
} finally {await f.close();}
});
@@ -660,9 +722,11 @@ test('reauth extends only the same identity, reconnect restores subscriptions, r
const replacement = await f.connect();
await ack(replacement, 'join_room', {token: token(f.s, 'web', web.grant_id, expiry)});
await ack(replacement, 'relay_request', subscribe);
code(await ack(node, 'bot_event', {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
const pushed = received(replacement, 'bot_event');
assert.equal((await ack(node, 'bot_event', {forward_session_id: f.s.forward_session_id, bot_id: f.s.bot_id,
context_generation: null, stream_id: randomUUID(), seq: 1, emitted_at: Date.now(),
name: 'status_update', payload: {status: 'offline', context_exists: false}}), 'FORBIDDEN');
name: 'status_update', payload: {status: 'offline', context_exists: false}})).status, 'success');
assert.equal((await pushed)[0].payload.status, 'offline');
node.on('relay_request', (_value, reply) => reply(snapshot(f.s.bot_id)));
assert.equal((await ack(replacement, 'relay_request', request('bot.snapshot'))).status, 'success');
f.store.revoke_grant({forward_session_id: f.s.forward_session_id, grant_id: web.grant_id, reason: 'permission_removed'});