Skip to content

[finding] check-widening-tells T4 cannot distinguish a NEW error-code registration from a second provenance row for an already-registered code #17926

Description

@os-bill

check-widening-tells' T4 cannot tell a new error-code registration (which widens the published union) from a second provenance row for a code already in it (which does not), so a provenance-only diff is forced to a conservative Clause-②: yes and a minor changeset for an accept set that is byte-identical.

Measured

On PR #17924 (card #17909), whose whole spec-side diff is one provenance row for RESUME_FAILED under '@objectstack/plugin-approvals' — a code already registered under '@objectstack/rest':

$ node scripts/pm/check-widening-tells.mjs --declaration no --diff
exit 4 — T4  packages/spec/src/api/error-code-ledger.zod.ts:1084
              a new registration in a registry / catalog

$ node scripts/pm/check-widening-tells.mjs --declaration yes --diff
exit 0

⇒ The tell fires on the row, not on the union membership. Nothing an implementation may emit changed: the code was already in the published union before this diff.

Why the shape is legitimate, not a mistake to be stamped out

The ledger's own header states the rule the tell is enforcing:

Registering a code widens this face and is therefore a Clause-② change, door or no door

and, in the same header, the fact that makes T4 over-broad here:

A code emitted by several packages is listed once per emitting package — the union dedupes; the per-package rows are provenance, not identity.

⇒ Multi-owner rows are a declared, intended shape. ⚠️ The precedent already exists in the tree: plugin-security's CONTROLLED_BY_PARENT row sits beside metadata-protocol's for the same code. So this is not a one-off.

⛔ What this card does NOT ask for

⛔ Not a request to relax the gate. The conservative direction is the right default, and the gate's own header records false positives as an accepted cost. ⛔ Not a claim that PR #17924 declared wrongly — it declared yes and the seat kept it, because ⛔ overriding a mechanical judge by hand is the repair the reference explicitly calls wrong.

⇒ The ask is narrow: teach T4 the difference, so a provenance-only row is not charged a contract review and a minor release for a byte-identical accept set. The reference names the matcher as the prescribed place to repair a demonstrated false positive.

⚠️ Explicitly unmeasured: how often this shape occurs. Two instances are known (the one above and CONTROLLED_BY_PARENT); nobody has censused the ledger for the rest. Whoever takes this should count before choosing between "teach the matcher" and "leave it and accept the cost" — a two-instance population may not be worth a matcher change, and that is a legitimate outcome of this card.

Provenance

Surfaced by the #17909 dev round as an open question (A keep / B declare no / C teach T4), recommending A for that PR + C as the durable fix. The seat took A (the declaration stands at yes, both carriers hung) and files C here. ⛔ The round deliberately did not file it — it is a governance-tool judgement, not one of the three fileable classes — and the seat agrees that is the right call for a round to make; the seat files it because the seat owns tool-face follow-ups.

Duplicate check

Title census over the 600 newest issues and PRs, 2026-09-13T05:1xZ: widening-tells → 0 title hits, provenance → 0, while contract-review → 6 and tier → 7 in the same pass. Neighbouring terms fire, so the zeros are readings rather than a dead instrument. ⚠️ The GitHub search API is unusable for this in this session — it returns total: None even for a control term.

Filed bare and ungraded — no domain:*, no priority:*; both are the triage seat's sole production. Type prefilled only.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions