Skip to content

pm-stack(aggregator): isCredentialLifecycleRow accepts garbage and impossible dates — measuredAt is typeof-only and expiresAt is Date.parse-only, unlike the isIsoDate gates in the same sanitize pass #4955

Open

Description

@dezren39

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 as typeof row.measuredAt === "string" — any string passes, including "not-a-date".
  • expiresAt (credential-lifecycle.ts:97) is checked only as Number.isFinite(Date.parse(row.expiresAt)) — Date.parse rolls 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

  • isCredentialLifecycleRow refuses a row whose measuredAt is not a valid ISO date per parseIsoDate/isIsoDate (validate.ts), including impossible dates such as 2026-02-30.
  • isCredentialLifecycleRow refuses a row whose present expiresAt is not a valid ISO date per the same contract (impossible and non-ISO strings refused).
  • Well-formed rows produced by lifecycleForEntry still pass unchanged.
  • Tests in pm-stack/aggregator/test/credential-lifecycle.test.ts (or the feed sanitize tests) cover: garbage measuredAt, impossible measuredAt, impossible expiresAt, non-ISO expiresAt, and the valid control.
  • sanitizeQuotaView counts such rows in droppedCredentialLifecycle (existing drop-and-count behaviour once the gate refuses them).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    on Nov 3, 2026
  2. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b run 2026-11-03 08:20 CST
    • Found via: filed this run
    • Task: isCredentialLifecycleRow date gates are weaker than the isIsoDate gates in the same sanitize pass — verified by laptop bun probe at main b963332; fixing is a small-fix (gate + tests).
    • Expectation: effort s; done when the gate refuses garbage/impossible/non-ISO measuredAt and expiresAt and tests cover it.
    • Links: queue packet enqueued this run (repo-health-code-pilot)
  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  4. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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).

  5. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  6. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  7. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: opencode-claimant
    • Session / ID: ses_e5e5d22f059fferQdZFcTPIugX · queue task 8071
    • Route: opencode-route/workhorse · build
  8. added
    user/reviewNeeds a user-look: Muse reviews first and handles what it can
    on Nov 4, 2026
  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · escalated

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: cron repo-fleet-monitor run 2026-11-04 04:12 CST
    • Found via: monitor reconciliation (in-progress tracker, Duty 3)
    • Task: escalation — item past 2x its effort/s deadline with no activity since handoff
    • Expectation: holder checkpoints, parks, or hands off; otherwise this is a requeue candidate once the queue DB is stable
    • Links: last holder note is the handoff of 2026-11-03 (opencode-claimant session); no comments, commits, or CI activity on this issue since

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/pm-stackpm-stack (Pad/Grist) in developing-today/codekind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientqueue/claimedQueue worker lifecycle statesource/creatorFiled by the new-issue-creator jobstatus/in-progressQueue worker lifecycle statetask/small-fixQueue packet kind: bounded fix with reproduction and targeted testsuser/reviewNeeds a user-look: Muse reviews first and handles what it canwork/assignedQueue worker lifecycle state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions