Fix blank externalId filter and no-op object-group removal events - #77
Merged
Merged
Conversation
normalize_external_id collapsed both "filter omitted" and "filter explicitly blank/whitespace" to None. The SQL callers bind that as `col IS NULL OR col = $n`, so a caller who passed a blank externalId filter (a mistake, or an attempt to match nothing) got back the full unfiltered authorized/listed set instead of zero rows. Every Some(_) input now stays Some(_): blank, NUL-bearing, and over-cap values normalize to a sentinel longer than MAX_EXTERNAL_ID_LEN, which the length CHECK constraint guarantees no stored row can equal. Only a genuinely omitted None still means "no filter". Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
remove_entity_from_object_group, clear_entity_object_groups, and their resource counterparts discarded the DELETE's affected-row count and always published entity.object_group.remove / entity.object_groups.clear (and the resource equivalents), even when zero rows were deleted — e.g. removing a membership that never existed, or clearing an already-empty set. Event consumers were told a change happened when nothing changed. delete_entity_object_groups_in_tx and delete_resource_object_groups_in_tx now return the deleted row count, and the four *_with_audit callers skip the event publish (but still commit) when it's zero — the same shape already used by add_entity_to_object_group_with_audit / add_resource_to_object_group_with_audit for the additive side, and by remove_group_member_with_audit for principal-group membership. Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
ianmuchyri
added a commit
that referenced
this pull request
Aug 13, 2026
* Keep a blank externalId filter distinct from an omitted one normalize_external_id collapsed both "filter omitted" and "filter explicitly blank/whitespace" to None. The SQL callers bind that as `col IS NULL OR col = $n`, so a caller who passed a blank externalId filter (a mistake, or an attempt to match nothing) got back the full unfiltered authorized/listed set instead of zero rows. Every Some(_) input now stays Some(_): blank, NUL-bearing, and over-cap values normalize to a sentinel longer than MAX_EXTERNAL_ID_LEN, which the length CHECK constraint guarantees no stored row can equal. Only a genuinely omitted None still means "no filter". Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com> * Suppress object-group membership events on no-op removals remove_entity_from_object_group, clear_entity_object_groups, and their resource counterparts discarded the DELETE's affected-row count and always published entity.object_group.remove / entity.object_groups.clear (and the resource equivalents), even when zero rows were deleted — e.g. removing a membership that never existed, or clearing an already-empty set. Event consumers were told a change happened when nothing changed. delete_entity_object_groups_in_tx and delete_resource_object_groups_in_tx now return the deleted row count, and the four *_with_audit callers skip the event publish (but still commit) when it's zero — the same shape already used by add_entity_to_object_group_with_audit / add_resource_to_object_group_with_audit for the additive side, and by remove_group_member_with_audit for principal-group membership. Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com> --------- Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two P2 findings from review of #69, both confirmed real and fixed.
1. A blank
externalIdfilter matched the unfiltered set, not nothing.normalize_external_idcollapsed both "filter omitted" (None) and"filter explicitly blank/whitespace" (
Some(" ")) down toNone.The SQL callers (
authorized_object_ids's entity path, andidentity::repo::list_entities) bind that ascol IS NULL OR col = $n, so a caller who passed a blankexternalId— a mistake, or anattempt to match nothing — got back the full unfiltered
authorized/listed set instead of zero rows.
Fix: every
Some(_)input now staysSome(_). Blank, NUL-bearing,and over-cap values normalize to a sentinel longer than
MAX_EXTERNAL_ID_LEN, whichchk_entities_external_id_lengthguarantees no stored row can equal — the same "unmatchable value"
approach the function already used for NUL bytes. Only a genuinely
omitted
Nonestill means "no filter".2. No-op object-group removals published membership-change events anyway.
remove_entity_from_object_group,clear_entity_object_groups, andtheir resource counterparts (
authz::repo) discarded the DELETE'saffected-row count and unconditionally published
entity.object_group.remove/entity.object_groups.clear(and theresource equivalents), even when zero rows were deleted — e.g.
removing a membership that never existed, or clearing an
already-empty set. Event consumers were told a change happened when
nothing changed.
Fix:
delete_entity_object_groups_in_tx/delete_resource_object_groups_in_txnow return the deleted rowcount, and the four
*_with_auditcallers skip the event publish(but still commit) when it's zero — the same shape already used by
add_entity_to_object_group_with_audit/add_resource_to_object_group_with_auditon the additive side, andby
remove_group_member_with_auditfor principal-group membership.Test plan
cargo buildcargo test --lib(unit tests, no DB)cargo clippy --all-targets -- -D warningscargo fmt --checkagainst its own freshly migrated database, single-threaded
(
.github/workflows/rust.yml'srun_oneloop) — all green,including the new tests:
-
models::external_id::tests::normalize_keeps_omitted_and_blank_distinct-
m31_entity_external_id::a_blank_external_id_filter_matches_nothing_via_authorized_listing-
m31_entity_external_id::a_blank_external_id_filter_matches_nothing_via_list_entities-
m32_authorized_object_ids_filters::blank_external_id_matches_nothing_not_everything-
m30_group_membership_many_to_many::removing_a_membership_that_never_existed_publishes_no_event-
m30_group_membership_many_to_many::removing_an_existing_membership_still_publishes_its_event-
m30_group_membership_many_to_many::clearing_an_already_empty_entity_membership_set_publishes_no_event-
m30_group_membership_many_to_many::removing_a_resource_membership_that_never_existed_publishes_no_event-
m30_group_membership_many_to_many::clearing_an_already_empty_resource_membership_set_publishes_no_event