Skip to content
Merged
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
12 changes: 11 additions & 1 deletion apps/web/src/app/api/dataset/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,17 @@ export function GET() {
'Each window is the half-open range [start, end) on the row timestamp named in `cutOn`. Windows are fixed against the Unix epoch, so a window is the same set of rows whoever asks and whenever they ask. Walk them in order to see every row exactly once.',
latestClosedWindow: newest,
latestWindowEnd: windowEnd(newest),
nextWindowOpensAt: windowEnd(newest),
// When the *next* slice becomes takeable, which is not when the next
// window opens — it is when that window closes.
//
// This field shipped as `nextWindowOpensAt` returning `windowEnd(newest)`,
// which is the same value as `latestWindowEnd` directly above it and is
// the wrong instant to hand a scheduler. The window starting at that
// moment is the one still filling, so a pipeline that woke then would
// re-pull the slice it already had and loop until the real boundary
// passed. One window further on is the first moment there is anything
// new to take.
nextWindowAvailableAt: windowEnd(windowEnd(newest)),
},

datasets: {
Expand Down
24 changes: 24 additions & 0 deletions apps/web/test/dataset-window.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,30 @@ test('a window that is not a timestamp is refused', () => {
assert.equal(got.error, 'bad-window');
});

test('the moment the next slice becomes takeable is always in the future', () => {
// The invariant the manifest got wrong on its first day. It advertised
// `windowEnd(latestClosedWindow())` as when to come back, and that instant has
// *already passed* — it is the boundary that closed the window being served.
// A scheduler obeying it would wake immediately, re-pull the slice it already
// had, and spin until the real boundary arrived.
//
// Asserting "strictly in the future" rather than a fixed string is the point:
// it fails for any now, and it is exactly the property a caller depends on.
for (const offsetMinutes of [1, 59, 60, 121, 239]) {
const now = Date.parse('2026-08-29T12:00:00.000Z') + offsetMinutes * 60_000;
const newest = latestClosedWindow(now);

assert.ok(
Date.parse(windowEnd(newest)) <= now,
'the served window has closed, so its own end is in the past',
);
assert.ok(
Date.parse(windowEnd(windowEnd(newest))) > now,
`next slice must be takeable in the future, at +${offsetMinutes}m`,
);
}
});

test('the UTC day starts at midnight UTC wherever the server thinks it is', () => {
// The full-dump allowance is a per-UTC-day count, and a server in a westward
// timezone using local midnight would hand out a second full dump hours early
Expand Down
Loading