Root session traces and explicitly own pending node admissions

Merged
VA
3d ago
+1413-150

A shared physical Session retained its opening caller, cancelled routes could supply reconnect context, and ownerless dials could survive while unrelated or terminal cursors remained active.

Keep the cached pool context-free. Physical stream.session spans are explicit roots. Interests add causal edges only within a proven shared subscriber registry; equal numeric IDs from foreign subscribers are never passed to the session subscriber. Neither the Link nor a dial retains a caller span.

Track each queued Open as an owned entry containing its interest ID, destination node and cancellation state. The existing Cursor drop guard withdraws that entry; dispatch transfers ownership to the route. Home discovery atomically rebinds pending home entries. A terminal route's undrained output owns no admission, so it cannot preserve or authorize installation of a reconnect. Each node owns its pending future, which is dropped when no live route or explicit admission remains.

Validation

  • bb1 native session suite: 43 passed in 3.070 s, including blocked and completed home reconnects with B terminal and unpolled while A drops, overlapping A/B span closure, cancellation before queued removal, cancellation while B waits for Welcome, moved-node cancellation, and equal channel span IDs under overlapping foreign subscribers.
  • Real-code pool Loom/property suite: 20 passed in 0.565 s. The terminal-undrained schedule uses actual Closed handling, Cursor drop, explicit admissions, node cleanup, removal wake and completion guard. Restoring active > live_routes fails its ownership assertion. Home binding races insertion under the actual admissions lock, with a broken-rebinding negative control. Existing cancellation, initialization, credit, deadline and peer-isolation models remain enabled, with no exploration caps.
  • gRPC: 14 passed in 0.314 s. Native observation suite: 8 passed in 0.064 s, including the shrinking same/foreign-registry property and a raw foreign-ID negative control. Existing root-retention and unobserved fast-path controls remain enabled.
  • Final Linux log: /home/var/log/buildbox/2026-10-16T120222153Z-3753101.log.
  • Windows: lb.ps1 cargo check --locked -p acyclic-stream -p acyclic-native-runtime -p acyclic-grpc-observability --features acyclic-stream/local,acyclic-stream/http,acyclic-grpc-observability/test-support --lib --tests passed (initial compile 11.07 s, final revised test check 1.70 s, no slot wait).
  • The 10k qualification retains eight TCP connections/Sessions, zero actor channels and the existing linear encoded-byte budgets. Loopback eight-node delta p50/p99 at the initial reviewed head 7de0c2a31 was 1.749160 / 3.336427 ms; the final run measured 1.143550 / 1.544824 ms. Single append/follow over loopback gRPC measured 0.062234 / 0.207282 ms. Shared-host latency samples are diagnostic; work counts and exact records establish correctness. These are loopback service measurements, not a deployed-cloud qualification.

Decisions

  • A physical span is a root. OperationSpan::new_root preserves only that explicit span and its subscriber, including when filtered; it never falls back to a caller. Causal capture stores historical indices, not span references. A closes while B remains active.
  • tracing does not expose generic dispatcher pointer equality. Safe causal linkage therefore requires the same downcast Registry identity, compared while both Dispatch values keep it alive. Layered registry subscribers are supported. Unknown custom subscribers conservatively omit the causal edge instead of accepting unverifiable foreign IDs. The native runtime uses tracing-subscriber's registry-only dependency for this check; it installs no subscriber.
  • Pending admissions are explicit per-interest entries with a node and Progress ownership, protected by one crate-local cfg shim mutex. No count inference remains. That lock is never nested, performs no tracing/provider callbacks and crosses no await. The cursor reservation counter remains only for its existing accounting and zero-cursor cleanup.
  • Pending futures live in a per-node map. Dropping an entry fences that future: late credentials cannot poll back into the pool. Completion separately checks live ownership before installing a Link or failing routes. The existing global credential epoch remains in force.
  • The model uses tiny opaque network futures; the real pool, cursor, cancellation atomic, admission lock and removal signal remain instrumented. Native gated tests exercise the actual credential/tonic path. No scheduler caps or stack-limit increases are used.
  • Checked-follow rejection fixtures now open their healthy sibling before the rejection and assert that same one-session invariant while it stays active. Sequential calls separated by zero interests cannot serve as a sibling-isolation test: the last interest must release its Session.
  • This is ownership inside the existing Streams transport. It adds no application store, protocol fields, polling or per-record observation wrapper. Pending entries are ephemeral physical-connection admission state owned by the existing Cursor guard.