Skip to content

[quality] /members lists ExternalDNS twice and shows SNCF under a stale project name after the #1991 catalog fix #2014

Closed

Description

@hivecommons-hive

Finding

/members lists ExternalDNS twice in its "Filter by CNCF project"
dropdown — once as ExternalDNS and once as External-dns — and filtering
by ExternalDNS silently omits SNCF.

This is the defect PR #1991 ("list ExternalDNS once in the catalog project
filter", e2f5f26, 2026-10-29) fixed for /architectures. That PR normalised
data/architectures/catalog.json, but that file is an input to
scripts/generate-members.mjs
, and the dependent artifact
data/members.json was not regenerated in the same change. The member
directory still carries the pre-#1991 spelling.

Observed on main (cbffa80) from the committed data these pages render:

node -e "const m=require('./data/members.json').members;
const s=new Set();m.forEach(x=>x.projects.forEach(p=>s.add(p)));
console.log('External-dns:',s.has('External-dns'),'ExternalDNS:',s.has('ExternalDNS'));
console.log('ExternalDNS ->',m.filter(x=>x.projects.includes('ExternalDNS')).map(x=>x.name));
console.log('External-dns ->',m.filter(x=>x.projects.includes('External-dns')).map(x=>x.name));"
External-dns: true ExternalDNS: true
ExternalDNS -> [ 'Swisscom' ]
External-dns -> [ 'SNCF' ]

while data/architectures/catalog.json spells it ExternalDNS for both
sncf-onprem and swisscom-cloud-native-telco.

What a visitor sees:

Page Symptom
/members → Filter by CNCF project two options for one project: ExternalDNS and External-dns
/members → select ExternalDNS 1 organization (Swisscom); SNCF is missing
/members → SNCF card and profile project tag reads External-dns
/architectures/sncf-onprem the same project reads ExternalDNS

External-dns is also not the project's published name, so the two pages
disagree about what SNCF runs.

Root cause

data/members.json is generated from data/architectures/catalog.json and
data/awards.json, but nothing reconciles the committed artifact against
its committed inputs.
Regenerating on main today is not a no-op:

npm run generate:members

produces exactly one content change (everything else in the diff is Prettier
formatting, which the refresh lane applies):

sncf projects
  committed:   [… "External Secrets Operator", "External-dns", "Harbor" …]
  regenerated: [… "External Secrets Operator", "ExternalDNS",  "Harbor" …]

tests/generate-members.test.mjs exercises the generator against synthetic
fixtures, and tests/members-data.test.mjs pins only the member ordering
("generate-members.mjs emits members sorted by name; regenerate the file").
Neither compares the committed data/members.json with the committed
catalog, so a PR that edits the catalog without regenerating passes every
required check — which is what happened here.

The weekly Refresh end user members lane (47 3 * * 2) will overwrite the
stale value on its next run, but the duplicate dropdown option is live until
then, and the lane is not the gate: the same half-update will recur on the
next catalog edit.

Recommendation

One PR, two parts:

  1. Refresh the artifact through its existing lane — run
    npm run generate:members and npm run format (or trigger
    .github/workflows/refresh-members.yml via workflow_dispatch) and commit
    the resulting data/members.json.

  2. Add the missing reconciliation guard so this cannot recur, in
    tests/members-data.test.mjs beside the existing ordering test:

    test('member projects and industries match the committed catalog', () => {
      const catalog = readJson('data/architectures/catalog.json');
      const bySlug = new Map();
      for (const entry of catalog) {
        const list = bySlug.get(entry.slug) || [];
        list.push(entry);
        bySlug.set(entry.slug, list);
      }
      for (const member of members) {
        const entries = bySlug.get(member.slug) || [];
        if (entries.length === 0) continue;
        for (const field of ['projects', 'industries']) {
          const expected = [
            ...new Set(entries.flatMap((entry) => entry[field] || [])),
          ].sort();
          assert.deepEqual(
            [...member[field]].sort(),
            expected,
            `${member.id} ${field} drifted from data/architectures/catalog.json; run \`npm run generate:members\``,
          );
        }
      }
    });

    Run against main today this fails with
    sncf projects drifted from data/architectures/catalog.json, i.e. it
    reproduces the defect; it passes once part 1 lands. A member with no
    catalog entry (awards-only) is skipped, matching the generator, and the
    comparison is order-insensitive so it stays independent of the separate
    option-ordering question in [quality] Catalog filter dropdowns list lowercase project and industry names after every capitalised one #2013.

    Without this guard, the behaviour that regresses undetected is the one in
    the table above: a data/architectures/catalog.json edit that does not
    regenerate data/members.json leaves /members showing a stale project
    name and a duplicate filter option, and no required check notices.

Who can land this

Part 1 changes generated data and part 2 is test-only; the two must land
together or CI is red either way, so this is not split into two issues.
The quality lane opens PRs for tests/ only, so it cannot land part 1 —
this needs a human or a lane that may commit regenerated data. The commands
and the replacement text above are complete, so applying it is mechanical.

Priority

  • Impact: medium — user-observable on /members: a duplicated filter option,
    a filter that omits a matching organization, and a project name that
    contradicts /architectures/sncf-onprem.
  • Effort: low — one command plus one test.

Activity

  1. added
    testingApproved by a Hive merger/owner for auto-merge on green CI
    agent/qualityApproved by a Hive merger/owner for auto-merge on green CI
    stage/010-actionableTriaged; eligible for the hive to pick up
    and removed
    stage/010-actionableTriaged; eligible for the hive to pick up
    on Oct 30, 2026
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

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions