fix(fnxc): the gate compared author stamps against ONE machine's calendar — five reds in two hours (#3277)

Root-cause fix for tonight's repeated red `main`, instead of repointing
stamps one at a time — **four PRs across three lanes did that in ninety
minutes** (#3261, #3269, and my #3263 and #3272, two of which I closed
as superseded by concurrent work).

## The defect

The fleet writes stamps from **many** machines; this gate evaluates them
on **one**.

#2941 fixed the case where the author sits **west** of the runner — a
correct 5pm-in-California stamp read as "tomorrow" under a UTC
comparison — by switching to the runner's **local** calendar. The mirror
case was left open, and that is what broke `main`:

| commit | landed (PDT) | = UTC | stamp written |
|---|---|---|---|
| `9094d1640e` | 16:12 | 23:12 Jul 31 | `2026-08-01-00:20` |
| `e52da740a5` | 16:32 | 23:32 Jul 31 | `2026-08-01-00:50` |
| `3f95c6d53e` | 16:40 | 23:40 Jul 31 | `2026-08-01-01:05` |

Those are **neither** the runner's local date **nor** UTC. They are the
*author's* local date in a UTC+1 container — and they are **correct** by
this project's own convention ("authors write the local date"). The
gate, running in PDT, called all three "tomorrow" and reddened `main`
for every other lane.

## The fix

A stamp is future only if it is ahead of **both** the local and UTC
calendar dates.

- Accepts both honest directions (author east or west of the runner).
- **Preserves #2941**, doesn't revert it — west of Greenwich the local
date is the earlier of the pair, so the 5pm-in-California case still
passes.
- Still catches an invented date: `scheduler.ts`'s `2026-08-06` stamp
(six days out) remains counted, and a mutation probe at `2026-09-15`
fails the gate.

## AGENTS.md corrected in the same commit

It still instructed **`date -u`**, which describes the *pre-#2941* gate.
That instruction is now the one that **produces** the failure from any
machine east of the runner — I followed it myself earlier tonight and
repointed stamps that were already correct. Rewritten to say: write your
own local date; the gate accepts anything not ahead of both calendars.

## Baseline

Auto-tightened for **37 files** — the gate's no-author drop path. Those
allowances were false positives carried since the UTC-only era, so this
**strengthens** the ratchet rather than widening it (`scheduler.ts` 2 →
1, keeping the genuinely-invented stamp counted).

## Verification

```
check-fnxc-future-dates           green
check-inert-sync-lane-conversions green
check-lane-wiring                 green
check-sql-column-literals         green
census --strict                   green

MUTATION: FNXC:MutationProbe 2026-09-15-10:00  → gate fails (real future dates still caught)
```

No changeset: tooling/gate + internal docs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 17:15:05 -07:00
committed by GitHub
parent 208c32c970
commit 1e7e6baef1
2 changed files with 22 additions and 2 deletions

View File

@@ -387,7 +387,7 @@ Note: the embedded main-content views Workflows (`_WorkflowEditorView`), Import
## FNXC_LOG comments:
- Please whenever you're working on a codebase. I want you to add comments describing the date of the change (must be in this format yyyy-MM-dd-hh:mm) and describing the requirements or the change in requirements that made you implement certain functionality.
- **Take the timestamp from `date -u`, not your local clock.** `check-fnxc-future-dates` validates against UTC, so a stamp written from a clock behind UTC is a FUTURE stamp the moment UTC rolls over — and `pnpm lint` passes locally, because the local date agrees with what you wrote. It surfaces only as a red `main` for everybody else. This cost the fleet four separate breakages in one day (`scheduler.ts`, a scheduler PG test, and `task-update.ts` twice, by different authors); every one was a real time on the wrong day. The gate also rejects an impossible clock time, so an hour above 23 or a minute above 59 fails for a different reason.
- **Write your own local date and a real clock time.** `check-fnxc-future-dates` accepts a stamp that is not ahead of BOTH the runner's local calendar and UTC, which is what makes it safe for a fleet whose machines sit in different zones. Do NOT reach for `date -u` to satisfy it: this instruction previously said to, back when the gate compared against UTC alone, and following it from a machine EAST of the runner writes tomorrow's date and reddens `main` for everybody else — five separate breakages in two hours on 2026-07-31, all invisible to their authors because `pnpm lint` passed locally. An earlier round of the same defect from the other direction (authors WEST of the runner, whose correct 5pm stamp read as "tomorrow" under a UTC comparison) cost four breakages in one day. Both directions are now accepted; a genuinely invented date — days out rather than hours — still fails. The gate also rejects an impossible clock time, so an hour above 23 or a minute above 59 fails for a different reason.
- I want you to write FNXC:Area-of-product in front of all your comments so they can be grepped.
- Most of this should be written as jsdocs but you can add short comments around for the important variables and more complex parts of the codebase.
- The idea is to encode the requiements of the system (especially software behavior, UX, and important technical decisions) into the code so it's clearer later why a certain piece of code was written.

View File

@@ -94,13 +94,33 @@ FNXC:FnxcStampHygiene 2026-07-31-03:40 (#2941 review): `toISOString()` is UTC, s
Greenwich it rolls the date forward for part of each day — a stamp written correctly at 5pm in
California read as "tomorrow" and failed the gate. Authors write the local date, so the comparison
has to use the local one.
FNXC:FnxcStampHygiene 2026-08-01-00:10 (five reds in two hours — LOCAL alone is not enough either):
The fleet writes stamps from MANY machines and this gate evaluates them on ONE. #2941 fixed the
author-west-of-the-runner case; the mirror case is an author EAST of it, and that is what broke main
five times in two hours. Measured: three direct-to-main commits landed at 16:12/16:32/16:40 PDT —
23:12/23:32/23:40 UTC on the 31st — carrying stamps of 2026-08-01-00:20/00:50/01:05. Those are
neither the runner's local date nor UTC; they are the AUTHOR's local date in a UTC+1 container. The
gate, running in PDT, called every one of them "tomorrow" and reddened main for every other lane.
So "future" cannot mean "after the runner's calendar". It means after EVERY calendar a correct
author could plausibly be writing from, which is bounded below by the runner's local date and above
by UTC (or vice versa west of Greenwich). Comparing against the LATER of the two accepts both
honest cases and still catches a genuinely invented date — the 2026-08-06 stamp in scheduler.ts,
six days out, fails under this rule exactly as it did before.
This preserves #2941's fix rather than reverting it: west of Greenwich the local date is the earlier
of the pair, so a 5pm-in-California stamp still passes.
*/
const now = new Date();
const today = [
const localToday = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, "0"),
String(now.getDate()).padStart(2, "0"),
].join("-");
const utcToday = now.toISOString().slice(0, 10);
/** The later of the two — a stamp is future only if it is ahead of both. */
const today = localToday > utcToday ? localToday : utcToday;
function scan() {
const counts = {};