Found while taking the measurement for #5899 (see PR #6284 for the full population). Filed unassigned as an observation, for PM triage. Not fixed there — out of that card's scope.
The defect
packages/fields/src/widgets/CapabilityMultiSelectField.tsx:82:
/** * objectui#2600 B5 — the curated platform capabilities are a FIXED, known set * … * (Mirrors @objectstack/spec/security `PLATFORM_CAPABILITIES`.) */constCURATED_CAPABILITY_LABELS=newSet(['manage_users','manage_org_users','manage_metadata','manage_platform_settings','setup_access','setup_write','studio_access',]);
It is used at line 157:
constnorm=name.replace(/\./g,'_');if(CURATED_CAPABILITY_LABELS.has(norm))returnt(`capability.label.${norm}`);returnbyName.get(name)?.label||name;The Set has 7 members. @objectstack/spec@17.2.0's PLATFORM_CAPABILITIES has 8:
manage_users manage_org_users manage_metadata manage_platform_settings
setup.access setup.write studio.access manage_sharing
Normalized (dots → underscores, which is what labelFor does), the difference is exactly one member: manage_sharing is missing from the local Set. The dotted-vs-underscore spellings are not drift — that transform is deliberate and performed at the call site.
Consequence: manage_sharing falls through to byName.get(name)?.label, i.e. the English label the sys_capability registry serves, and is never localized — while the seven capabilities beside it in the same picker are. That is the exact class the Mirrors comment was written to guard against.
Why nothing caught it
scripts/check-spec-symbol-derivation.mjs rule 2 exists to flag precisely this — a declaration whose doc comment claims spec alignment with no compile-time tie behind it. It could not see this one: both of its scanners skip non-exported declarations, and CURATED_CAPABILITY_LABELS is module-local. That hole is #5899's subject; with the export filter relaxed, this declaration is one of only two additional rule-2 findings in the whole tree, and the only one with measured drift.
Fix shape (not prescribing — this needs the same judgement #5899 does)
Derive the set from the spec instead of restating it, e.g. build it from PLATFORM_CAPABILITIES.map(c => c.name.replace(/\./g, '_')). That both closes today's gap and makes the next spec addition arrive automatically — and it retires the unbacked claim at the same time. If a curated subset is genuinely wanted (i.e. some platform capability should deliberately keep its registry label), that intent needs writing down, because the comment currently says the opposite.
Measured on a76b18cf2 against @objectstack/spec@17.2.0.
Related: #5899 (the instrument hole that hid it), PR #6284 (the census).
Found while taking the measurement for #5899 (see PR #6284 for the full population). Filed unassigned as an observation, for PM triage. Not fixed there — out of that card's scope.
The defect
packages/fields/src/widgets/CapabilityMultiSelectField.tsx:82:It is used at line 157:
The
Sethas 7 members.@objectstack/spec@17.2.0'sPLATFORM_CAPABILITIEShas 8:Normalized (dots → underscores, which is what
labelFordoes), the difference is exactly one member:manage_sharingis missing from the localSet. The dotted-vs-underscore spellings are not drift — that transform is deliberate and performed at the call site.Consequence:
manage_sharingfalls through tobyName.get(name)?.label, i.e. the English label thesys_capabilityregistry serves, and is never localized — while the seven capabilities beside it in the same picker are. That is the exact class theMirrorscomment was written to guard against.Why nothing caught it
scripts/check-spec-symbol-derivation.mjsrule 2 exists to flag precisely this — a declaration whose doc comment claims spec alignment with no compile-time tie behind it. It could not see this one: both of its scanners skip non-exported declarations, andCURATED_CAPABILITY_LABELSis module-local. That hole is #5899's subject; with the export filter relaxed, this declaration is one of only two additional rule-2 findings in the whole tree, and the only one with measured drift.Fix shape (not prescribing — this needs the same judgement #5899 does)
Derive the set from the spec instead of restating it, e.g. build it from
PLATFORM_CAPABILITIES.map(c => c.name.replace(/\./g, '_')). That both closes today's gap and makes the next spec addition arrive automatically — and it retires the unbacked claim at the same time. If a curated subset is genuinely wanted (i.e. some platform capability should deliberately keep its registry label), that intent needs writing down, because the comment currently says the opposite.Measured on
a76b18cf2against@objectstack/spec@17.2.0.Related: #5899 (the instrument hole that hid it), PR #6284 (the census).