Repository navigation
fix(persistence): enforce exact overlapping same-role membership share budget #616
Description
Finding
Membership owner #605 already enforces a composition-level invariant introduced through #610/#612: for one member and one membership role, the known simultaneously active shares may sum below one, may equal one exactly, but must never exceed one. The owner compares the exact values represented by binary64 inputs rather than ordinary f64 accumulation.
The persistence repair in #615 currently closes only the row-local (0, 1] mismatch. PostgreSQL can still persist two or more individually valid rows for the same observed unit and membership_type_code, with overlapping event-time validity and different targets, whose aggregate share exceeds one. Example: 0.75 + 0.5 on overlapping Department memberships. Such a stored state cannot be reconstructed into membership_core::MembershipNetwork without the owner rejecting it.
A naive SUM(membership_weight) <= 1 over the current PostgreSQL numeric column is also insufficient. The #612 binary64 boundary must remain authoritative. The decimal spellings
0.97346286672337940.00388510227154842580.02265203100507213
sum below one as decimal numeric, while the exact binary64 values represented by those inputs sum above one. Persistence must therefore evaluate the owner-equivalent binary64 values, not a decimal surrogate and not an ordinary floating sum.
Required RED
On #615 or its verified successor, before production repair, add live PostgreSQL contracts proving:
- same tenant + same observed unit + same role + overlapping validity:
0.75then0.5across different membership targets rejects the second write atomically; - exact unity
0.75 + 0.25remains valid; - sub-unity partial views remain valid and are not normalized;
- different roles are independent;
- disjoint event-time spells are independent;
- the scientific(membership): make same-role share-budget admission exact at the binary64 boundary #612 pathological binary64 triplet is rejected even though its decimal spellings sum below one;
- a rejected write does not mutate the persisted row set;
- concurrent writers for an initially empty same-member/same-role lane cannot both commit a budget overrun.
Causal repair boundary
- Keep Membership semantics in
membership_core; persistence only enforces parity at the storage boundary. - Do not rewrite
0006or0009; use the next forward migration after the active feat(persistence): persist idempotent analysis runs #287/fix(persistence): enforce membership weight and share budgets #615 stack. - Canonicalize persisted share admission to the binary64 value the Rust owner would receive. Do not use tolerance, clamp, renormalization, decimal-only
SUM, row duplication, or inferred complementary membership. - The cross-row check must serialize on the narrow
(tenant, observed-unit, role)authority before evaluating overlap. A plain cross-row trigger without serialization is race-prone when the first competing rows do not yet exist. - Preserve cross-classification: separate roles have separate budgets. Preserve time variation: non-overlapping spells do not share a budget.
- Any helper function/trigger must be covered by rollback, migration validation, direct SQL acceptance, and concurrency evidence. Do not hold an application transaction open while doing scientific computation.
Acceptance
Keep open until the surviving persistence owner has:
- public RED -> minimal causal forward migration;
- exact-head Rust/rustdoc/tests/line+branch+edge coverage;
- PostgreSQL 18 direct-write and concurrent-writer proof, including the scientific(membership): make same-role share-budget admission exact at the binary64 boundary #612 binary64 counterexample;
- Security/CodeQL/Semgrep/supply-chain gates and qualifying independent review;
- ordinary landing of prerequisite feat(persistence): persist idempotent analysis runs #287 followed by non-force restack and normal protected-main landing of the successor;
- downstream scientific(longitudinal): preserve time-varying cross-classified membership in CWC admission #604 consumer/recovery evidence after the Membership owner itself lands.
Activity
test(persistence): add exact overlapping membership budget RED (#616)
fix(persistence): enforce exact same-role share budget (#616)
fix(persistence): add membership share budget rollback (#616)
fix(persistence): exercise exact share-budget repair (#616)
fix(persistence): fold exact share budget into catalog (#616)
test(persistence): exercise catalog-owned share budget (#616)
seonghobae commentedon Sep 19, 2026
Owner-path update after the initial RED/candidate SQL:
49f1575528548005ba8967df10f3606eb46dacc9, still Draft/open/mergeable on feat(persistence): persist idempotent analysis runs #287.6668a161a312f546c58b5a5823e27b19bb3f29e6.0010_membership_same_role_share_budget.up.sql/ rollback remain56c9a89c.../db360788....7e48075bde8c95bc6cc62ec8615fcb8145330460now folds0010into the canonicalMigrationCatalog::from_embedded()after0009, with rollback before0009rollback; historical migrations were not rewritten.49f1575528548005ba8967df10f3606eb46dacc9updates the live contract to consume that embedded production path rather than applying candidate SQL out-of-band.This closes the catalog-integration sub-gap but does not make #616 GREEN. Fresh exact-head Rust Foundation run
35460543660is queued; hosted PostgreSQL 18 must still prove exact binary64 payload extraction, temporal overlap/disjointness, rollback and concurrent first-writer serialization. #287/#538 prerequisite and independent review remain intact.