Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence
The repository wasn't mounted in this sandbox (only /workspace/problem.json), so I couldn't run the actual vitest suite. I reproduced and verified the exact cutoff arithmetic with an executable model — which reproduces the reported 300-minute (5 h) failure window precisely and proves the 48 h fix safe — and wrote the solution to /workspace/SOLUTION.md.
Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence
Module: apps/api/src/modules/assessment
Affected tests
- assessment.test.ts:502 — rejects starting attempt when quiz is past due and lateSubmissions is false
- assessment.test.ts:752 — rejects submission when quiz is past due and lateSubmissions is false
- gradebook.test.ts:446 — shows grade when showResultsAfter=after_due and dueDate has passed
Symptoms: AssertionError: the given combination of arguments (undefined and string) is invalid on expect(result.error).toContain('past due'), and expected undefined to be defined on the gradebook entry. Only fails when run ~00:00Z–05:00Z; green after.
t780 (DF-EDUOS-DEXDAT-COM-CO-22, commit 58ae08a9) made due dates calendar dates in Colombia (fixed UTC-05:00, no DST). endOfDueDayUtc() in submission-service.ts computes the cutoff as the end of the due date's day:
// D = UTC calendar date of the due timestamp; cutoff = D+1 04:59:59.999Z
function endOfDueDayUtc(due: Date): Date {
return new Date(Date.UTC(
due.getUTCFullYear(), due.getUTCMonth(), due.getUTCDate() + 1, // next day
4, 59, 59, 999, // 00:00 -05:00
));
}
Fixtures use new Date(Date.now() - 86400000) ("yesterday at this exact wall time"). With now = T and fixture UTC date D, the cutoff is D+1 04:59:59.999Z, and T ∈ [D+1 00:00Z, D+2 00:00Z):
now (UTC) |
cutoff vs now | fixture past due? |
|---|---|---|
00:00Z ≤ now < 05:00Z |
D+1 05:00Z > now |
No ❌ |
05:00Z ≤ now < 24:00Z |
D+1 05:00Z ≤ now |
Yes ✅ |
During 00:00Z–05:00Z it's still 19:00–24:00 the previous evening in Bogota — still inside the due date's Bogota day. The service is correct; the fixtures are time-of-day dependent. Verified reproduction:
now=00:00Z | 24h pastDue=false | 48h pastDue=true
now=02:00Z | 24h pastDue=false | 48h pastDue=true
now=04:00Z | 24h pastDue=false | 48h pastDue=true
now=05:00Z | 24h pastDue=true | 48h pastDue=true
Minute sweep of a full UTC day: bad24 = 300 (exactly the 5-hour window), bad48 = 0.
Change the three past-due fixtures to a 48 h lookback. Do not touch endOfDueDayUtc().
assessment.test.ts (both sites) and gradebook.test.ts:446:
- const dueDate = new Date(Date.now() - 86400000);
+ // 48h lookback: always past the Bogota end-of-day cutoff, regardless of run time-of-day.
+ const dueDate = new Date(Date.now() - 2 * 86400000);
Equivalent variants:
- new Date(Date.now() - 24 * 60 * 60 * 1000)
+ new Date(Date.now() - 48 * 60 * 60 * 1000)
- addDays(new Date(), -1)
+ addDays(new Date(), -2)
48 h is safe at every wall-clock time: if due = T - 48h has UTC date D, then T ∈ [D+2 00:00Z, D+3 00:00Z), so cutoff D+1 04:59:59.999Z is always strictly before T (≥ ~19 h margin). Proven per-second under both plausible date-extraction readings.
Sweep the rest of the suite (the lesson):
rg -n "86400000|24 \* 60 \* 60 \* 1000|Date\.now\(\)\s*[-+]" \
apps/api/src/modules/assessment/__tests__
rg -n "Date\.now\(\)\s*-|new Date\(Date\.now\(\)" apps/api/src --glob '*.test.ts'
Prefer centralizing into helpers (pastDueDate() => new Date(Date.now() - 2*86400000)). Don't blindly apply 48 h to future-date fixtures.
A. Deterministic math check (ran here):
function endOfDueDayUtc(due){const d=new Date(due);return new Date(Date.UTC(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate()+1,4,59,59,999));}
const past=(now,due)=>endOfDueDayUtc(due).getTime()<now.getTime();
const base=Date.UTC(2025,0,15); let bad24=0,bad48=0;
for(let m=0;m<1440;m++){const now=new Date(base+m*60000);
if(!past(now,new Date(now-86400000))) bad24++;
if(!past(now,new Date(now-2*86400000))) bad48++;}
console.log({bad24,bad48}); // { bad24: 300, bad48: 0 }
B. Targeted run:
cd apps/api
pnpm vitest run --no-file-parallelism \
src/modules/assessment/__tests__/assessment.test.ts \
src/modules/assessment/__tests__/gradebook.test.ts
C. Pin the clock into the danger window (real regression guard):
cd apps/api
faketime '2025-06-01 02:46:00' pnpm vitest run --no-file-parallelism \
src/modules/assessment/__tests__/assessment.test.ts \
src/modules/assessment/__tests__/gradebook.test.ts
If faketime is unavailable, add temporarily to each file:
import { beforeAll, afterAll, vi } from 'vitest';
beforeAll(() => vi.useFakeTimers({ now: new Date('2025-06-01T02:46:00.000Z') }));
afterAll(() => vi.useRealTimers());
Tests must now pass at 00:00Z, 02:46Z, 05:00Z, and 23:00Z.
D. Full suite: cd apps/api && pnpm vitest run --no-file-parallelism → 0 failed (was 3 failed / 4350 passed at 02:46Z).
endOfDueDayUtc() returning D+1 04:59:59.999Z is the intended DF-22 spec (a quiz due "today" stays open until 23:59:59 Bogota). Weakening it is a product regression. Fix the fixtures, not the spec.
When a suite gains a timezone/calendar-day cutoff convention, sweep every relative-date fixture for run-time dependence at the convention's day boundary, and pin at least one test to that boundary window (00:00Z–05:00Z for America/Bogota).
Caveat: I could not execute the actual vitest suite because the eduos repo is not present in this sandbox. The failure-window reproduction and the safety proof are from an executable model of endOfDueDayUtc; the diff locations/line numbers are from the problem report and should be confirmed against the checked-out files before applying.
# Evidence - Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-24T03:46:50.664Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "PROBLEM: 3 apps/api tests fail ONLY when the suite runs between ~00:00Z and ~05:00Z UTC: assessment.test.ts 'rejects starting attempt when quiz is past due and lateSubmissions is false' (:502), 'rejects submission when quiz is past due and lateSubmissions is false' (:752), gradebook.test.ts 'shows grade when showResultsAfter=after_due and dueDate has passed' (:446). Signatures: chai 'AssertionError: the given combination of arguments (undefined and string) is invalid' on expect(result.error).toContain('past due') and 'expected undefined to be defined' on the gradebook entry. ROOT CAUSE: t780 (DF-EDUOS-DEXDAT-COM-CO-22) introduced a date-only due-date convention where the submission cutoff is the END of the dueDate's UTC calendar date in America/Bogota (fixed -05:00, no DST) \u2014 endOfDueDayUtc() in submission-service.ts returns D+1 04:59:59.999Z for due date D. Fixtures built as new Date(Date.now() - 86400000) ('yesterday at this exact wall time') are therefore NOT past the cutoff when the suite runs 00:00Z-05:00Z (Bogota 19:00-24:00 of the due date), so the service correctly refuses to reject/hide and the assertions fail. Morning runs (after 05:00Z) are green \u2014 purely time-of-day dependent, zero product-code regression. FIX: make the 3 past-due fixtures use a 48h lookback (- 2*86400000) instead of 24h \u2014 verified safe for every wall-clock time (worst case now=00:00Z: now-48h cutoff D-1 05:00Z is always < now). Do NOT change the product cutoff logic \u2014 it is the intended DF-22 spec behavior (Colombia-fair due dates). LESSON: when a suite gains a timezone/calendar-day cutoff convention, sweep all relative-date fixtures for time-of-day dependence at the convention's day boundaries.", "environment": "eduos monorepo apps/api vitest suite on bunker-2cdce4d0 (pnpm, Node 22, isolated per-file workers)", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence", "provider": "openrouter", "solved_at": "2026-09-24T03:46:50.664Z", "version": "Beta 6ff97aae era (convention introduced t780 commit 58ae08a9)"}The repository wasn't mounted in this sandbox (only /workspace/problem.json), so I couldn't run the actual vitest suite. I reproduced and verified the exact cutoff arithmetic with an executable model — which reproduces the reported 300-minute (5 h) failure window precisely and proves the 48 h fix safe — and wrote the solution to /workspace/SOLUTION.md.
Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence
Module: apps/api/src/modules/assessment
Affected tests
- assessment.test.ts:502 — rejects starting attempt when quiz is past due and lateSubmissions is false
- assessment.test.ts:752 — rejects submission when quiz is past due and lateSubmissions is false
- gradebook.test.ts:446 — shows grade when showResultsAfter=after_due and dueDate has passed
Symptoms: AssertionError: the given combination of arguments (undefined and string) is invalid on expect(result.error).toContain('past due'), and expected undefined to be defined on the gradebook entry. Only fails when run ~00:00Z–05:00Z; green after.
t780 (DF-EDUOS-DEXDAT-COM-CO-22, commit 58ae08a9) made due dates calendar dates in Colombia (fixed UTC-05:00, no DST). endOfDueDayUtc() in submission-service.ts computes the cutoff as the end of the due date's day:
// D = UTC calendar date of the due timestamp; cutoff = D+1 04:59:59.999Z
function endOfDueDayUtc(due: Date): Date {
return new Date(Date.UTC(
due.getUTCFullYear(), due.getUTCMonth(), due.getUTCDate() + 1, // next day
4, 59, 59, 999, // 00:00 -05:00
));
}
Fixtures use new Date(Date.now() - 86400000) ("yesterday at this exact wall time"). With now = T and fixture UTC date D, the cutoff is D+1 04:59:59.999Z, and T ∈ [D+1 00:00Z, D+2 00:00Z):
now (UTC) |
cutoff vs now | fixture past due? |
|---|---|---|
00:00Z ≤ now < 05:00Z |
D+1 05:00Z > now |
No ❌ |
05:00Z ≤ now < 24:00Z |
D+1 05:00Z ≤ now |
Yes ✅ |
During 00:00Z–05:00Z it's still 19:00–24:00 the previous evening in Bogota — still inside the due date's Bogota day. The service is correct; the fixtures are time-of-day dependent. Verified reproduction:
now=00:00Z | 24h pastDue=false | 48h pastDue=true
now=02:00Z | 24h pastDue=false | 48h pastDue=true
now=04:00Z | 24h pastDue=false | 48h pastDue=true
now=05:00Z | 24h pastDue=true | 48h pastDue=true
Minute sweep of a full UTC day: bad24 = 300 (exactly the 5-hour window), bad48 = 0.
Change the three past-due fixtures to a 48 h lookback. Do not touch endOfDueDayUtc().
assessment.test.ts (both sites) and gradebook.test.ts:446:
- const dueDate = new Date(Date.now() - 86400000);
+ // 48h lookback: always past the Bogota end-of-day cutoff, regardless of run time-of-day.
+ const dueDate = new Date(Date.now() - 2 * 86400000);
Equivalent variants:
- new Date(Date.now() - 24 * 60 * 60 * 1000)
+ new Date(Date.now() - 48 * 60 * 60 * 1000)
- addDays(new Date(), -1)
+ addDays(new Date(), -2)
48 h is safe at every wall-clock time: if due = T - 48h has UTC date D, then T ∈ [D+2 00:00Z, D+3 00:00Z), so cutoff D+1 04:59:59.999Z is always strictly before T (≥ ~19 h margin). Proven per-second under both plausible date-extraction readings.
Sweep the rest of the suite (the lesson):
rg -n "86400000|24 \* 60 \* 60 \* 1000|Date\.now\(\)\s*[-+]" \
apps/api/src/modules/assessment/__tests__
rg -n "Date\.now\(\)\s*-|new Date\(Date\.now\(\)" apps/api/src --glob '*.test.ts'
Prefer centralizing into helpers (pastDueDate() => new Date(Date.now() - 2*86400000)). Don't blindly apply 48 h to future-date fixtures.
A. Deterministic math check (ran here):
function endOfDueDayUtc(due){const d=new Date(due);return new Date(Date.UTC(d.getUTCFullYear(),d.getUTCMonth(),d.getUTCDate()+1,4,59,59,999));}
const past=(now,due)=>endOfDueDayUtc(due).getTime()<now.getTime();
const base=Date.UTC(2025,0,15); let bad24=0,bad48=0;
for(let m=0;m<1440;m++){const now=new Date(base+m*60000);
if(!past(now,new Date(now-86400000))) bad24++;
if(!past(now,new Date(now-2*86400000))) bad48++;}
console.log({bad24,bad48}); // { bad24: 300, bad48: 0 }
B. Targeted run:
cd apps/api
pnpm vitest run --no-file-parallelism \
src/modules/assessment/__tests__/assessment.test.ts \
src/modules/assessment/__tests__/gradebook.test.ts
C. Pin the clock into the danger window (real regression guard):
cd apps/api
faketime '2025-06-01 02:46:00' pnpm vitest run --no-file-parallelism \
src/modules/assessment/__tests__/assessment.test.ts \
src/modules/assessment/__tests__/gradebook.test.ts
If faketime is unavailable, add temporarily to each file:
import { beforeAll, afterAll, vi } from 'vitest';
beforeAll(() => vi.useFakeTimers({ now: new Date('2025-06-01T02:46:00.000Z') }));
afterAll(() => vi.useRealTimers());
Tests must now pass at 00:00Z, 02:46Z, 05:00Z, and 23:00Z.
D. Full suite: cd apps/api && pnpm vitest run --no-file-parallelism → 0 failed (was 3 failed / 4350 passed at 02:46Z).
endOfDueDayUtc() returning D+1 04:59:59.999Z is the intended DF-22 spec (a quiz due "today" stays open until 23:59:59 Bogota). Weakening it is a product regression. Fix the fixtures, not the spec.
When a suite gains a timezone/calendar-day cutoff convention, sweep every relative-date fixture for run-time dependence at the convention's day boundary, and pin at least one test to that boundary window (00:00Z–05:00Z for America/Bogota).
Caveat: I could not execute the actual vitest suite because the eduos repo is not present in this sandbox. The failure-window reproduction and the safety proof are from an executable model of endOfDueDayUtc; the diff locations/line numbers are from the problem report and should be confirmed against the checked-out files before applying.
# Evidence - Problem class: vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-24T03:46:50.664Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "PROBLEM: 3 apps/api tests fail ONLY when the suite runs between ~00:00Z and ~05:00Z UTC: assessment.test.ts 'rejects starting attempt when quiz is past due and lateSubmissions is false' (:502), 'rejects submission when quiz is past due and lateSubmissions is false' (:752), gradebook.test.ts 'shows grade when showResultsAfter=after_due and dueDate has passed' (:446). Signatures: chai 'AssertionError: the given combination of arguments (undefined and string) is invalid' on expect(result.error).toContain('past due') and 'expected undefined to be defined' on the gradebook entry. ROOT CAUSE: t780 (DF-EDUOS-DEXDAT-COM-CO-22) introduced a date-only due-date convention where the submission cutoff is the END of the dueDate's UTC calendar date in America/Bogota (fixed -05:00, no DST) \u2014 endOfDueDayUtc() in submission-service.ts returns D+1 04:59:59.999Z for due date D. Fixtures built as new Date(Date.now() - 86400000) ('yesterday at this exact wall time') are therefore NOT past the cutoff when the suite runs 00:00Z-05:00Z (Bogota 19:00-24:00 of the due date), so the service correctly refuses to reject/hide and the assertions fail. Morning runs (after 05:00Z) are green \u2014 purely time-of-day dependent, zero product-code regression. FIX: make the 3 past-due fixtures use a 48h lookback (- 2*86400000) instead of 24h \u2014 verified safe for every wall-clock time (worst case now=00:00Z: now-48h cutoff D-1 05:00Z is always < now). Do NOT change the product cutoff logic \u2014 it is the intended DF-22 spec behavior (Colombia-fair due dates). LESSON: when a suite gains a timezone/calendar-day cutoff convention, sweep all relative-date fixtures for time-of-day dependence at the convention's day boundaries.", "environment": "eduos monorepo apps/api vitest suite on bunker-2cdce4d0 (pnpm, Node 22, isolated per-file workers)", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "vitest-past-due-fixture-bogota-eod-cutoff-tod-dependence", "provider": "openrouter", "solved_at": "2026-09-24T03:46:50.664Z", "version": "Beta 6ff97aae era (convention introduced t780 commit 58ae08a9)"}