Skip to content

aggregator(provider-display): underscore strip breaks one-shot iterables and collapses renamed+plain providers to one display name #6891

Open

Description

@dezren39

aggregator(provider-display): underscore strip breaks one-shot iterables and collapses renamed+plain providers to one display name

Summary

The leading-underscore display strip added to providerDisplayNames in PR #6889 (commit 8dc7e75af7, the #1823 provider rename) introduced two defects in pm-stack/aggregator/src/provider-display.ts:

  1. The function now spreads its Iterable<string> parameter twice, so a one-shot iterable produces a garbage map. providerDisplayNames is declared over Iterable<string> (line 37), and the pre-rename implementation spread it exactly once. The new code spreads it at line 38 to build the stripped list and again at line 44 (const originals = [...names]) to recover the keys. For a one-shot iterable — a generator, a Map.keys() / Set.values() iterator — the second spread is empty, originals[i] is undefined, and the result is a single-entry map keyed by undefined. Every lookup misses, and providerDisplayName silently falls back to the unstripped canonical id.
  2. The underscore strip is not collision-aware, unlike the -direct rule in the same function. The -direct strip keeps the suffix when a plain base provider exists in the same set, expressly "so the two stay distinguishable" (module header). The underscore strip has no such guard: a set containing both the renamed id and its plain base — e.g. _claude and claude, or the new pool series _commandcode and the pre-rename series commandcode that the rename deliberately kept in the store — renders both providers under the identical display string.

Evidence

pm-stack/aggregator/src/provider-display.ts:37-58 at 4cd2927643 (origin/main):

export function providerDisplayNames(names: Iterable<string>): Map<string, string> {
  const list = [...names].map((name) => { ... });   // first spread (line 38)
  ...
  const originals = [...names];                      // second spread (line 44)

Verified this run on the laptop with the real file verbatim (md5 f16c0bdb00963613ca7b19399f667520, probe ~/git/fleet/probe-provider-display, bun 1.4.4):

Input Result
["_claude", "_antigravity.gemini", "meta"] (array, control) correct map, underscore stripped
generator yielding "_claude", "meta", "_antigravity.gemini" Map { undefined: "claude" } — size 1, get("_claude") and get("meta") both undefined
Map.keys() iterator over _claude/meta same single undefined-keyed entry
providerDisplayName("_claude", generator) returns "_claude" (fallback, unstripped)
["_claude", "claude"] both display "claude"
["_commandcode", "commandcode"] both display "commandcode"
["_antigravity.gemini", "antigravity.gemini"] both display "antigravity.gemini"
["openai", "openai-direct"] (contrast) -direct correctly kept: the existing rule is collision-aware

Control: the shipped suite pm-stack/aggregator/test/provider-display.test.ts passes 11/11 on the same tree (run via git archive origin/main) — it only ever passes arrays, and has no renamed+plain pair case.

All current call sites (render.ts:2167, render.ts:3364, render.ts:3714) pass arrays, so defect 1 is latent today — but the exported signature promises Iterable<string>, and the singular helper providerDisplayName(name, names) invites exactly the iterator inputs that break.

Why it matters

This module is the dashboard's one display-name rule, and its header states the contract: display strings change, ids/keys never do, and names that would collide stay distinguishable. Defect 1 breaks the type contract silently (no throw — wrong markup), and defect 2 makes two different providers/series indistinguishable on the quota board precisely in the migration window the rename created, where old and new ids coexist in the store by design.

Acceptance criteria

  • providerDisplayNames reads its input exactly once: a generator or Map.keys() iterator yields the same map as the equivalent array, keyed by the exact input strings.
  • When stripping the leading underscore would make a name's display equal to another distinct input's display in the same set (renamed + plain base both present, plain or dotted), the renamed name keeps a distinguishable display (mirroring the -direct rule's keep-on-collision behaviour); when there is no collision, the underscore is stripped as today.
  • New regression tests in pm-stack/aggregator/test/provider-display.test.ts cover: a one-shot iterable input, and a renamed+plain pair (_claude + claude and/or _commandcode + commandcode).
  • bun test test/provider-display.test.ts passes (existing 11 tests unchanged in behaviour).

Related: #1823 / PR #6889 (the rename that introduced the strip); #6125 (the original -direct display rule).

Activity

  1. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor
  2. 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/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 7, 2026
  3. dezren39 commented on Nov 7, 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-07T14:20Z
    • Found via: filed this run
    • Task: Fix providerDisplayNames so it reads its Iterable input once and keeps renamed+plain providers distinguishable on display collision (evidence and probe in the issue body).
    • Expectation: effort/s — done when one-shot iterables yield the array-equivalent map, collision pairs stay distinguishable, and new regression tests pin both cases.
    • Links: probe ~/git/fleet/probe-provider-display (laptop); queue packet enqueued to repo-health-code-pilot this run
  4. developing-today commented on Nov 8, 2026

    @developing-today
    Contributor

    🤖 Agent note · handed off

  5. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  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 8, 2026
  7. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 8, 2026
  8. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/aggregatorPackage/app: aggregator (pm-stack in this repo)app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)queue/parkedQueue task is parked (waiting on CI, time, or an event)source/creatorFiled by the new-issue-creator jobstatus/parkedDeliberately paused with a recorded resume conditionstatus:in-reviewtask/small-fixQueue packet kind: bounded fix with reproduction and targeted testswork/assignedQueue worker lifecycle state

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions