Context
0213 decision 7 says a flag switches a feature on or off and an entitlement sells it. Nothing checked it, so a flag namedProPlanReportsFlag or a Flag({ plan: "pro" }) would quietly turn the flag system into a second, unaudited
entitlement engine that bypasses the plan catalogue (0363 decision 6). Wave 10 wrote the rule as an oxlint plugin at
@stackra/feature-flags/lint and left it disabled everywhere.
Decision
- What it checks.
feature-flags/no-plan-targetingreports:- a class that extends
Flag(...)whose name containsPlan,Tier,Edition,EntitlementorSubscription(singular or plural) as a PascalCase word; and - a key in the
Flag({...})options object that is one of those words, in any case.
- a class that extends
- What it does not check. It reads syntax only. It does not inspect flag targeting data stored at run time; that data is checked by the flags service, which refuses a targeting rule whose fact is a plan, tier, edition or entitlement fact.
- Level and preset. The rule runs at
errorin thebase.jsoncpreset of@stackra/oxlint-config, so every member gets it. It has no autofix: moving a flag to an entitlement is a design change. A justified exception is anoxlint-disable-next-linewith a-- reason:comment, which review reads. - Home. The rule’s source stays in
@stackra/feature-flags(entry./lint), next to theFlagkind it reads; the preset loads it as a jsPlugin. - Values. None. The five words are the commercial vocabulary of 0213 decision 7, not a business value; which features a plan sells stays plan-catalogue data (0363 decision 6), and flag targeting stays settings data.
Consequences
- A paid feature can only be gated by an entitlement, so the plan catalogue stays the one source of what a tier sells.
- A flag that happens to use one of the words for an unrelated meaning must be renamed or carry a reasoned disable.
Enforcement
planned —packages/tooling/oxlint/src/base.jsonc lists the plugin and sets feature-flags/no-plan-targeting to
error; the rule’s own tests sit in packages/state/feature-flags/src/lint/**/__tests__/. Until the preset change
lands, nothing runs it.
Supersedes and sources
Supersedes: none. Sources:notes/handover/build-wave10-2026-10-25.md (settings and flags builder, not built item 4
and question 1); packages/state/feature-flags/src/lint/constants/no-plan-targeting-rule.constant.ts.