Skip to content

scientific(membership): reject zero-share active affiliations #613

Open

Description

@seonghobae

Scientific/domain finding

MembershipWeight on #605 currently accepts finite [0,1], including +0.0 and -0.0. That is inconsistent with the canonical active-membership meaning and with TEPP's existing persistence boundary: MembershipAssignmentRecord::validate already rejects membership_weight <= 0.0, and migration 0006_typed_membership_assignment has CHECK (membership_weight > 0).

A zero-share edge therefore has no affiliation mass but can still enter MembershipNetwork, contribute an active group/role edge, alter classify_membership_design, and affect nested-ICC applicability. This is structural contamination, not an estimator tolerance question.

Required RED

On #605 or its verified successor:

  • MembershipWeight::new(+0.0) and MembershipWeight::new(-0.0) must return InvalidMembershipWeight;
  • transparent serde must reject zero as well;
  • the minimum positive binary64 subnormal (f64::from_bits(1)) remains valid so the repair does not invent an epsilon threshold;
  • 1.0 remains valid and existing finite >1, negative, NaN, and infinity refusals remain intact.

Repair boundary

Change the owner VO domain from [0,1] to (0,1]. Do not clamp, renormalize, use a tolerance, or reinterpret zero as missing data. #612 exact dyadic share-budget arithmetic remains separate and may still represent arithmetic zero internally; persisted/public MembershipWeight must remain strictly positive.

Persistence follow-up

Zero is already refused by both the Rust persistence record validator and PostgreSQL 0006. A separate persistence-owner gap remains for the upper bound: MembershipAssignmentRecord::validate and the database currently accept weights above one even though membership_core rejects them. Repair that through the active persistence lineage without rewriting historical migration 0006 or colliding with #287's reserved 0008_analysis_run_persistence migration.

Acceptance

  • public RED before production repair;
  • minimal (0,1] owner fix with unit/serde edge coverage;
  • exact-head Rust/rustdoc/test/coverage/security/CodeQL/Semgrep evidence and qualifying independent review before feat(membership): add owner-issued single-membership admission #605 lands;
  • keep this issue open through protected-main landing; source repair on a mutable Draft is not completion.

Activity

  1. seonghobae commented on Sep 19, 2026

    @seonghobae
    ContributorAuthor

    Source lineage is now concrete on #605. Public RED 3ee43cca54594eaba1c49324dd6fb55a3c00f49c rejects +0.0/-0.0 through both constructor and transparent serde while preserving f64::from_bits(1) as the smallest valid positive share. Causal repair 064d5e3e285c604a431f8b87ee504a1d1ad887f0 changes MembershipWeight to finite (0,1] and documents why zero-share active edges are structurally invalid. #605 is still Draft; exact-head Rust Foundation/Security/CodeQL are queued and Semgrep is pending, with no qualifying current-head approval. Keep #613 open through protected-main landing and downstream acceptance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions