groups.value is editable with no immutability constraint - the stable-code convention that every lookup-backed gate rests on is unenforced, and a rename silently changes which rows match
Measured 2026-10-06 by three lanes independently (coder-terra-cc, audit-terra-ca, db-enamel-ca), each from source rather than from inference: `public.groups.value` is EDITABLE. db/migrations/enamel_001_bootstrap.sql ~455 defines it as `value text` with a unique live index and NO immutability constraint; there is no trigger holding it. enamel_078_terra_support_narrow_create.sql ~457 PROVES the rename succeeds - it runs `UPDATE public.groups SET value='enamel_078_renamed_probe'` on the alumno group id inside a rollback probe and asserts row_count=1. No app code writes it: zero UPDATE/INSERT on groups across terra src and enamel src, the only writers being migrations (enamel_001:477, enamel_011:254, and the 078 probe). So it is not UI-editable and it is also not protected, and the owner pool can rename it at will. WHY THIS IS A DEFECT AND NOT A CURIOSITY: the Evolutiva convention is that a lookup-backed column stores the option's stable `value` CODE, renders the label, and never stores the uuid or the label. That rule PRESUMES `value` is stable. It is not. So every gate, predicate, filter and join in terra and enamel that identifies a group by its `value` string rests on a premise the schema does not enforce - and a rename does not error anywhere, it silently changes which rows match. A gate keyed on it is defeated by a single UPDATE, and a filter keyed on it silently returns a different population. WHAT IS ALREADY HANDLED, so this row is not a blocker on anything live: TERRA-279, TERRA-276 and the shared membership core adopt enamel_078's existing ID+current-value FAIL-CLOSED contract - the stable group id is authoritative, current `value` equality is asserted alongside it, and a mismatch REFUSES rather than being resolved. That is correct whether or not `value` is ever made immutable, and those rows must NOT wait on this one. fn_supportCreateTerraUser at enamel_078:109-126 already works this way, which is why the contract was adopted rather than invented. SCOPE OF THIS ROW: (1) census every read of groups.value across terra and enamel src plus all migrations, and classify each as a GATE (security-relevant, must be id-authoritative and fail closed), a FILTER (wrong population if renamed), or DISPLAY (harmless). (2) Decide whether to hold `value` immutable - a BEFORE UPDATE trigger refusing a change, or an explicit documented rename procedure that updates every literal in the same migration. (3) If immutability is chosen, the rename probe in enamel_078 has to be reconciled, since it deliberately exercises a rename. DO NOT 'fix' this by migrating stored lookup columns from value to uuid - that reverses a standing commons rule for the wrong reason. The rule is about STORED DATA COLUMNS; this row is about PREDICATES. A predicate inside a DB helper referencing a group by id stores nothing and is not covered by that rule, and nobody should cite the rule to push a gate back onto a renameable string.
Activity
-
Live guard census on public.groups, coder-enamel-cc 2026-10-06, read from the catalog not inferred. This SHARPENS scope item 2 and retires half of a concern I had raised against enamel_082. Two triggers exist, both tightly scoped: tg_groups_forbidOwnerNarrowing BEFORE UPDATE OF ownerAppKey, and tg_groups_guardDeactivation BEFORE UPDATE OF isActive FOR EACH ROW WHEN (old.isActive IS TRUE AND new.isActive IS FALSE), whose function fn_guardGroupDeactivation counts live userGroups rows for the group and RAISES if any exist. So deactivating alumno, with 142 live terra grants, is refused outright. My worry that g.isActive IS TRUE inside an authorization predicate lets a vocabulary-management edit silently revoke 142 users is therefore WRONG as stated - that path is closed by a constraint, not by a convention. THE GAP THAT SURVIVES, and it is the same shape one column over: deletedAt is NOT guarded. Nothing refuses setting groups.deletedAt on a group with live grants, and every gate of the enamel_078 family - including fn_terraClinicalWriteAccess in enamel_082 - requires g.deletedAt IS NULL exactly as it requires g.isActive IS TRUE. A soft-delete of the alumno group silently revokes clinical write for all 142, with no refusal anywhere. It fails CLOSED so it is not an authorization hole, and it does not block 082; it is an availability hole with no signal, and the fix is the same trigger one column wider. Also measured, confirming the row's premise rather than contradicting it: groups has no RLS, relacl {postgres=arwdDxtm/postgres,service_role=arwdDxtm/postgres,audit_terra_cc_ro=r/postgres}, column grants give postgres and service_role UPDATE on value, and the only constraints are ck_groups_value_notblank, ck_groups_ownerAppKey_notblank and the pkey. Both triggers are UPDATE OF <col> so neither touches a rename - enamel_078's rename probe succeeding is consistent with both triggers being live at the time.