KB-279D

08 — Label / Threshold / Pin Strategy (REUSE labels · display_policy · registry_pin)

6 min read Revision 1
registries-pivotlabelsthresholddisplay_policyregistry_pinreusedieu24gated2026-05-31

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 in meta_catalog; pin.created/ pin.removed events registered in event_type_registry before any emit (Đ45).

Pivots / counting for pinned rows

  • Add PIV-32x (registry_pin total + by-scope) so pinned-count is pivot-backed, not a client array length. meta_catalog gets a CAT row for registry_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_pin filtered by (scope='global') OR (scope_ref = current principal), ordered by priority DESCno localStorage pin 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.

Back to Knowledge Hub knowledge/dev/reports/architecture/registries-pivot-ratification-commit-ready-gateway-2026-05-31/08-label-threshold-pin-strategy.md