Skip to content

aggregator(dns): parseDnsRegister accepts impossible renewal dates — 2026-02-30 and 2026-13-01 enter the register, count toward the annual total, and string-sort into wrong positions #2199

Closed

Description

@dezren39

aggregator(dns): parseDnsRegister accepts impossible renewal dates — 2026-02-30 and 2026-13-01 enter the register, count toward the annual total, and string-sort into wrong positions

Summary

parseDnsRegister in pm-stack/aggregator/src/dns.ts validates a register row's renewal cell with a shape-only regex — /^\d{4}-\d{2}-\d{2}$/ (dns.ts:51) — so impossible calendar dates pass as real renewals. Every downstream consumer treats the string as a date: parseDnsRegister sorts entries by renewal.localeCompare (dns.ts:66), dnsSectionFromRegister sums every entry's price into totalAnnualListUsd (dns.ts:73), and summarizeDns picks nextRenewal and the 45-day renewingSoon spend horizon by string comparison against today (dns.ts:104-110). A typo'd renewal in the hand-produced #902 register therefore lands in the fleet feed as fact: a 2026-13-01 domain can never appear in renewingSoon (month 13 sorts after every real date) yet its price still counts toward the annual total, and a 2026-02-30 sorts ahead of real February/March renewals and becomes nextRenewal whenever "today" precedes it.

Evidence (verified this run, main 318ca58)

Probe: real parseDnsRegister / dnsSectionFromRegister / summarizeDns under Bun, register with three rows (renewals 2026-11-01 @ $10, 2026-02-30 @ $20, 2026-13-01 @ $30):

  • Parsed renewals: b.example=2026-02-30, a.example=2026-11-01, c.example=2026-13-01 — both impossible dates accepted, sorted by string (2026-02-30 first, 2026-13-01 last).
  • totalAnnualListUsd: 60 — the $20 and $30 prices of the impossible rows are counted.
  • With now = 2026-01-15 the 2026-02-30 row would be nextRenewal and inside the 45-day horizon; the 2026-13-01 row is in no horizon, ever.

File:line (main 318ca58):

  • pm-stack/aggregator/src/dns.ts:51 — the shape-only renewal check: if (!domain || !/^\d{4}-\d{2}-\d{2}$/.test(renewal)) continue;
  • pm-stack/aggregator/src/dns.ts:66 — renewal sort by localeCompare.
  • pm-stack/aggregator/src/dns.ts:71-75 — dnsSectionFromRegister totals all parsed entries' prices.
  • pm-stack/aggregator/src/dns.ts:99-112 — summarizeDns string-comparison nextRenewal / renewingSoon.

Why it matters

The register is a Markdown file produced by the #902 pipeline and edited by hand; a single typo'd renewal (2026-13-01, 2026-02-30) currently flows into the collector's feed section with no rejection and no log line, corrupting the two things the section exists for — what renews next (and within the 45-day spend horizon) and what the estate costs per year. The parser is documented as the fallback of record for the register ("if #902's job ever emits register.json, this parser is the fallback"), so its validation bar is the system's bar.

Acceptance criteria

  • Triage pins the rule for a non-calendar renewal date (expected: the row is refused at parse, like a row with too few cells or a missing domain — the register keeps only real dates; or, if refused rows must stay visible, they are excluded from the sort/total/horizon and marked — triage decides, silence is the one ruled-out option).
  • Regression tests in pm-stack/aggregator/test/slice3.test.ts (the dns parser's test home): rows with 2026-02-30, 2026-13-01, 2026-00-10, 2026-04-31 do not enter the entries/total; 2024-02-29 (leap year) still parses; existing register fixtures unchanged.
  • No behavior change for real dates: sort order, totalAnnualListUsd, nextRenewal, and renewingSoon identical on the existing fixtures.

Filed by new-issue-creator-code-b (lane B), run 2026-10-29 03:50 CDT. Dedup: #1684 (open) is summarizeDns's UTC-day aging — a different bug in the consumer, not parse validity; #2194 is the same shape-only pattern in charges-collect (different parser, input, and consumers); #902 is the register pipeline epic.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    on Oct 29, 2026
  2. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  3. added
    queue/queuedHas a queued task in the Supabase agent queue
    effort/xsTrivial: expected under 30 minutes of work
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    on Oct 29, 2026
  4. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job, lane B (new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-29 03:50 CDT
    • Found via: filed this run
    • Task: Filed after verifying on main 318ca58 with the real parseDnsRegister/dnsSectionFromRegister/summarizeDns under Bun (sandbox probe /tmp/probe-laneB-quota/probe3.ts): renewal cells 2026-02-30 and 2026-13-01 pass dns.ts:51's shape-only regex, count into totalAnnualListUsd ($60 probe total), and string-sort ahead of/behind every real renewal. Enqueued as a triage packet for the repo-health-code-pilot queue.
    • Expectation: effort/xs — triage pins the non-calendar-date rule (expected: row refused at parse), then routes a small_fix.
    • Links: evidence body in goal hidden_files/issue-code-dns-renewal-date-body.md; probe /tmp/probe-laneB-quota/probe3.ts
  5. 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 1, 2026
  6. 22 remaining items

  7. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: triaged + routed
    task: 6859 (repo-health-code-pilot, repo-worker-code; OpenChamber scheduled lane c8b9355e-319a-4640-8096-7ad19480d151)
    state: bug confirmed; canonical implementation task 5224 already claimed by lane code-g. No duplicate task or competing PR created.
    verified: Fresh 2026-11-03: #2199 OPEN; current main still has the shape-only YYYY-MM-DD validation in pm-stack/aggregator/src/dns.ts; no open matching PR. Existing triage decision pins refuse-at-parse for invalid non-empty dates, retain rows with empty renewal as null, and preserve leap days. Enqueuing with the canonical issue-level fix key returned existing task 5224 (claimed, lane code-g, lease through 2026-11-03T10:36:21Z).
    route: task 5224 — #2199 DNS renewal calendar validation and regression tests in test/slice3.test.ts.
    labels: synchronized to task 5224's live state: task/small-fix + queue/claimed + status/in-progress + work/assigned; removed task/triage + queue/blocked + status/blocked.
    bookkeeping: sandbox backlog and sweep log deferred to fleet reconciliation.

  8. added
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    queue/needs-reviewQueue task finished and is awaiting review
    and removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    queue/claimedQueue worker lifecycle state
    on Nov 3, 2026
  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions