Skip to content

design(aggregator): ingest, fail-closed — one validation contract for every document the PM stack consumes #2576

Open
3 of 3 issues completed

Description

@dezren39

Design from the design-architect pass 18 (2026-10-30). Full design doc: files/designs/code-aggregator-ingest-contract.md in the keep-the-four-repos-healthy goal workspace. The first non-mcpx design in this series — the mcpx conformance inventory is nearly exhausted, and this is the largest un-designed cluster in the repo: over 100 open issues carry aggregator in the title, the validation family is thirty-plus strong, and eight more were filed today alone. Audited against origin/main 7c52ced1.

Problem

The aggregator (pm-stack/aggregator) consumes documents it does not produce — fleet feed files, quota exports, snapshot history, session lists, GitHub billing, Cloudflare exports — and almost every boundary validates some of its document and trusts the rest. The failure modes of the unvalidated remainder:

  1. One bad element discards everything — a mapper throws on a null element and the whole source vanishes for the run (aggregator(opencode): mapServerSessions never validates the v2 wire containers — a non-array data on any endpoint, or one null session entry, throws out of the mapper and drops every session from that server; an id-less entry fabricates an undefined-sessionId row #2543 sessions, aggregator(jules): mapJulesItems never validates the keeper state — one null or mistyped item discards every Jules session for the run, a string items fabricates phantom sessions #2531 Jules, aggregator(github-billing): billing items never validated — an impossible date discards the whole month's collection, negative amounts are stored as usage #2493 a whole month of billing over one impossible date).
  2. Malformed input is fabricated into rows — an id-less entry becomes an undefined-sessionId row (aggregator(opencode): mapServerSessions never validates the v2 wire containers — a non-array data on any endpoint, or one null session entry, throws out of the mapper and drops every session from that server; an id-less entry fabricates an undefined-sessionId row #2543); a string items fabricates phantom sessions (aggregator(jules): mapJulesItems never validates the keeper state — one null or mistyped item discards every Jules session for the run, a string items fabricates phantom sessions #2531); a failed R2 enumeration publishes r2Buckets: [] in a status: ok document (cloudflare-export: R2 bucket enumeration fails open — a 500/malformed buckets response publishes r2Buckets: [] in a status:ok document, and pagination past 100 buckets is silently dropped #2574).
  3. Out-of-range values publish as live measured data — a window at −20% or 1e9% appended as percent history against limit: 100 (aggregator(quota-gather-map): entryFromResult and entryFromFileExport never range-check percent — a window at -20% or 1e9% publishes as live ok data and appends as pct history against limit:100 #2571, aggregator(snapshot-records): recordsFromProviderUsage never range-checks used_percent — a Codex window at -20% or 1e9% is appended as measured pct history against its own limit:100 #2568); negative counts as live data (aggregator(quota-gather-map): entryFromTurso never range-checks usage — negative counts publish as live ok data and append negative snapshot records #2553); an unpriced addon as measured $0 (supabase-export: billedFromAddons silently zeroes unpriced selected addons (and accepts negative amounts) — a paid addon reports as measured $0 #2492); impossible dates (2026-02-30, 2026-13-01) stored and string-sorted into wrong positions (aggregator(snapshot-records): shape-only dates win freshestDate — a 2026-13-01 'Last checked' silently drops a pool's snapshot history, 2026-02-30 is stored as a phantom March date #2264, 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, aggregator(quota-extract): Claude and Meta reset dates are never validated — an impossible date is emitted verbatim as resetsAt, and a Feb-30 Meta reset silently discards the pool's whole read #2470).

Each fix is effort/xs and re-derives the same guards privately, so the guards disagree and the next boundary repeats the pattern — the same shape already designed away in sibling repos (openchamber #802, nix #1145).

Evidence

Decisions recorded

  1. One contract, validated once at ingest: shape, element types, ranges, and real calendar dates, before any consumer, store append, or render. Failure yields exactly one sanctioned outcome per boundary — reject and retain (feed sections, quota entries, store files), drop the element and count it (independent collection elements: sessions, billing items, pool entries), or refuse the append (snapshot records). Forbidden everywhere: throwing across the boundary discarding valid siblings; fabricating rows/ids/zeros; publishing out-of-range values as live data. A genuine zero is a successful validation whose value is 0 (the feat(quota-plane): browser-token adapter framework — managed credentials, fail-closed dashboard-API clients #1153 wording).
  2. One primitives module, pm-stack/aggregator/src/validate.ts, no new dependency: isRecord, isNonEmptyString, isFiniteNumber, isNonNegative, isPercentOf(limit), strict isIsoDate/parseIsoDate (component re-check — 2026-02-30 fails instead of rolling into March), arrayOf(guard) (drop-and-count), and a future-timestamp bound (5-minute skew; a future generatedAt is invalid, not maximally fresh, aggregator(health): dns/aa section stamps are never validated — a garbage dns generatedAt renders ageMs NaN/null, a future aa generatedAt renders age 0 (maximally fresh) for as long as the skew lasts #2242).
  3. Phases absorb the per-site queue: each phase names its issue family and closes, with evidence, whichever are still open when it starts; any already closed per-site is verified against the primitives (converted if it hand-rolled), never re-fixed.

Phases

Open questions stay in the design doc (projector/meta-exporter adoption across the ts/mjs boundary; a config clause for #2161; whether drop counts surface on the dashboard — watchdog #1260 is the candidate consumer; #1684's UTC-day ageing is a correctness bug in valid data, deliberately out of scope).

🤖 Design-architect pass 18 — agent note: this issue is the design of record; phases are its sub-issues. Do not re-derive the decisions above in triage; flag genuine blockers here instead.

Acceptance criteria

  • The three implementation phases stay linked from this design issue until each merges or is explicitly declined with rationale recorded here.
  • Each phase meets its own acceptance criteria; no phase widens scope beyond the design doc (files/designs/code-aggregator-ingest-contract.md).
  • When all phases have landed or been declined, this issue closes with a summary linking the merged PRs.

Activity

  1. added
    priority/normalOrdinary priority (the default)
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 agent-note:v1 — design-architect pass 18 (2026-10-30): this is the design of record for the aggregator ingest-validation family (30+ open per-site issues). Phases #2577 (primitives + contract + probe matrix), #2578 (feed/snapshot boundaries, gated on #2577), #2579 (extractor/mapper boundaries, gated on #2577) are linked as sub-issues. The named per-site issues are absorbed by the phases per the design named-scope rule — triage of a named issue should check its phase before fixing per-site. Decisions are recorded in the body; do not re-derive in triage.

  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Program tending (2026-10-31, via #1352): Phase 1 of this epic, #2577 (validate.ts primitives — the ingest contract in the README), closed today (2026-10-31T11:04Z), recording it here since this thread's last activity (2026-10-30T05:34Z) predates the landing.

    Remaining per the epic's own phase list: #2578 and #2579, both still OPEN. Nothing blocked is recorded on the closed phase. Sub-issue state verified this run via gh issue view (3 sub-issues: 1 closed, 2 open).

  4. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Program tending (2026-10-31): Phase 2 (#2578, feed and snapshot boundaries) closed 2026-10-31T14:32Z, following Phase 1 (#2577) earlier today. Progress: 2 of 3 phases closed. Remaining: Phase 3 #2579 (extractor and mapper boundaries), still open. Not a completion proposal — just keeping the parent current.

  5. 3 remaining items

  6. 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
  7. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off (re-handoff)

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: repo queue worker lane code-a (repo-worker-code)
    • Session / ID: task 7961
    • Found via: queue claim, lane assignment opencode
    • Task: task 7961 (triage, design(aggregator): ingest, fail-closed — one validation contract for every document the PM stack consumes #2576) re-claimed by lane code-a; execution-lane assignment is opencode. Handed off — the laptop claimant launches the bound opencode session, which drives the task to its terminal state. Claim labels already synced. NOTE: this claim is attempt 6/6 at the max_attempts cap — if this lease expires unlaunched, task 7961 will need a max_attempts raise to become claimable again.
    • Expectation: claimant session picks up task 7961 within its lease.
  11. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  12. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: picked-up
    queue: repo-health-code-pilot
    task: 7961 (triage, worker repo-worker-code, lane c8b9355e-319a-4640-8096-7ad19480d151)
    by: openchamber-scheduled-lane
    at: 2026-11-04T03:05Z

    Fresh pickup check: issue #2576 is OPEN; no hard-stop labels are present and its claim labels (queue/claimed, status/in-progress, work/assigned) are already in sync. The design body says not to re-derive its decisions; I’m checking the current state of the three named phase issues and any live fix ownership before recording the triage verdict. No worktree or code changes started.

  13. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: triaged
    queue: repo-health-code-pilot
    task: 7961 (triage, worker repo-worker-code, lane c8b9355e-319a-4640-8096-7ad19480d151)
    by: openchamber-scheduled-lane
    at: 2026-11-04T03:11Z

    Triage verdict against current main head ece818d386cebcacfdb591e1d41695036d211d43 (latest commit 2026-11-04T02:55:49Z): issue #2576 remains LIVE because Phase 3 #2579 is still OPEN. Phase 1 #2577 is CLOSED (PR #3014 merged); Phase 2 #2578 is CLOSED (PRs #3035 and #3070 merged). The current claimed-task list shows existing implementation task #6900 for #2579, so no duplicate follow-up task was created.

    Ordering note for the Phase 3 owner: open PR #5126 (fix(aggregator): reject invalid file-export measuredAt, for #5118) also edits pm-stack/aggregator/src/quota-gather-map.ts and its test, adding an isIsoDate gate for entryFromFileExport. It is authored by dezren39; this triage did not modify it. Reconcile that adjacent change before touching the same mapper/function so the measuredAt guard is not duplicated or conflicted. PR #5126 currently reports checks SUCCESS with one lower-severity bot thread unresolved.

    Action: design decisions were not re-derived; #2576 stays open pending Phase 3. No branch/worktree/code edits/tests or new task were made. This triage is complete; lifecycle labels for task 7961 will be removed.

  14. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: done
    queue: repo-health-code-pilot
    task: 7961 (triage, worker repo-worker-code, lane c8b9355e-319a-4640-8096-7ad19480d151)
    by: openchamber-scheduled-lane
    at: 2026-11-04T03:12Z

    outcome: triaged. Issue #2576 stays OPEN pending Phase 3 #2579. Phase 1 #2577 and Phase 2 #2578 are CLOSED; existing implementation task #6900 remains the fix path. The parent issue's claim labels (queue/claimed, status/in-progress, work/assigned) were removed after task completion. Related PR #5126 was noted for coordination and left untouched. No follow-up task was created.
    bookkeeping: deferred to fleet reconciliation per the scheduled-lane protocol.

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/codeepicMeta-issue grouping multiple items (org Project Type=Epic)kind/designA design proposal (design-architect): multi-step, phased, decision-bearingpriority/normalOrdinary priority (the default)source/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions