Repository navigation
Description
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-30first,2026-13-01last). totalAnnualListUsd: 60— the $20 and $30 prices of the impossible rows are counted.- With
now = 2026-01-15the2026-02-30row would benextRenewaland inside the 45-day horizon; the2026-13-01row 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 bylocaleCompare.pm-stack/aggregator/src/dns.ts:71-75—dnsSectionFromRegistertotals all parsed entries' prices.pm-stack/aggregator/src/dns.ts:99-112—summarizeDnsstring-comparisonnextRenewal/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 with2026-02-30,2026-13-01,2026-00-10,2026-04-31do 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, andrenewingSoonidentical 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
developing-today commentedon Oct 29, 2026
CODE-1085
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
22 remaining items
dezren39 commentedon Nov 3, 2026
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.
fix(aggregator): reject impossible DNS register renewal dates (#2199) (…
dezren39 commentedon Nov 3, 2026
🤖 Agent note · done