Skip to content

core: give the pass verdict one definition#72

Merged
fosskar merged 1 commit into
mainfrom
pass-verdict
Jul 25, 2026
Merged

core: give the pass verdict one definition#72
fosskar merged 1 commit into
mainfrom
pass-verdict

Conversation

@fosskar

@fosskar fosskar commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Found by an architecture review of the engine loop, then validated by ablation — deleting each guard and running the whole suite — before any code changed.

The problem

One question — "were this agent's sessions fully seen this pass?" (issue #49) — was written out four times in SessionFilesystem:

fn frozen(key)             → incomplete_agents.contains(agent) || resolver.agent_failed(agent) || excluded_parts(..)
fn tombstone_withheld(key) → incomplete_agents.contains(agent) || resolver.agent_failed(agent)
fn state_retained(key)     → incomplete_agents.contains(agent) || resolver.agent_failed(agent)
fn pass_complete()         → incomplete_agents.is_empty() && !resolver.any_agent_failed()

Three were re-exported through SessionPass so Engine::tick_once could remember to call each in the right spot — before executing a tombstone, before settling the recipient fingerprint, before pruning carried state. Forgetting one is silent data loss: a local path-map failure tombstoning healthy sessions on every peer, which is the bug class #49 exists to prevent.

Validation: every engine-side guard was untested

Delete the guard, run cargo test --workspace:

guard load-bearing? caught by any test?
frozen (projection) yes excluded_projects_neither_publish_nor_materialize
tombstone_withheld (lib.rs:353) no — unreachable
state_retained (lib.rs:396) yes
pass_complete (lib.rs:387) yes

tombstone_withheld can never fire: frozen already strips a blind agent's keys out of the reconcile index, and Action::Tombstone is only emitted for a key that has an index entry (reconcile.rs:275-286). Since begin_pass snapshots before projecting, the two always agree within a pass.

The change

Name the predicate once and express the rest over it:

/// Whether this pass could not see `agent`'s sessions completely: its scan
/// aborted or its path-map resolution failed (#49). The one definition …
fn agent_incomplete(&self, agent: &str) -> bool
  • frozen and state_retained are now one line over it; pass_complete keeps its cheap aggregate form, documented as the pass-wide form of the same question.
  • tombstone_withheld is removed, along with its SessionPass forwarder and the tick_once guard. Dead code that looks load-bearing is worse than no code: no test can exercise it, so it implies a coverage it does not have. The behaviour it nominally protected is now pinned by a test that survives refactors of either mechanism.
  • The session_filesystem tests that asserted tombstone_withheld now assert frozen — the same condition, and the mechanism that actually withholds.

Closing the coverage hole

Three new engine-level tests pin the consequences where they land:

  • frozen_agent_does_not_tombstone_a_peers_session
  • frozen_agent_keeps_its_carried_state
  • incomplete_pass_does_not_settle_the_recipient_set

Each was verified to fail when its mechanism is removed:

ablate state_retained  → CAUGHT by frozen_agent_keeps_its_carried_state
ablate pass_complete   → CAUGHT by incomplete_pass_does_not_settle_the_recipient_set
ablate frozen          → CAUGHT by frozen_agent_does_not_tombstone_a_peers_session

The fixture runs two agents deliberately. With one agent, freezing it empties the local snapshot and reconcile's empty-dir wipe guard suppresses tombstones on its own — the first version of the tombstone test passed without the freeze doing any work. A healthy second agent keeps the snapshot non-empty so the test actually exercises the freeze.

One observation, deliberately not acted on

Ablation also showed the || resolver.agent_failed(agent) half of agent_incomplete is currently redundant: snapshot() mirrors resolver failures into incomplete_agents (session_filesystem.rs:538) in the same pass, so removing the disjunct changes nothing and no test notices.

It stays. It names a real failure mode, and making the verdict depend on that mirroring would couple the definition to an implementation detail of snapshot. Flagging it in case you disagree.

Verification

  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace -- --test-threads=1 — 258 passed, 0 failed
  • nix fmt -- --ci — 0 changed

No version bump: no user-visible behaviour change (refactor plus test coverage).

"This agent's sessions were not fully seen this pass" (#49) was written
out four times in SessionFilesystem — frozen, tombstone_withheld,
state_retained and pass_complete each re-derived
`incomplete_agents.contains(agent) || resolver.agent_failed(agent)` —
and three of them were re-exported through SessionPass for tick_once to
remember to call. Forgetting one is silent data loss: a local path-map
failure tombstoning healthy sessions on every peer.

Name it once as agent_incomplete and express the rest over it. The
per-key questions collapse to state_retained; pass_complete keeps its
cheap aggregate form. tombstone_withheld goes: frozen already keeps a
blind agent's keys out of the reconcile index, and Action::Tombstone
needs an index entry, so the guard could never fire — deleting it and
running the whole suite changed nothing.

Deleting each of the other guards also changed nothing, so pin the
consequences at the engine level, where they land:

  frozen_agent_does_not_tombstone_a_peers_session
  frozen_agent_keeps_its_carried_state
  incomplete_pass_does_not_settle_the_recipient_set

Each now fails when its mechanism is removed. The fixture runs two
agents on purpose: with one, freezing it empties the local snapshot and
reconcile's empty-dir wipe guard suppresses tombstones by itself, so the
tombstone test passed without the freeze doing any work.

The session_filesystem tests that asserted tombstone_withheld now assert
frozen — same condition, and the mechanism that actually withholds.
@fosskar
fosskar enabled auto-merge July 25, 2026 09:06
@fosskar fosskar closed this Jul 25, 2026
auto-merge was automatically disabled July 25, 2026 09:07

Pull request was closed

@fosskar fosskar reopened this Jul 25, 2026
@fosskar
fosskar enabled auto-merge July 25, 2026 09:08
@fosskar
fosskar merged commit bc66607 into main Jul 25, 2026
6 checks passed
@fosskar
fosskar deleted the pass-verdict branch July 25, 2026 09:15
@fosskar fosskar mentioned this pull request Jul 25, 2026
fosskar added a commit that referenced this pull request Jul 25, 2026
Three internal changes landed since v0.20.0 — no features, nothing
breaking — so PATCH per the pre-1.0 rule:

  #71 cli: single-source the systemd hardening set
  #72 core: give the pass verdict one definition
  #73 core: give the encrypted session store one owner

#71 is the only one that alters a shipped artifact: the unit written by
`ssync service install` now lists the sandbox properties alphabetically.
The property set is unchanged and systemd is order-insensitive here, so
behaviour is identical.

Bumping [workspace.package] version is the whole release: nix/effects.nix
tags v0.20.1 and generates notes once nixbot goes green on main.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant