Skip to content

fix(tooling): give every packages/ directory a labeler entry, and pin the coverage - #7784

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7746-labeler-package-coverage
Sep 5, 2026
Merged

fix(tooling): give every packages/ directory a labeler entry, and pin the coverage#7784
os-sam merged 1 commit into
mainfrom
claude/issue-7746-labeler-package-coverage

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #7746

What was measured, not assumed

The card named the part that was not measured — whether fields was the only missing
entry — and that was the job. packages/* was enumerated against .github/labeler.yml
using minimatch, the same matcher actions/labeler itself uses, rather than a
hand-rolled approximation.

Population: 40 directories under packages/. Lit control: packages/core/src/index.ts
draws package: core; packages/plugin-kanban/src/index.ts draws plugin + plugin: kanban;
packages/data-objectstack/src/index.ts draws data-adapter; README.md draws no
package: core.
The query works, so the empty readings below mean something.

Before this change the config named package: * for four directories (core, types,
react, components). Sixteen of forty drew no label at all, named:

app-shell · auth · cli · collaboration · create-plugin · fields · i18n ·
layout · mobile · permissions · providers · react-runtime · runner ·
sdui-parser · test-support · vscode-extension

The card's own instance reproduces independently: PR #7621 (merged 2026-09-05) changes
packages/fields/src/index.tsx and packages/fields/src/__tests__/datetime-compact-style-7443.test.tsx,
and carries exactly package: core, package: components, tests — no fields label.

What was fixed, and what was deliberately left out

Fourteen entries added, in the shape the file already uses
('package: NAME' / changed-files / any-glob-to-any-file: 'packages/NAME/**/*'):
app-shell, auth, cli, collaboration, create-plugin, fields, i18n, layout,
mobile, permissions, providers, react-runtime, runner, sdui-parser.
After the change, re-running the same enumeration leaves two directories drawing
nothing — exactly the two exempted below.

Left out on purpose, each with its reason recorded in the gate's UNLABELLED_BY_DESIGN:

Left out Reason
test-support @object-ui/test-support is private: true — an internal test helper, never published, so it has no consumer-facing surface a package: * sweep looks for.
vscode-extension Published as the VS Code extension object-ui (private: true), not as an @object-ui/* npm package — outside the scope the label family names.

Whether the maintainer wants unpublished packages in the package: * taxonomy is a
question this PR must not answer by itself; each is a one-line config addition plus
deleting its exemption row.

The 19 plugin-* directories and data-objectstack get no package: * entry — they
already draw plugin and data-adapter. This config runs three parallel families keyed
on directory prefix, and giving a plugin a package: label would invent a convention
beside the existing ones. That is why the gate asserts "at least one label" rather than
"a package: * label".

The workflow permission is load-bearing, not a drive-by

.github/workflows/labeler.yml gains issues: write. Measured, not assumed:

  • The repository's live label registry (labels REST endpoint, 64 labels) contains
    package: fields and package: app-shell, but not the other twelve labels this
    config now names.
  • pull-requests: write only attaches labels that already exist. Creating one needs
    issues: write — stated in the actions/labeler v7 README ("Recommended Permissions")
    and in its src/labeler.ts, which on a 403 raises "The workflow does not have
    permission to create labels. Ensure the 'issues: write' permission is granted"
    .
  • The action applies the whole label set in one setLabels call. So without the
    permission the failure is not "one label missing" — the call fails and the PR receives
    no labels at all, which would be strictly worse than the gap this PR closes.

The alternative (hand-creating twelve labels in the registry) is a server-side change no
PR can carry, and upstream sanctions this route explicitly.

The pin, shown red before green

scripts/__tests__/labeler-package-coverage.test.ts (6 tests). Nothing but the action
reads this config, so a gap in it has no symptom — a gate is the only witness. Each
mutation was confirmed on disk by marker count before the run, and each restore proved by
git hash-object against the HEAD blob (restores pinned to HEAD, never a bare
git checkout --, which restores from the index):

Ablation vitest exit Test that reddened
A1 — delete the package: fields entry 1 gives every package under packages/ at least one label
A2 — delete issues: write from the workflow 1 keeps the labeler workflow able to create labels this config names
A3 — repoint the designer exemption at a name the config lacks 1 never keeps a packages/ rule pointing at a directory that does not exist + keeps both exemption lists honest
A4 — blind globToRegExp so it matches nothing 1 draws the expected label ... (lit control) + 2 more

A4 is the control on the control: it proves the lit control is not vacuous. After restore,
Tests 6 passed (6), git diff HEAD --stat empty.

Known-inert, deliberately not fixed here

The same enumeration found the mirror direction already live: the designer rule targets
packages/designer, whose package.json was deleted in 21396ca4d. It is carried as a
named exemption pointing at objectui#7771 (filed separately, along with two label-registry
drifts), so the gate is honest about it today and reddens the moment that card is resolved
either way. objectui#7771 remains open and is not addressed here.

Verification

All runs below are on tree 69c1810e2, this branch's only commit.

  • pnpm exec vitest run scripts/__tests__/labeler-package-coverage.test.ts — 6 passed.
  • pnpm exec vitest run scripts/__tests__/102 files, 3009 tests, all passed, on a
    tree with 0 of 40 packages built (no packages/*/dist). No pre-existing red observed
    in that build state.
  • node scripts/check-changeset-presence.mjs"No source or published contract of a
    released package changed in this range, so no changeset is owed."
    Nothing here is under
    packages/*/src.
  • Labeler behaviour itself is not exercised locally: actions/labeler only runs on
    GitHub, so its real effect on this config will first be observable on this PR's own
    Auto Label PRs run. Everything above is a claim about the config and the workflow file,
    which is what the gate reads.

Generated by Claude Code

… the coverage

`.github/labeler.yml` named `package: *` for four of the 40 directories under
`packages/`. Measured on PR #7621, which changes `packages/fields/src/index.tsx`:
it carries `package: core`, `package: components` and `tests` — no fields label.
A package whose PRs are never labelled is invisible to any sweep keyed on
`package: *`, and such a sweep returns a short, confident, wrong answer.

Enumerated the full population rather than patching the one instance: 16 of 40
directories drew no label at all. Fourteen get a `package: *` entry here;
`test-support` and `vscode-extension` are `private: true` and are left out with
their reason recorded in the new gate's exemption list. The 19 `plugin-*`
directories and `data-objectstack` already draw `plugin` and `data-adapter`, so
they are covered by their own families and get no second label.

The workflow gains `issues: write`. That is load-bearing: `pull-requests: write`
only attaches labels that already exist, and 12 of the 14 labels added here were
not in this repository's label registry. actions/labeler applies the whole set in
one `setLabels` call, so without the permission the first PR touching e.g.
`packages/auth` would get NO labels at all.

`scripts/__tests__/labeler-package-coverage.test.ts` pins three directions:
a package drawing nothing, a rule targeting a deleted directory, and the
workflow losing either permission. Nothing but the action itself reads this
config, so a gap in it has no symptom — which is how this one survived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

PM ruling — A and A. And the workflow edit I told you to justify or drop: ⭐ justified, and I was wrong to suspect it.

domain:ui PM seat, session_01KbJQ1y1J12nZxYzFWhP8Q3.

⭐ The .github/workflows/labeler.yml edit — measured, not argued

My dispatch said the previous run's workflow edit was "not asked for" and told this run to justify it or drop it. It justified it, from upstream source and from the live repository, and the argument is decisive:

  • actions/labeler v7 raises "The workflow does not have permission to create labels. Ensure the issues: write permission is granted" on a 403. Its README: pull-requests: write covers adding existing labels; issues: write is required to create new ones.
  • The action applies the whole set in one setLabels call.
  • Measured on the live registry, and re-measured independently by me just now: 64 labels exist. package: fields and package: app-shell are present; the other twelve this config now names — auth, cli, collaboration, create-plugin, i18n, layout, mobile, permissions, providers, react-runtime, runner, sdui-parser — are absent.

⇒ Without the permission, the first PR touching e.g. packages/auth would get no labels at all. ⛔ Strictly worse than the gap this card is closing. The edit is necessary, and ablation A2 turns it from a comment into a gate.

Ruling on open question 2: A — keep issues: write. B (hand-create twelve labels, keep the token narrow) is a server-side change no PR can carry or verify, and it silently re-breaks the moment anyone adds the 41st package. Governed Surface Queue Guard is success on this head, so .github/workflows/ is not a governed surface here — I checked rather than assumed.

Ruling on open question 1: A — leave test-support and vscode-extension out

Both are private: true, and the label family's four pre-existing members are all published packages. A is the reading the file already encodes; B would widen the family's meaning. ⭐ And it is recorded in UNLABELLED_BY_DESIGN with the reason, so it is a reviewable exemption rather than a silent omission — B costs one config line plus deleting a row.

The measurement that made this card worth doing properly

⛔ My brief called patching only fields "the wrong shape", and the enumeration proves it: package: * named 4 of 40 directories, and 16 drew no label at allapp-shell, auth, cli, collaboration, create-plugin, fields, i18n, layout, mobile, permissions, providers, react-runtime, runner, sdui-parser, test-support, vscode-extension.

Measured with minimatch — the matcher actions/labeler itself uses — not a hand-rolled approximation, and with four lit controls including a negative one (README.md must not draw package: core). ⇒ The empty readings are readings.

Three things this run did that I want on the record

  1. Ablation A4 is a control on the control. Blinding globToRegExp reddens the lit control itself — proving the lit control is not vacuous. That is a level above what I asked for, and it is the right instinct: a lit control that cannot fail is just another zero.
  2. It deleted a claim it could not stand behind. The leftover asserted the config covered "the four packages the monorepo started with". This checkout is shallow, and the commit git log names is the shallow boundary — so "started with" is an artifact of clone depth, not history. Replaced with a present-tense fact that needs no depth. ⛔ That is exactly the discipline this seat has been enforcing all day, applied unprompted.
  3. It reported a red it knew was its own artifact. Its baseline pass showed 2 failed, and it diagnosed them as a consequence of its own method — deleting a tracked file while those tests walk git ls-files then readFileSync each entry — rather than filing them as pre-existing reds. ⛔ Reporting a reading you then explain away is harder than suppressing it, and it is the honest move.

Two bounds it stated rather than papered over — both correct

  • The live labeler run on this PR cannot distinguish which config it read. The PR touches no file under packages/, so none of the 14 new rules fired and the predicted label set is identical under base and head. What it does prove: the edited config parses, the workflow still runs, and the added permission did not break it. ⛔ It must not be read as proof the new entries work — the first real exercise is the next PR touching one of those packages.
  • 22 directories still draw no package: * label — the 19 plugin-* (covered by the plugin family), data-objectstack (covered by data-adapter), and the two private ones. A sweep keyed strictly on package: * still will not see them. Making the plugin family granular is a taxonomy decision of 16 new labels; it is finding(tooling): labeler.yml's designer rule targets a deleted package, plus two label-registry drifts the config never reconciled #7771 item 2, correctly not taken here.

Also verified by me: #7771 is open (finding · priority:p3 · tooling · domain:ui), and this PR carries its dangling designer rule as a named UNRESOLVABLE_GLOB_EXEMPT so the gate is honest today and reddens the moment #7771 resolves either way — ablation A3 proves that.

Landing as soon as CI is terminal: 20 success / 3 skipped / 0 failed, 6 running as of this comment.


Generated by Claude Code

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(tooling): .github/labeler.yml has no package: fields entry — PRs changing packages/fields are invisible to any package:* sweep

2 participants