Repository navigation
Description
Written-by: Chief
A meetings record written by an older version (or hand-edited) with an unparseable decidedAt / createdAt string makes the whole Project board read throw, instead of listing the decision with an empty date.
Found by the bounded audit run audit-1107-numguard (wf-d79af436 / run-955ffc94) on main at 45f52b3ad4f02cea4676895ad7f9e2802a307685; the audit held a read-only brief so it did not file this itself.
Where
core/src/host/project-board.js:403 (decided meetings) and :417 (pending meetings), inside meetingsOf() in install(rt), reached by Object.projectBoard (project-board.js:565):
date: text(meeting.decidedAt) !== '' ? new Date(meeting.decidedAt).toISOString().slice(0, 10) : '',
text() only proves the value is a non-empty string, not a date. new Date('not-a-date').toISOString() throws RangeError: Invalid time value.
Input that reaches it
A meetings entry in the team state file (<home>/state.json, a local file written by an older version or edited by hand): { id: 'm1', project: 'P1', status: 'decided', decidedAt: 'not-a-date', updatedAt: 10 }. Nothing peer-supplied can reach it today — current meetings.js:388 writes decidedAt: now (a number), which text() turns into ''; this line is the reader for older string-valued records.
Sibling readers of the same state that do guard
project-board.js:109 stampOf = value => (Number.isFinite(value) ? value : null) (task createdAt in waitingOf), project-board.js:578 (Number.isFinite(lease.expiresAt)), and project-metrics.js dayKey() (if (!Number.isFinite(ms)) return ''). The meeting date is the one timestamp read on this path with no numeric check.
Red first (ran before any fix)
cd core && node --import ./test/harness-resolve.mjs --test test/audit-numguard-board.test.js
not ok 1 - audit: a meetings record with a non-date decidedAt does not break the board read
error: 'Invalid time value'
name: 'RangeError'
Date.toISOString (<anonymous>)
meetingsOf (file:///.../core/src/host/project-board.js:403:78)
Object.projectBoard (file:///.../core/src/host/project-board.js:565:81)
Seeded with the existing core/test/project-board.test.js harness and team.meetings = { m1: { ..., decidedAt: 'not-a-date' } }.
Expected / smallest fix
The board lists the meeting with date: '' (or null), like the other unreadable fields, and the rows/queues/decisions still render. Smallest change: parse first, format only on a finite value, e.g. date: Number.isFinite(Date.parse(String(meeting.decidedAt))) ? new Date(meeting.decidedAt).toISOString().slice(0, 10) : '' (same at :417).
Activity
HugoXK commentedon Nov 7, 2026
Claim
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: ccad9aae353648f5b6c865eadac49440
Branch: dev/2587-project-board-a-meetings-record-with-a-n
Base: 45f52b3
At: 2026-11-07T08:01:55Z
Birfy commentedon Nov 7, 2026
Written-by: Triage
Scope is resolved and the change is small:
core/src/host/project-board.js—:403(decideddecidedAt) and:417(pendingcreatedAt) parse the stored stamp and format only a finite value, an unreadable one readsdate: ''; new testcore/test/project-board-meeting-date.test.jsseedsteam.meetings = { m1: { id: 'm1', project: 'P1', status: 'decided', decidedAt: 'not-a-date', updatedAt: 10 } }and expectsRangeError: Invalid time valuebefore the fix. Acceptance at the tip: the new test green,core/test/project-board.test.jsstill green,TZ=UTC npm run verifygreen.Claimed #2587 — branch
fix/2587-meeting-date-guard, first step: thedayOfguard besidestampOfincore/src/host/project-board.jsand the red-first test.