docs(adr): defer per-client DSM sessions, restructure name helper (closes #47)#100
Merged
Merged
Conversation
…oses #47) The 2026-04-16 review flagged the largest spec-vs-code gap: docs/specs described a session_key parameter on AuthManager.get_session() for per-MCP-client DSM sessions under Streamable HTTP, but the live signature was get_session() -> str. Three options were considered (ship the multi- session path now, retract the spec, or formally defer with the helper restructured). This commit records the deferral (Option 3 from #47). - src/mcp_synology/core/auth.py: _build_session_name() now accepts an optional session_key: str | None = None. With None it preserves the current uuid-suffix behavior; with a key it returns the future per-key shape. The constructor still calls it with no key, so every existing code path is byte-identical. - get_session() signature unchanged. No public-API surface change today. - docs/specs/architecture.md: Auth Manager interface example reverted to the live get_session() signature with a forward-pointer to the planned section + ADR. The dedicated section renamed "Future" -> "Planned: Per-Client Sessions (Streamable HTTP)" with an explicit deferral note and a back-reference to the ADR. - docs/adr/0001-per-client-dsm-sessions.md: new ADR capturing question, options-with-tradeoffs, decision, consequences, and five revisit triggers (Streamable HTTP gaining a concrete plan, multi-tenant use case, subagent isolation request, tool-surface roughly doubling, single-shared-session pain under real load). - tests/core/test_auth.py: three new tests in TestSessionNaming pin the helper contract (no-key uuid format preserved, with-key per-key derivation, None-vs-empty-string distinction). 603 unit tests pass (up from 600), 96.25% coverage. auth.py 100%.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Owner
|
[QA] Starting review at |
cmeans
reviewed
May 6, 2026
Owner
cmeans
left a comment
There was a problem hiding this comment.
[QA] Round 1 — PASS
Verified at e59ad2b against current main (3790b62 = #97 + #98 in).
Static checks
uv run ruff check src/ tests/→ cleanuv run ruff format --check src/ tests/→ 69 files already formatteduv run mypy src/→ Success: no issues found in 28 source files
Tests
uv run pytest→ 603 passed, 112 deselected, 18 warnings, 0 failed in 23.4s- Coverage 96.25% (matches PR body);
auth.pyat 100% (matches) - The 112 deselected =
addopts = "-m 'not integration and not vdsm'"frompyproject.toml:99. Those tests run separately; vdsm CI ran SUCCESS at this SHA.
Spec ↔ code ↔ ADR alignment
docs/specs/architecture.md:64Auth Manager example showsasync def get_session(self) -> str:→ matches livesrc/mcp_synology/core/auth.py:266. ✓- Spec line 69 forward-pointer link
#planned-per-client-sessions-streamable-httpresolves to the renamed#### Planned: Per-Client Sessions (Streamable HTTP)heading at line 309 (GitHub-style anchor lowercases + drops parens). ✓ - Spec → ADR back-reference
../adr/0001-per-client-dsm-sessions.mdresolves correctly relative todocs/specs/architecture.md. ✓ - ADR §Decision states
_build_session_name(session_key: str | None = None) -> str→ matchesauth.py:72. ✓ - Five revisit triggers each name a concrete, observable condition (Streamable HTTP plan, multi-tenant deployment, subagent isolation request, tool-surface roughly doubling, real-world single-shared-session pain). Actionable. ✓
Helper contract
_build_session_name()no-arg path: existingtest_session_name_formatstill passes; newtest_build_session_name_without_key_uses_uuidindependently pins the uuid-suffix shape. Byte-identical to pre-PR._build_session_name(session_key="mcp-client-xyz")→MCPSynology_{instance_id}_mcp-client-xyz(new test)._build_session_name(session_key="")→MCPSynology_{instance_id}_(new test pins the None-vs-empty distinction).- Re-verified TDD claim: rolled
auth.pyback to 79933a7 baseline; the three new tests fail withTypeError: _build_session_name() got an unexpected keyword argument 'session_key'; restored, all pass.
Public API surface
get_session()signature unchanged (auth.py:266). No callers insrc/pass asession_key(grep confirms zero hits outside of docstrings/tests/docs). The deferred-stub framing holds.
Issue #47 coverage
- All three options the issue laid out (ship multi-session, retract spec, deferred stub) are enumerated in the ADR with concrete pros/cons. Option 3 chosen, decision recorded, revisit triggers documented. The "decide before expanding the DSM tool surface" framing from #47's context is met.
Stale-reference grep
- No remaining "Future: Per-Client Sessions" string in the spec.
- No
get_session(session_key=...)callers insrc/. All occurrences are doc/test text or the planned-section example.
Transparency notes (not findings)
- PR body says "17 warnings"; my run shows 18. Pytest warning counts are environment-dependent (Python sub-version, dep versions); test count and coverage match exactly so I'm not flagging this.
- PR body refers to
auth.py:254for the unchanged signature; current line isauth.py:266(drift due to expanded_build_session_namedocstring). Cosmetic; the claim itself holds.
CI rollup
- 13 SUCCESS / 5 SKIPPED required checks at
e59ad2bincl. vdsm integration tests SUCCESS. The "QA Gate" PENDING context will resolve when this label transition lands.
Zero blockers, zero substantive, zero observations. Ticking all six test-plan checkboxes pre-signoff. Applying Ready for QA Signoff as the final act; awaiting maintainer's QA Approved.
18 tasks
4 tasks
cmeans-claude-dev Bot
added a commit
that referenced
this pull request
May 13, 2026
The previous commit ran sed -i 's/#PR_PLACEHOLDER/#105/' globally, which substituted both the Phase 2 entry (correct) AND the ADR-0001 entry (wrong — that one actually shipped via #100). Restoring #100 on the ADR line. The ADR-0001 entry has had #PR_PLACEHOLDER on main since #100 itself merged — the original PR forgot the post-open substitution. This commit also closes that latent drift while fixing the regression I just introduced.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #47. Records the deferred-stub decision (Option 3 from the issue) for the per-MCP-client DSM session model. Spec drift closed; the helper is restructured so a future per-client session pool is a bounded change; runtime behavior unchanged.
What changed
src/mcp_synology/core/auth.py—_build_session_name()now accepts an optionalsession_key: str | None = None. WithNoneit preserves the current uuid-suffix shape (MCPSynology_{instance_id}_{uuid8}); with a key it returns the planned per-key shape (MCPSynology_{instance_id}_{session_key}). The constructor still calls_build_session_name()with no key, so every existing code path is byte-identical.get_session()signature unchanged — no public-API surface change today.docs/specs/architecture.md— the Auth Manager interface example was showing the futureget_session(session_key=...)signature even though the live code isget_session() -> str. Reverted the example to match live code with a forward-pointer to the planned section + ADR. Renamed the dedicated section header from "Future: Per-Client Sessions (Streamable HTTP)" to "Planned: Per-Client Sessions (Streamable HTTP)" and added an explicit deferral-not-oversight statement plus a back-reference to the ADR.docs/adr/0001-per-client-dsm-sessions.md(new) — first ADR in the project. Captures: question, three options with concrete tradeoffs, decision (Option 3), consequences (zero behavior change, spec drift closed, bounded next step), and five concrete revisit triggers:Also inventories what the next implementation step looks like when a trigger fires — define client-identity contract, spec session-pool lifecycle, decide credentials policy — so the deferred work has a clear shape rather than an open-ended re-design.
tests/core/test_auth.py— three new tests inTestSessionNamingpin the helper contract:test_build_session_name_without_key_uses_uuid— no-arg form keeps the uuid-suffix shape (defends against a future change accidentally altering the default).test_build_session_name_with_key_uses_key_as_suffix—session_key="mcp-client-xyz"producesMCPSynology_{instance_id}_mcp-client-xyz.test_build_session_name_with_empty_key_uses_empty_suffix—session_key=""is honored as-is, not treated asNone. Pins the None-vs-empty distinction so a future pool cannot accidentally collide with the no-key default.CHANGELOG.md— entry under### Added.Why Option 3 (and not 1 or 2)
From the ADR's tradeoffs section, condensed:
session_keythrough; one missed call site silently regresses). YAGNI risk if the trigger never fires. The strongest case for it — "refactor cost grows linearly with the tool surface" — is real but conditional.Out of scope
session_keytoget_session()'s signature. Doing so without the underlying session pool would either silently no-op (misleading) or raise NotImplementedError (a real behavior change for callers that don't exist yet). Both are net-worse than the current "no parameter at all". Punted to the trigger-fired follow-up.Verification
uv run ruff check src/ tests/— clean.uv run ruff format --check src/ tests/— 69 files already formatted.uv run mypy src/—Success: no issues found in 28 source files.uv run pytest— 603 passed (up from 600 onmainafter fix(filestation): create_folder per-path serial calls (closes #95) #97), 17 warnings, 0 failed. Coverage 96.25% (auth.pyat 100%).Test plan
auth.py:254; "Planned" section reads as a deferral pointer, not a roadmap promise._build_session_name()no-arg behavior is byte-identical to pre-PR (verified by the existingtest_session_name_format)._build_session_name(session_key="x")produces the documented per-key shape (covered by new tests).get_session()signature unchanged.### Added(placeholder#PR_PLACEHOLDERto be substituted post-merge).