feat: F5.6c measured and accepted on test-filius; tickets up to 5 s ahead of F's clock

test-filius refused every ticket as from the future: its clock was 886 ms
behind the dashboard's machine, with NTP still 8 ms (one ticket in 125, as a
ticket carries whole seconds). F now takes a ticket signed up to 5 s ahead
(TICKET_AHEAD_SECONDS); itsdangerous refuses one second, and the oracle
keeps that on record.

tools/live measures and accepts on a live F, with fresh projects each run:
m3 (200 rooms of the largest example with pings: probe p95 37 ms), m4 (63 KB
document; dragging makes an update per pointer move, 59/s), m5 (an hour
through the proxy), acceptance (two browsers, 14 checks, passed three times)
and clock (F's clock against ours).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 19:22:22 +00:00
co-authored by Claude Opus 5.5
parent 087721cba3
commit 416d5fdea3
15 changed files with 630 additions and 7 deletions
+47
View File
@@ -0,0 +1,47 @@
// M5 on a live server F: does a WebSocket through the openresty hold an hour?
// One person in a fresh project, with /sync (the document) and /sim (the
// room) open and nothing to say — the proxy's limit is between two bytes,
// so only the pings of F (/sim, 30 s) and Hocuspocus (/sync) keep them. Every
// five minutes a line; at the end a probe through /sim and a change through
// /sync, to show that both still carry.
//
// npx vite-node tools/live/m5.ts [minutes]
import { cleanUp, joinDoc, newProject, openRoom, signIn } from './lib.js';
const minutes = Number(process.argv[2] ?? 65);
const started = Date.now();
const elapsed = () => `${((Date.now() - started) / 60_000).toFixed(1)} min`;
try {
const project = await newProject();
const cookie = await signIn(project, { name: 'Dauer' });
const shared = await joinDoc(project, cookie);
const room = await openRoom(project, cookie);
let syncClosed = '';
shared.provider.on('close', ({ event }: { event: { code: number; reason: string } }) => (syncClosed ||= `${event.code} ${event.reason} at ${elapsed()}`));
let simClosed = '';
void room.closed.then((reason) => (simClosed = `${reason} at ${elapsed()}`));
console.log(`open: /sync and /sim, holding ${minutes} min`);
for (let minute = 5; minute <= minutes; minute += 5) {
await new Promise((resolve) => setTimeout(resolve, Math.min(5, minutes - minute + 5) * 60_000));
console.log(`${elapsed()}: /sync ${syncClosed || 'open'}, /sim ${simClosed || 'open'}`);
}
room.send({ kind: 'input', node: 'probe', app: 'terminal', action: 'enter', args: { line: '' }, reqId: 'last' });
const answer = await room
.waitFor((message) => message.reqId === 'last', 10_000)
.then(
() => 'answered',
() => 'no answer',
);
shared.doc.getMap('meta').set('m5', Date.now());
const stored = await new Promise<string>((resolve) => {
const end = Date.now() + 10_000;
const look = () => (shared.provider.unsyncedChanges === 0 ? resolve('stored') : Date.now() > end ? resolve('not stored') : setTimeout(look, 50));
look();
});
console.log(`after ${elapsed()}: /sim probe ${answer}, /sync change ${stored}; closed: /sync ${syncClosed || 'never'}, /sim ${simClosed || 'never'}`);
room.close();
shared.close();
} finally {
await cleanUp();
}