Repository navigation
Description
Problem
isCredentialLifecycleRow (pm-stack/aggregator/src/credential-lifecycle.ts:85-105, new in the credential-lifecycle module) is the gate sanitizeQuotaView uses to drop-and-count malformed credential-lifecycle rows before rendering (pm-stack/aggregator/src/feed.ts:617). Its two date fields are validated far more weakly than every other date gate in the same sanitize pass:
measuredAt(credential-lifecycle.ts:95) is checked only astypeof row.measuredAt === "string"— any string passes, including"not-a-date".expiresAt(credential-lifecycle.ts:97) is checked only asNumber.isFinite(Date.parse(row.expiresAt))—Date.parserolls impossible dates over and accepts non-ISO strings, so"2026-02-30"(→ Mar 2) and"March 3, 2026"both pass.
The sibling gates in the same file/pass use the strict contract: isQuotaFreshness requires isIsoDate(v.measuredAt) (feed.ts:502), isQuotaWindow requires isIsoDate(v.resetsAt) (feed.ts:474), and isIsoDate/parseIsoDate (pm-stack/aggregator/src/validate.ts:116) rejects impossible calendar dates by round-trip. This is the same defect family as #2199/#4835/#4858 and the validateStreamEvent finding (OB-2026-11-03-096), in a gate that was added after those fixes.
Verified (laptop bun probe at main b963332, /tmp/probe-credlc.log)
control valid: true
measuredAt garbage: true ("not-a-date")
measuredAt impossible 2026-02-30: true
expiresAt impossible 2026-02-30: true
expiresAt non-ISO March 3 2026: true
expiresAt garbage: false (only the fully-unparseable case is refused)
So a lifecycle row with a garbage measuredAt or an impossible/non-ISO expiresAt survives sanitization and renders (with a bogus measurement/expiry stamp) instead of being dropped and counted in droppedCredentialLifecycle, defeating the drop-and-count contract the pass documents (#2429/#3921/#4546 pattern).
Why it matters
The credential-lifecycle section exists to tell the user when a credential expires or has expired. A row whose dates were never validated can show a wrong expiry state/stamp with no signal that the row was malformed, while every neighbouring section drops such rows and surfaces the drop count.
Acceptance criteria
isCredentialLifecycleRowrefuses a row whosemeasuredAtis not a valid ISO date perparseIsoDate/isIsoDate(validate.ts), including impossible dates such as2026-02-30.isCredentialLifecycleRowrefuses a row whose presentexpiresAtis not a valid ISO date per the same contract (impossible and non-ISO strings refused).- Well-formed rows produced by
lifecycleForEntrystill pass unchanged. - Tests in pm-stack/aggregator/test/credential-lifecycle.test.ts (or the feed sanitize tests) cover: garbage
measuredAt, impossiblemeasuredAt, impossibleexpiresAt, non-ISOexpiresAt, and the valid control. sanitizeQuotaViewcounts such rows indroppedCredentialLifecycle(existing drop-and-count behaviour once the gate refuses them).
Activity
dezren39 commentedon Nov 3, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: handed_off
task 8071 (small_fix, lane code-j) claimed 2026-11-03 and assigned by the execution-lane system to the opencode lane (assigned_lane=opencode) — checkpoint handed_off recorded; the laptop claimant launches the bound opencode session to implement. Issue #4955 revalidated OPEN, no hard-stop labels; its body already carries the verified probe (garbage/impossible measuredAt and impossible/non-ISO expiresAt all pass the gate at main b963332) and the exact fix contract (isIsoDate/parseIsoDate from validate.ts for both fields + tests in credential-lifecycle.test.ts).
dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off
opencode; re-handing off to the laptop claimant (earlier handoff at 15:22Z expired unlaunched).dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 4, 2026
🤖 Agent note · escalated
This item carries queue/claimed + work/assigned + status/in-progress, but nothing has moved in ~13h against a 2h (effort/s) expectation. Adding user/review per the lifecycle protocol §6 escalation rule. Note: the repo-queue Supabase backend is in an outage window today (code#5370), so the monitor is NOT requeueing from GitHub alone this run — if the holding session is done, the next monitor run after DB recovery will reset the labels and requeue; if you are still holding this, please checkpoint now and the escalation clears.