Two blocking lifecycle findings from the deep review:
- `SessionStore.enter()` is a public cross-package primitive that a caller can
separate from `prepare()` by arbitrary work, so it must re-check the id: a
stale prepared session could otherwise overwrite a live store entry of the
same id, and the stale session's detach disposer would later delete the REAL
session. Re-add the duplicate-id throw (removed earlier on a coverage
rationale that only held for the back-to-back internal caller). Tests cover
the stale-overwrite rejection and the prepare/enter/announce lifecycle (which
also covers the throw branch).
- `AgentHandle.dispose()` exposed the raw single-shot cordis effect disposer, so
a concurrent/second dispose() returned immediately (effect epoch already
cleared) instead of awaiting the in-flight teardown — violating the
dispose(): Promise<void> contract that every caller observes the same
quiescence boundary. Memoize the disposal promise in startOwned. Regression
test gates the loop's final flush, fires two dispose() calls, and asserts the
second stays pending until the first's teardown completes (fails without the
memo).