fix(gate): the FNXC stamp gate never validated the hour, so 25:30 passed - #2995
Conversation
STAMP captures only the date and the future check compares that capture alone, so a stamp could carry any hh:mm. Four stamps on main already read -24:40 or -24:00 and a fifth -25:30 is in flight. AGENTS.md specifies yyyy-MM-dd-hh:mm. The stamp exists to make the FNXC record a readable chronology, and a time that cannot exist quietly costs it that. Counted alongside the future-dated population rather than as a second gate: same defect class, and one ratchet is cheaper to keep honest than two. The four existing stamps are normalized by clamping the impossible hour to 23 with minutes preserved, so relative ordering within each file survives. That is a normalization with a stated rule, not a claim about the true minute. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#2992 merged while this branch was open, putting 2026-07-30-25:30 on main. The extended gate flagged them in CI, which is the gate working -- it is the first thing it has caught that was not already there when it was written. Clamped to 23:30 by the same rule as the other four: hour to 23, minutes kept. Baseline deliberately NOT re-recorded. It is timezone-dependent -- a UTC run tightens 176 files relative to a UTC-7 run because stamps dated tomorrow are not future there -- so committing either machine's copy would hand the other a red gate. Main's committed baseline passes under both, verified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
97c4f4a to
b7e25d0
Compare
CI went red, and the gate was right — it caught something that wasn't there when I opened this#2992 merged while this branch was open, putting Rebased onto current The baseline is timezone-dependent, and I am deliberately not committing oneWorth recording, because it cost me the diagnosis and will cost the next person the same:
Same tree, same code — the date the run compares against decides whether a stamp dated tomorrow counts as future. So a baseline generated on my machine and committed would hand CI a different population, and vice versa. Committing either machine's copy is a way to make the gate wrong for everyone else. Main's committed baseline passes under both — verified by running each with the file restored in between, so neither run's self-write contaminated the other — so this PR does not touch it. That is also why the earlier Re-verified after the rebase: impossible-hour mutation still flagged, gate exit 0 under both That self-write behaviour is the same trait that let a stale baseline reach main earlier today. It is the single thing about this gate I would change next — writing on a drop while merely reporting on a rise means the file's contents depend on who ran it last and where they were sitting. |
|
Warning Review limit reached
Next review available in: 26 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
#3006) **`main` is currently red on the FNXC gate.** ``` $ node scripts/check-fnxc-future-dates.mjs # on origin/main scripts/reconcile-task-state-consistency.mjs: 2 future-dated FNXC stamp(s), baseline allows 0 scripts/lib/backend-db.mjs: 1 scripts/__tests__/reconcile-task-state-consistency.test.mjs: 1 exit 1 ``` #2994 carried four `2026-07-30-26:10` stamps. I flagged them on that PR before it merged; #2995 (the hour check) landed first, so the merge order turned the warning into a red gate rather than a red PR. Clamped to `23:10` — same rule as the nine before it: hour to `23`, minutes preserved, so ordering within each file survives. This is a normalization with a stated rule, not a claim about the true minute. **Verified:** FNXC gate exit 0, `reconcile-task-state-consistency` 8 pass / 0 fail. Comment-text only. ### Worth fixing at the source Thirteen impossible-hour stamps across six PRs in two days, and the hours climb — `24:40` → `25:30` → `26:10`. They are being written as a continuing sequence past midnight rather than read off a clock, which is a reasonable instinct and produces an invalid stamp every time. The trap is that the honest spelling does not work either: a genuine post-midnight stamp needs *tomorrow's* date, and the gate compares against the **local** calendar — so `2026-07-31-00:40` written from UTC-7 is future-dated and fails for a different reason. Clamping to `23:xx` is currently the only spelling that satisfies both, which is not obvious and is why this keeps recurring. If it recurs again, the fix is probably in the error message rather than more normalization PRs: the gate could name the valid range and the timezone it compares against, so the next author sees the constraint at the moment they hit it. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… on them `main` is red on `check-fnxc-future-dates`. The cause is four stamps reading `2026-07-30-26:10` — an hour that cannot exist. They are exactly what #2995 taught this gate to catch. That PR landed the hour validation (`00-23`) after #2999 had already merged these four, so the gate started reporting a defect that was sitting there rather than one anybody introduced afterwards. The guard is working; nothing had been checking before it. Corrected by literal normalisation — 26:10 on the 30th IS 02:10 on the 31st — which keeps the chronology the author was recording rather than flattening the stamps to an arbitrary in-range hour. AGENTS.md specifies `yyyy-MM-dd-hh:mm`, and the stamp's whole purpose is a readable why-does-this-exist trail, so the ordering is the part worth preserving. THE BASELINE TIGHTENING RIDES ALONG, and it is a date rollover rather than anything anyone did. Stamps written yesterday as `2026-07-31` were future then and were baselined as such; today they are past, so 176 files ratchet to zero. The gate rewrites the file as a side effect and exits 0, so leaving it uncommitted dirties the tree on every subsequent run for everyone — which is why it belongs in this commit rather than a later one. Re-recording on a decrease is the rule this gate and its siblings already state. Worth knowing about the design: this churn recurs whenever a day boundary passes with future-dated stamps in the baseline, and it shrinks only as people stop writing them — which is the behaviour the gate exists to produce. 93 files still carry a non-zero allowance, so the drain is not finished. MEASURED - gate red before, exit 0 after, and stable across two consecutive runs - baseline -176/+25 entries, all date-rollover - inert-seam, sql-literal, lane-wiring gates and the census: all green - the reconciler's own suite: green Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… on them (#3010) ## `main` is currently red on `check-fnxc-future-dates` Four stamps read `2026-07-30-26:10` — an hour that cannot exist. They're exactly what #2995 taught this gate to catch. That PR landed the hour validation (`00-23`) *after* #2999 had already merged these four, so the gate started reporting a defect that was already sitting there rather than one introduced afterwards. **The guard is working**; nothing was checking before it. ``` scripts/lib/backend-db.mjs:41 scripts/reconcile-task-state-consistency.mjs:8, :51 scripts/__tests__/reconcile-task-state-consistency.test.mjs:109 ``` Corrected by **literal normalisation** — 26:10 on the 30th *is* 02:10 on the 31st — rather than flattening them to an arbitrary in-range hour. AGENTS.md specifies `yyyy-MM-dd-hh:mm`, and the stamp exists to give a readable why-does-this-exist trail, so the ordering is the part worth preserving. ## The baseline tightening rides along, and it's a date rollover Stamps written yesterday as `2026-07-31` were future *then* and were baselined as such. Today they're past, so **176 files ratchet to zero**. Nobody did anything. The gate rewrites the baseline as a side effect and exits 0, so leaving it uncommitted dirties the tree on every subsequent run **for everyone** — which is why it belongs in this commit rather than a later one. Re-recording on a decrease is the rule this gate and its siblings already state. Worth knowing about the design, since I wrote it: this churn recurs whenever a day boundary passes with future-dated stamps in the baseline, and it shrinks only as people stop writing them — which is the behaviour the gate exists to produce. **93 files still carry a non-zero allowance**, so the drain isn't finished. If it stays noisy once those clear, the gate's fail-on-tighten contract is the thing to revisit, not the stamps. ## Measured | check | result | |---|---| | gate | red before, **exit 0 after**, stable across two consecutive runs | | baseline | −176/+25 entries, all date-rollover | | inert-seam · sql-literal · lane-wiring · census | all green | | reconciler's own suite | green | ## One correction to a claim I made earlier this session While investigating I reported the gate as hanging for 600s. It wasn't — the harness killed the process (exit 144) and the empty output made it look like a stall. The gate completes in seconds. Noting it because I nearly filed a performance bug against a healthy script. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…letion date (#3007) The **fifth** instance of the async-memo shape #2998 documents, and the one that survived #3001's sweep. `lifecycleDates` gates its `completed` value on `isCompleteColumn || isArchivedColumn` — both derived from the async `taskColumnFlags` prop — while listing neither: ```js }, [task.createdAt, task.executionCompletedAt, task.archivedAt, task.column, locale, lifecycleNowMs]); ``` First paint runs with the flags undefined, the role helpers fall back to the legacy ids, and on a board whose complete lane is named anything but `done` that answers false. The flags arrive, `task.column` has not changed, nothing recomputes, and the card renders **no "Completed <date>" line at all**. ## Why #3001's sweep called this covered That PR recorded `mergeSignature` as *"the last live site … nine persistent candidates, seven covered transitively or by a dependency that already carries the flags."* This memo was presumably in the covered pile, and the reasoning is nearly right: it **does** list a dependency that changes — `lifecycleNowMs`. But that value is driven by a timer scheduled with `millisecondsUntilNextLocalMidnight` (FN-8561, so compact date labels turn over at the viewer's midnight). **A dependency that changes once a day is not coverage for a value that must be correct on first paint.** The card shows no completion date for the rest of the session. That distinction is worth adding to the doc's property 2: *does a listed dependency change* is the wrong question — *does it change when the resolved value arrives* is the right one. ## Verification | state | result | |---|---| | clean | 2/2 pass | | revert the dep fix | **1 failed / 1 passed** | The control case (a `done` board) passes either way by design, so a failure in the renamed case means "renamed board", not "nothing renders". **One trap worth recording**, since it nearly cost me the finding: my first `completedLine()` used `time[datetime]:last-of-type`. When only the *Created* line renders, that selector returns **that** element — so the pre-resolution absence assertion silently passed against the wrong node. The test now matches on the element's own `Completed` label. A positional selector cannot express "this specific line is missing". `tsc -p tsconfig.app.json` 0 errors, lint clean, 8/8 across all three renamed-lane TaskCard suites. ## Note `main` is currently red on the FNXC gate for an unrelated reason (#2994's impossible-hour stamps landing after #2995); fixed in #3006. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
check-fnxc-future-dates.mjsvalidates the date portion of a stamp and never looks at the clock time:The capture stops before the hour, so a stamp may carry any
hh:mmand pass. Found while pre-flighting #2992, whose new comments read2026-07-30-25:30.It is not one typo
Four stamps already on
maincarry a clock time that cannot exist:Three separate authors, so this is the gate's blind spot rather than one person's slip — and #2992 adds two more, which is how I noticed.
AGENTS.md specifies
yyyy-MM-dd-hh:mm. The stamp's whole purpose is to make the FNXC record a readable chronology of why code exists; a timestamp that cannot exist quietly costs it that, and nothing was going to catch it.The fix
Hours
00-23, minutes00-59, counted per file alongside the future-dated population rather than as a separate gate — same defect class (a stamp that does not describe a real moment), and one ratchet is cheaper to keep honest than two.Mutations, both directions:
2026-07-30-25:002026-07-30-23:75475 known future-dated stamp(s), none added, exit 0On the four existing stamps
Normalized by clamping the impossible hour to
23, minutes preserved, so relative ordering within each file survives. That is a normalization with a stated rule, not a claim about the true minute —-24:40most plausibly meant "just past midnight", but writing2026-07-31-00:40would be future-dated against today's local calendar and fail the very gate this PR extends. Clamping keeps every stamp real, ordered, and non-future; the exact minute was already unrecoverable.Verified: FNXC gate exit 0, lane-wiring gate exit 0,
task-list-board-columns5/5, lint clean.Comment-only changes to the CLI files (stamp text inside FNXC blocks), so no behaviour change and no changeset.
Noted separately on #2992 so its two new stamps get corrected there rather than landing and immediately failing this gate.