fix(gate): main is red — normalize #2994's four impossible-hour stamps (#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>
This commit is contained in:
gsxdsm
2026-07-31 00:16:39 -07:00
committed by GitHub
parent cd237ae760
commit 52a66297fc
3 changed files with 4 additions and 4 deletions

View File

@@ -106,7 +106,7 @@ test("apply reconciles done task and emits exactly one note", async () => {
});
/*
FNXC:OperatorScriptLaneAssumptions 2026-07-30-26:10:
FNXC:OperatorScriptLaneAssumptions 2026-07-30-23:10:
THE INVARIANT: both consistency checks ask the task's OWN lanes, and they failed in OPPOSITE directions.
Keyed on the literals, `hasDoneTransient` (`column === "done"`) NEVER fires on a renamed board, so a

View File

@@ -38,7 +38,7 @@ function ensureMigrationsStaged() {
}
}
/* FNXC:OperatorScriptLaneAssumptions 2026-07-30-26:10: exported so operator scripts can reach core
/* FNXC:OperatorScriptLaneAssumptions 2026-07-30-23:10: exported so operator scripts can reach core
helpers (lane resolution) through the SAME staged-dist seam `openBackend` already uses, rather than
each growing its own dist path — `@fusion/core` is not resolvable from the repo-root `scripts/`. */
export async function importCore() {

View File

@@ -5,7 +5,7 @@ import { openBackend, importCore } from "./lib/backend-db.mjs";
const DEFAULT_NOTE = "FN-4000 reconciliation: cleared stale transient failure state using TaskStore done-normalization so database and task JSON remain synchronized.";
/*
FNXC:OperatorScriptLaneAssumptions 2026-07-30-26:10:
FNXC:OperatorScriptLaneAssumptions 2026-07-30-23:10:
Both checks ask this task's OWN lanes, because keyed on the literals they fail in BOTH directions.
`hasDoneTransient` gated on `column === "done"`. On a board whose complete lane is named anything
@@ -48,7 +48,7 @@ export function findTaskStateInconsistencies(task, lanes = {}) {
}
/*
FNXC:OperatorScriptLaneAssumptions 2026-07-30-26:10:
FNXC:OperatorScriptLaneAssumptions 2026-07-30-23:10:
`resolveLanes` is INJECTED, not built here, and `main` below supplies the real one.
Resolving inside this function would drag `importCore()` — and therefore a built `packages/core/dist`