Skip to content

test(session): keep tests out of the home session store - #116

Merged
ThatRendle merged 13 commits into
mainfrom
change/session-root-resolution
Sep 11, 2026
Merged

ThatRendle merged 13 commits into
mainfrom
change/session-root-resolution

Conversation

@ThatRendle

Copy link
Copy Markdown
Contributor

Summary

Stops the test suite writing sessions into the developer's real ~/.dmon/sessions, and settles what marks a dmon project root.

  • Root marker settled: a project root is a directory containing .dmon/config.yaml, the rule SessionDirectoryResolver already implements. The alternative, a bare .dmon/ directory, was rejected: ActiveModelStore writes config.local.yaml on a model switch, which would quietly relocate sessions.
  • Spec + ADR: the session-storage discovery requirement is replaced. It uses .dmon naming instead of the stale .daemon, the config-file marker, and the bootstrap the code actually performs (~/.dmon/, not CWD). It states only what is true and decided. A root's sessionStore is ignored from a subdirectory; that case is deliberately unspecified and parked in tech-debt/session-store-setting-ignored-from-subdirectory.md. ADR-004 gets an amendment note only.
  • Three writers fixed. Each one's config.yaml marker now explicitly pins sessionStore: local:
    • LiveToolCallE2ETest: a session on every live run. It now asserts the session lands in its temp root, and it is tagged Category=Live, so plain make test no longer makes a paid call.
    • CoreProcessFixture / IntegrationSmokeTest: the empty "twin" session on every make test. It now asserts the session lands in the fixture's root (this runs in CI).
    • CoreProcessManagerRestartTests: writes to ~ only when the global config sets sessionStore: global or a path, so a store diff couldn't see it (design D9).
  • Rule pinned by SessionDirectoryResolverTests: a .dmon/ holding only config.local.yaml is not a root (mutation-checked).

No production code changes. No user sessions move.

Verification

Whole-suite check (task 3.2): no dmon host process and nothing on port 8666. Both session stores were counted with find -type d, before and after.

Pass non-Live Live home store repo store canary
1, real stores exit 0, every assembly complete (Core 627 passed / 1 skipped, Terminal 194/194) exit 0; LiveToolCallE2ETest Passed (not Skipped) with a real Gemini call 856 → 856 770 → 770 n/a
2, canary sessionStore=<scratch>/canary-store exit 0, same counts exit 0 856 → 856 770 → 770 never created

The canary proves the point only because ~/.dmon/config.yaml sets no sessionStore; I confirmed that (the environment is the lowest configuration layer). openspec validate session-root-resolution --strict passes. All three sections were approved by the section supervisor.

Found along the way (registered, not fixed here)

  • tech-debt/terminal-tests-hang.md, cause verified: the three InitCommandTests hang because InitFeedFixture awaits pack-core.sh stdout EOF with no timeout, while a reusable MSBuild node spawned by dotnet pack holds the pipe open until it idles out (~15 min). With MSBUILDDISABLENODEREUSE=1 they pass in 7 s; the control hangs. Core's ComposedCoreFeedFixture is a line-for-line twin and the leading suspect for the intermittent Core hang. Workaround until fixed: export MSBUILDDISABLENODEREUSE=1 for suite runs.
  • New notes: meko-live-smoke-passes-without-key.md (make test-live reports Passed having tested nothing) and repo-dmon-config-unpinned.md (a latent hazard, tied to the subdirectory question).

After merge

  • Amend tech-debt/live-e2e-test-writes-into-home-session-store.md's status line with the merge commit.
  • /opsx:archive session-root-resolution.
  • Pruning the test sessions that have already piled up in ~/.dmon/sessions is left to the owner.

Change: session-root-resolution

🤖 Generated with Claude Code

https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu

ThatRendle and others added 13 commits September 10, 2026 08:54
Settle ADR-004's self-contradiction in favour of its algorithm and the
code: .dmon/config.yaml marks a project root. Replace the session-storage
discovery requirement, and isolate the two tests measured writing into
~/.dmon/sessions (LiveToolCallE2ETest and CoreProcessFixture's
IntegrationSmokeTest), each with a regression assertion.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
Add paired SessionDirectoryResolver tests: a .dmon/ holding only
config.local.yaml resolves to the global store, and adding config.yaml
makes the same tree resolve locally. A precondition guard stops the
negative case passing vacuously under a real .dmon/config.yaml.

