08 — Label / Threshold / Pin Strategy (REUSE labels · display_policy · registry_pin)
title: 08 — Label / Threshold / Pin Strategy date: 2026-05-31 gate: RG5 (+ Đ32 for pin). Rehearsed valid; commit DEFERRED.
08 — Label / Threshold / Pin Strategy
A. Labels — REUSE existing Đ24 machinery (no new SoT)
Verified live: label_rules 38 (facet_id, rule_type, condition jsonb, result_label, priority, **skip_wide_warning**, status); taxonomy_facets 10 (cardinality, **max_labels_per_entity**);
taxonomy 58 (hierarchical: parent_id, parent_facet, depth, scope[]); entity_labels 722,803;
species_collection_map 164 (discriminator_field/value/operator/config).
Strategy: grouping labels come from taxonomy/label_rules applied to entity_labels —
NEVER a hardcoded label array in Nuxt. A PIV-31x label-by-facet pivot (gated, P5) counts
entity_labels GROUP BY label_code so the 722,803 labels become pivot-backed (today uncounted by
any pivot). skip_wide_warning already governs wide-facet UX.
B. Threshold — the GAP and the fix
GAP confirmed: taxonomy_facets.max_labels_per_entity governs how many labels of a facet an
entity may carry — it is NOT an "ungrouped display ceiling." No column anywhere stores a
per-species 'max rows shown ungrouped' threshold. The literal 50 lives in the rehearsed
v_registry_label_grouping_required view and must move into data.
Fix — display_policy table (rehearsed valid, doc 14 E12):
CREATE TABLE display_policy (
species_code text PRIMARY KEY,
max_ungrouped smallint NOT NULL DEFAULT 50,
note text,
CONSTRAINT display_policy_ceiling_ck CHECK (max_ungrouped > 0 AND max_ungrouped <= 50) -- 50 = hard MAX
);
-- rows: ('__default__',50,'system default = MAX'), ('dot_tool',30,'smaller'), ('collection',50)
Rules encoded:
- 50 = MAX ungrouped ceiling, not a target. The CHECK forbids any policy above 50.
- Smaller allowed per species / list type (e.g. dot_tool=30).
- Default resolved from a
__default__PG row, not a literal in SQL/Nuxt. - A list must be classified (grouped via taxonomy/labels) before it becomes unmanageable —
when
list_count > resolved_ceiling, the surface requires grouping, it does not just paginate.
Live demo (rehearsal): of 160 leaves, 28 exceed their resolved ceiling (no literal 50 in the
query). Alternative home considered: a max_ungrouped column on entity_species/meta_catalog,
or reuse of the existing kg_thresholds (green/yellow/red by dimension) pattern. display_policy
is preferred (one concern, born+registered). No frontend thresholds anywhere.
C. Pin — confirm absent, finalize registry_pin (NEW, propose-only)
Verified: no pin/ghim/watch table exists (pg_class LIKE '%pin%/%ghim%/%watch%' → only PG
internals + kg_thresholds). So registry_pin is genuinely NEW.
Finalized shape (rehearsed valid; CHECK confirmed live):
CREATE TABLE registry_pin (
id serial PRIMARY KEY,
object_ref text NOT NULL, -- CAT code / pivot code / list / node key
object_kind text, -- 'registry'|'pivot'|'list'|'node'
surface_ref text NOT NULL, -- route/surface the pin shows on
pinned_by text NOT NULL,
scope text NOT NULL DEFAULT 'user',
scope_ref text, -- user/role/team id when scope<>global
reason text,
priority int NOT NULL DEFAULT 0,
active boolean NOT NULL DEFAULT true, -- soft-retire only (Đ0 Atom)
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT registry_pin_scope_ck CHECK (scope IN ('global','user','role','team'))
);
Governance (Đ37 / Đ32)
- global scope pins require approval (
approval_requests/apr_approvals, the reused Đ32 spine); user/role/team pins are self-service within scope. - Owner of the pin registry = an existing governance owner (Đ37); no new Mother.
- Soft-retire only (
active=false); births registered inmeta_catalog;pin.created/pin.removedevents registered inevent_type_registrybefore any emit (Đ45).
Pivots / counting for pinned rows
- Add PIV-32x (
registry_pintotal + by-scope) so pinned-count is pivot-backed, not a client array length.meta_cataloggets a CAT row forregistry_pin. - Pinned rows are an overlay/sort key on existing lists, never a separate count universe —
a pinned registry still counts once in
v_living_lists(no double-count).
UI contract
- Nuxt reads
registry_pinfiltered by(scope='global') OR (scope_ref = current principal), ordered bypriority DESC— nolocalStoragepin array (that would be hardcode/Đ28). - Pin/unpin = INSERT / soft-retire via API → PG; never client-only state.
D. Gate
Labels = REUSE (no gate beyond RG5 acceptance). display_policy + registry_pin = NEW, gated to
RG5 (+ Đ32 for global pins / pin births), macros P7 (threshold) and P8 (pin). Both
rehearsed clean (doc 04). Species-Collection Law v0.5 + Đ36 are DRAFT → label-grouping that depends
on them stays GATED_BY_APPROVAL.