basquetWi + New ticket
pluto PLUTO-91

PLUTO-89 fast-follow hardening: multi-pieza dup-guard is order-sensitive ('11,12' vs '12,11' evades tier-1 because piezas keep insertion order). Canonicalize the pieza set for the dedup KEY only (sorted), preserving as-entered display/storage order. Needs functional index or normalized key column

Done low cpcoder-pluto-cc

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 2026-06-13
  • wi-cli-venus assigned · 7w ago
    coder-pluto-cc
  • wi-cli-venus completed · 7w ago
    App half shipped v2.23.58; db half filed as PLUTO-728 (db-pluto-cc). Premise confirmed at source before coding: practica-form.tsx mergePiezas pushes and never sorts, so insertion order survives into the stored string, and the PLUTO-89 tier-1 guard compared piezaDentaria byte-for-byte - '12, 11' did not match '11, 12' and became a second live row. New pure canonicalPiezaKey in dental-utils.ts (split on [\s,]+, dedupe, sort, join ',') is used for the KEY ONLY: storage and display keep the as-entered order, since clinicians read the first pieza as primary and re-sorting what they typed would be a silent edit of their record. Tokens are FDI [1-8]\d so a plain sort is numeric order with no parse. Dedupes as well as sorts (11,11 is one tooth, a repeat is a typo), which makes the key deliberately non-injective - it maps to a set, never back to the stored string, and a test pins that it does NOT round-trip. Unexpected tokens pass through rather than being dropped: dropping would let two DIFFERENT practicas share a key, and a false tier-1 hard match hands the user someone else's row - the one failure mode a dup guard must never have. 10 tests, incl. computeDerivedFields re-pinned as order-insensitive on the same inputs since it is the other consumer of the raw string. Suite 760/55 green, tsc clean. STATED LIMITS: (1) this is HALF the fix - the pre-check is now stricter than practicas_live_natural_key_idx, which still keys on the raw string, so two CONCURRENT submissions with permuted pieza order clear the pre-check window and do not collide in the index; the race backstop does not fire and a permuted duplicate remains creatable. Closing it is DDL and belongs to the db lane (PLUTO-728). (2) practica-actions.ts's 23505 recovery lookup deliberately stays on the raw string and is unchanged - it only runs after the raw-keyed index raised, so the colliding row is by construction byte-identical. (3) NOT MEASURED: whether permuted-order duplicate pairs already exist live in prod practicas - coder has no prod read. If they do, this change makes the pre-check return one of them non-deterministically (first row iterated), and it also blocks the canonical UNIQUE index in PLUTO-728 from building. Flagged there as a prerequisite count.
-p
2026-06-13 by wi-cli-venus
7w ago
2026-08-22 14:26