You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
build(id): voiding_scenarios tests fail at main — nixos-rebuild switch cannot complete (blocks code#3720 fold) #3787
nix build .#nixosConfigurations.amd.config.system.build.toplevel fails at current main: the id package's checkPhase fails in the voiding_scenarios test suite — 9 passed, 3 failed:
test_s2_mutual_removal_removes_both
test_s2_concurrent_re_add_is_voided
test_s2_three_cycle_removes_every_member
The lib-test compile breakage from PR #3738 was fixed by 47b0620; these are runtime test failures, present at e764f4e and still present at 51919e9 (no pkgs/id changes since). Full log: nix log /nix/store/4fzywg1xa5mxdlss9c7ik1drv4ic077r-id-0.1.0.drv (on the laptop).
Impact
Any nixos-rebuild switch of the laptop at main exits 1 — the toplevel cannot build (unit-id-primary.service → system-units → etc → toplevel all fail on the one dependency). This blocks the code#3720 flake-fold activation: two switch attempts on 2026-11-01 (08:59 and 09:54 CST, the second at e764f4e with this exact failure) both ended SWITCH_EXIT=1. The six folded systems (github-backup, worktree-reaper, quota exporters, dashboard-backup, oc-password-sync) remain on their restored plain-file units, timers verified active — service is unaffected, but the fold cannot land until this passes.
Unblock
Make the three voiding_scenarios tests pass in the nix build (they may behave differently under the sandbox than under cargo test). Afterwards the fold completes with one run of the staged runbook on the laptop (~/.local/state/flake-fold-backup-2026-11-01/README.md): pull, ~/run-switch.sh, verify.
Root cause. PR #3725 wired groups::fold into event_store::resolve() as a gate: the fold's member judge (Engine::member in pkgs/id/src/groups/fold.rs) evaluates each membership op against the fold's sequential current state, in canonical (causal + event-ID) order. For removals entangled with concurrent removals, that sequential pre-judgment contradicts the strong-remove concurrency semantics the membership layer owns (resolve.rs rule 2: authority judged at the op's causal position; the layer's mutual_removes exemption):
Mutual removal: A removes B applies; B's concurrent removal of A is then refused NotActive (B already struck in fold state) — but B authored it while active. Fold roster {A, C} instead of {C}.
Three-cycle: C's removal of creator A is refused NotAdmin (creator policy evaluated against post-removal state). Fold roster {A, C} instead of {}.
Concurrent re-add: C's removal of B is voided NotAMember (A's concurrent removal landed first), flipping the op's applied flag, and C's causally-later re-add of B is then handed to the fold's internal layer as a no-op, corrupting its causal frontier (Invalid: attempted to add a member who is already active).
The standalone membership scenarios (tests/membership_scenarios.rs, same scenarios) were green throughout — only the store-level suite, which runs through the fold gate added in #3725, failed. Reproduced identically with host cargo test (not a nix-sandbox effect), and the fold's wrong verdicts were measured directly with a fold-level harness before fixing.
Fix (pkgs/id/src/groups/fold.rs, +56/−2): when a removal is entangled — another removal in the same group, concurrent with it (neither in the other's causal past), names this op's author or its target member — the fold defers arbitration to the membership layer instead of pre-judging against sequential state. The concurrency test is the discriminator that preserves the pinned #168 behavior: a manager's unilateral removal of an Admin is still fold-refused, because the Admin's answering removal is causally later, not concurrent. Role changes remain Admin-only; friendships keep their dissolve path.
cargo test --test voiding_scenarios: 12 passed, 0 failed (was 9/3).
cargo test --lib: 1678 passed, 0 failed. Neighbors membership_scenarios (16), voiding_properties (7), production_ingest (11), content_removal (2): all pass.
Nix build test #1: nix build .#id-web — green; checkPhase log shows 41 test suites ok, 0 failed; all three S2 tests ok. Output /nix/store/qhnanvlf9sh3qy572ip6w00m2q5040x2-id-0.1.0.
Hermetic id-clippy-lint / id-rustfmt-check: still red, but every finding is pre-existing at main in files this PR does not touch (clippy: web/markdown.rs, web/world_ws.rs, world/tests.rs, web/identity.rs — tracked in id: clippy-lint red on main — expect_used at src/tags.rs:1484,1499 (clippy 1.99) #2401; rustfmt: 16 files, groups/fold.rs not among them). This PR introduces no new lint/format findings.
The unit-id-primary → toplevel cascade should now clear; the fold-activation switch sequence can resume once #3871 merges.
Completion note from the fold run: this issue's fix (#3871 → 2250410a) is verified in main and did its job — the switch build now gets past the voiding scenarios. The fold remains blocked by successor breakages that were queued behind this one: #3951 (vt100 dev-dep; fixed on main by d488f321c) and now #4015 (stale shell-contract expectation + sqlx-sqlite lock guard). Full record on #3720.
Activity
developing-today commentedon Nov 1, 2026
CODE-1526
fix(id/groups): let the membership layer arbitrate removals tangled i…
dezren39 commentedon Nov 1, 2026
Fixed — PR #3871
Root cause. PR #3725 wired
groups::foldintoevent_store::resolve()as a gate: the fold's member judge (Engine::memberinpkgs/id/src/groups/fold.rs) evaluates each membership op against the fold's sequential current state, in canonical (causal + event-ID) order. For removals entangled with concurrent removals, that sequential pre-judgment contradicts the strong-remove concurrency semantics the membership layer owns (resolve.rsrule 2: authority judged at the op's causal position; the layer'smutual_removesexemption):NotActive(B already struck in fold state) — but B authored it while active. Fold roster {A, C} instead of {C}.NotAdmin(creator policy evaluated against post-removal state). Fold roster {A, C} instead of {}.NotAMember(A's concurrent removal landed first), flipping the op'sappliedflag, and C's causally-later re-add of B is then handed to the fold's internal layer as a no-op, corrupting its causal frontier (Invalid: attempted to add a member who is already active).The standalone membership scenarios (
tests/membership_scenarios.rs, same scenarios) were green throughout — only the store-level suite, which runs through the fold gate added in #3725, failed. Reproduced identically with hostcargo test(not a nix-sandbox effect), and the fold's wrong verdicts were measured directly with a fold-level harness before fixing.Fix (
pkgs/id/src/groups/fold.rs, +56/−2): when a removal is entangled — another removal in the same group, concurrent with it (neither in the other's causal past), names this op's author or its target member — the fold defers arbitration to the membership layer instead of pre-judging against sequential state. The concurrency test is the discriminator that preserves the pinned #168 behavior: a manager's unilateral removal of an Admin is still fold-refused, because the Admin's answering removal is causally later, not concurrent. Role changes remain Admin-only; friendships keep their dissolve path.Verification (laptop, branch
fix/id-voiding-scenarios-3787@ fea193a):cargo test --test voiding_scenarios: 12 passed, 0 failed (was 9/3).cargo test --lib: 1678 passed, 0 failed. Neighborsmembership_scenarios(16),voiding_properties(7),production_ingest(11),content_removal(2): all pass.nix build .#id-web— green; checkPhase log shows 41 test suites ok, 0 failed; all three S2 testsok. Output/nix/store/qhnanvlf9sh3qy572ip6w00m2q5040x2-id-0.1.0.nix build .#id-web --rebuild— green again (41 ok, 0 failed) and reproduced the identical output path (deterministic).id-clippy-lint/id-rustfmt-check: still red, but every finding is pre-existing at main in files this PR does not touch (clippy:web/markdown.rs,web/world_ws.rs,world/tests.rs,web/identity.rs— tracked in id: clippy-lint red on main — expect_used at src/tags.rs:1484,1499 (clippy 1.99) #2401; rustfmt: 16 files,groups/fold.rsnot among them). This PR introduces no new lint/format findings.The
unit-id-primary → toplevelcascade should now clear; the fold-activation switch sequence can resume once #3871 merges.Merge pull request #3871 from developing-today/fix/id-voiding-scenari…
dezren39 commentedon Nov 1, 2026
Completion note from the fold run: this issue's fix (#3871 →
2250410a) is verified in main and did its job — the switch build now gets past the voiding scenarios. The fold remains blocked by successor breakages that were queued behind this one: #3951 (vt100 dev-dep; fixed on main byd488f321c) and now #4015 (stale shell-contract expectation + sqlx-sqlite lock guard). Full record on #3720.