Add an ADR-004 amendment note resolving its prose/algorithm
contradiction in favour of its own discovery step 1. No decision change.

Tasks: 1.1, 1.2
Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
The ADR-004 note claimed the code implements all of discovery step 1,
but sessionStore is read from the working directory's layered config,
not the discovered root's, so a root's setting is ignored from a
subdirectory. Per the Product Owner, state only what is true and
decided: narrow the note to the marker, limit the spec's redirect and
project-local scenarios to what holds, leave the subdirectory case
explicitly unspecified, and tighten the bootstrap scenario to match
BootstrapService. Correct the design's claim that the test marker is
inert.

Register three new notes (subdirectory sessionStore, $HOME as a root,
untested BootstrapService) and mark the Terminal hang intermittent.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
CoreProcessFixture launched its core in a temp directory with no
.dmon/config.yaml, so session discovery fell back to the developer's
real ~/.dmon/sessions and IntegrationSmokeTest left an empty session
there on every make test. Write a sessionStore: local marker (explicit,
so a global sessionStore: global cannot win) and assert the created
session lands under the fixture's root.

Tasks: 2.1
Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
LiveToolCallE2ETest pinned its provider in config.local.yaml, which does
not mark a project root, so every run wrote a session into the
developer's real ~/.dmon/sessions. Add a sessionStore: local
config.yaml marker, assert the session persisted under the temp content
root before cleanup, and tag the class Category=Live so plain make test
no longer makes a paid API call; make test-live runs it.

Tasks: 2.2
Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
CoreProcessManagerRestartTests wrote a .dmon/config.yaml with providers
but no sessionStore, so a developer whose global config says
sessionStore: global had its session.create land in their real home
store. Add an explicit sessionStore: local. Found by the section-2
supervisor; the earlier measurement could not see it because the
measuring machine's global config sets no sessionStore.

Tasks: 2.3
Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
D8 excluded LegacyExtensionsListIgnoredIntegrationTest because it is
already a project root, the reasoning D9 disproved; it is out of scope
because it never creates a session. Point the measurement risk at D9.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
Fold the precedence clause into the redirect scenario and record which
test covers each scenario, including the ones only unit-tested or
untested.

Tasks: 3.1
Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XE8kvUxRFyFRL54gWWkBED
3.2: two full passes (real stores, then a canary sessionStore) with
every assembly complete. Neither the home nor the repo session store
gained a directory, and the canary was never created.

The Terminal hang is named and forced: the three InitCommandTests
block in InitFeedFixture on stdout EOF while a reusable MSBuild node
spawned by pack-core.sh holds the pipe. MSBUILDDISABLENODEREUSE=1
makes them pass in 7 s; the control hangs. Registered, not fixed here.

3.3: live-e2e writer note resolved (three writers); new notes for the
MekoLiveSmokeTests silent pass and the repo's unpinned config.yaml;
path-form correction and nested wizard roots; the Terminal hang cause.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu
B1: the hang notes now name ComposedCoreFeedFixture, the Core twin of
InitFeedFixture, in the fix list and as the leading Core-hang suspect;
the intermittency explanation is labelled inferred and the "whenever
no nodes" claim is dropped; only one Core run counts as a fair
non-reproduction.
B2: the repo-marker note explains why a root pin is not read from a
subdirectory working directory and leads with a test-side guard.
R1/R2: the canary premise (no global sessionStore) and the scope of
the pinning rule (tests that create a session) are stated.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu
Adds CompositionRootTests.RunDotnetAsync to the hang note's fix list,
per the supervisor's re-review. Every section is now closed.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu
The proposal said CoreProcessFixture was the only other writer and
that bootstrap creates an empty sessions/. Section 2 found a third
writer (CoreProcessManagerRestartTests, design D9) and section 1
corrected the bootstrap text. Also records the deliberately
unspecified subdirectory case and lists the restart test under Impact.

Change: session-root-resolution

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CJu5CxqY5B6o3GfMVAfsu
@ThatRendle
ThatRendle merged commit 90eed64 into main Sep 11, 2026
5 checks passed
@ThatRendle
ThatRendle deleted the change/session-root-resolution branch September 11, 2026 14:23
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