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
+13 -3
View File
@@ -2,7 +2,8 @@
// (tools/ticket-oracle → tickets.python.json): every ticket the oracle made
// reads the same — the same payload, or refused where Python refuses it, for
// the same reason (a signature that does not match, an age beyond 60 s or
// below 0) —; tickets signed in Node carry the same JSON and the same moment
// more than 5 s ahead) —, except a ticket signed up to 5 s ahead of F's
// clock, which Python refuses and F takes (two machines' clocks); tickets signed in Node carry the same JSON and the same moment
// as Python's (packed by Node's zlib, Chromium's, the bytes differ; an
// unpacked one is the same to the byte); and
// the claims the server takes from a ticket: a person, a login, a project
@@ -11,7 +12,7 @@
import { readFileSync } from 'node:fs';
import { inflateSync } from 'node:zlib';
import { describe, expect, it } from 'vitest';
import { readSigned, signature, TICKET_SALT, TICKET_SECONDS, TicketError, verifyTicket } from '../src/ticket.js';
import { readSigned, signature, TICKET_AHEAD_SECONDS, TICKET_SALT, TICKET_SECONDS, TicketError, verifyTicket } from '../src/ticket.js';
import { signTicket } from './tickets.js';
interface OracleCase {
@@ -36,6 +37,8 @@ const unpacked = (ticket: string) => {
return (ticket.startsWith('.') ? inflateSync(bytes) : bytes).toString('utf8');
};
const byName = (name: string) => oracle.cases.find((each) => each.name === name)!;
/** Python refuses these, F takes them: signed ahead of F's clock, within TICKET_AHEAD_SECONDS. */
const AHEAD = ['fromTheFuture', 'fiveSecondsAhead'];
function outcome(ticket: string, now: number): { payload: unknown } | { error: string } {
try {
@@ -52,7 +55,7 @@ describe('the ticket as itsdangerous signs it', () => {
expect([TICKET_SALT, TICKET_SECONDS]).toEqual([oracle.salt, oracle.ttl]);
});
it.each(oracle.cases.map((each) => [each.name, each] as const))('reads „%s“ as Python does', (_name, each) => {
it.each(oracle.cases.filter((each) => !AHEAD.includes(each.name)).map((each) => [each.name, each] as const))('reads „%s“ as Python does', (_name, each) => {
expect(outcome(each.ticket, each.now)).toEqual(each.error === undefined ? { payload: each.payload } : { error: each.error });
});
@@ -66,6 +69,13 @@ describe('the ticket as itsdangerous signs it', () => {
expect(readSigned(ours, oracle.secret, oracle.signedAt)).toEqual(each.payload);
});
it.each(AHEAD)('takes „%s“, which Python refuses: a ticket from up to 5 s ahead of F\'s clock', (name) => {
const each = byName(name);
expect(each.error).toBe('expired');
expect(outcome(each.ticket, each.now)).toEqual({ payload: byName('plain').payload });
expect(oracle.signedAt - each.now).toBeLessThanOrEqual(TICKET_AHEAD_SECONDS);
});
it('covers a packed and an unpacked payload', () => {
expect(byName('plain').ticket.startsWith('.')).toBe(true);
expect(byName('listPayload').ticket.startsWith('.')).toBe(false);