Skip to content

fix: add DmScope field to SessionConfig to persist dm_scope setting - #3067

Merged
afjcjsbx merged 3 commits into
sipeed:mainfrom
SiYue-ZO:fix/session-dm-scope-save
Jun 11, 2026
Merged

fix: add DmScope field to SessionConfig to persist dm_scope setting#3067
afjcjsbx merged 3 commits into
sipeed:mainfrom
SiYue-ZO:fix/session-dm-scope-save

Conversation

@SiYue-ZO

@SiYue-ZO SiYue-ZO commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

The "Session Scope"(运行时会话隔离范围)setting on the config page can be modified in the UI but cannot be saved. After saving and reloading the page, it always reverts to the default per-channel-peer.

Root Cause

The frontend sends dm_scope as part of the session config, but the backend SessionConfig struct lacked the corresponding DmScope field. Go's encoding/json silently discards unknown fields during unmarshal, so the value was lost on every PATCH request.

Additionally, the custom MarshalJSON only emits the session block when Dimensions or IdentityLinks are non-empty, so even if dm_scope were stored, it would not appear in GET responses.

Fix

  • Add DmScope string field with json tag dm_scope to SessionConfig struct
  • Update MarshalJSON condition to include session when DmScope is set

Only one file changed: pkg/config/config.go (2 insertions, 1 deletion).

SiYue-ZO added 2 commits June 9, 2026 10:48
The frontend sends dm_scope as part of the session config, but the
backend SessionConfig struct lacked the corresponding field. Go's
encoding/json silently discards unknown fields, so the value was lost
on every PATCH request. Additionally, MarshalJSON only emitted the
session block when Dimensions or IdentityLinks were set, so even a
stored dm_scope would not appear in GET responses.

- Add DmScope string field with json tag 'dm_scope' to SessionConfig
- Update MarshalJSON condition to include session when DmScope is set
The dm_scope field was stored in config but never translated into the
dimensions array that the routing layer actually consumes. This meant
changing the session isolation scope in the UI had no effect at runtime.

Add ApplyDmScope() to SessionConfig which maps the user-facing dm_scope
values (per-channel-peer, per-channel, per-peer, global) to the
corresponding dimension arrays. Call it in LoadConfig post-processing
and in both the PATCH and PUT API handlers.

Includes table-driven tests covering all dm_scope values and the
precedence rule (explicit dimensions > derived from dm_scope).
@SiYue-ZO

SiYue-ZO commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Update: dm_scope is now wired into runtime behavior

The initial commit only fixed the storage problem (dm_scope was silently dropped during JSON unmarshal). This follow-up commit fixes the runtime problem — dm_scope was a dead config field that never affected actual session isolation.

What was missing

The routing layer reads SessionConfig.Dimensions (e.g. ["chat", "sender"]) to construct session keys, but the frontend only sends the user-friendly dm_scope value (e.g. "per-channel-peer"). No code ever translated one into the other.

What this commit adds

  1. SessionConfig.ApplyDmScope() — maps dm_scopeDimensions:

    dm_scope Dimensions
    per-channel-peer ["chat", "sender"]
    per-channel ["chat"]
    per-peer ["sender"]
    global []

    Explicit Dimensions take precedence when already set.

  2. Called in LoadConfig post-processing — covers startup and config reload.

  3. Called in PATCH and PUT API handlers — covers frontend save flow.

  4. Table-driven tests — all 4 scope values + precedence + no-op cases.

@afjcjsbx

Copy link
Copy Markdown
Collaborator

Hi @SiYue-ZO, thank you for the PR,

PATCH /api/config leaves session.dimensions stale
Changing the "Session Scope" via the dashboard still fails to update runtime behavior. In web/backend/api/config.go:147, the merge starts from config.LoadConfig(), and this PR adds ApplyDmScope() in pkg/config/config.go:1479. This means the base merge object already contains the dimensions derived from the old dm_scope. After a patch like {"session":{"dm_scope":"per-channel"}}, newCfg.Session.Dimensions remains populated. As a result, ApplyDmScope() exits early (pkg/config/config.go:349), causing the saved file to end up with a mismatched configuration (e.g., dm_scope: "per-channel" but dimensions: ["chat","sender"]). Since routing relies on Dimensions (pkg/routing/route.go:101), the UI will display the new scope while the runtime continues to use the old one.

Inconsistent scope mapping for legacy/fresh configs in UI
Existing or fresh configurations still reload with a UI-displayed scope that doesn't match reality. handleGetConfig() directly serializes the output of LoadConfig() (web/backend/api/config.go:36), but ApplyDmScope() only handles the dm_scope -> dimensions conversion; it lacks the inverse mapping when only dimensions are present. Therefore, for legacy configs—or even DefaultConfig() which initializes with ["chat"] (pkg/config/defaults.go:47)—the API response might completely omit session.dm_scope. The frontend form then falls back to its default (per-channel-peer in web/frontend/src/components/config/form-model.ts:376). Consequently, the page can display an incorrect scope until the configuration is explicitly rewritten.

The reviewer identified two bugs in the original PR:

1. PATCH /api/config leaves session.dimensions stale: LoadConfig()
   derives dimensions from the old dm_scope, and the merge carries
   those stale dimensions forward. ApplyDmScope() then exits early
   because dimensions is already populated, causing a mismatch between
   dm_scope (new) and dimensions (old).

2. Legacy/default configs omit dm_scope in GET response: configs with
   explicit dimensions but no dm_scope (including DefaultConfig) return
   no dm_scope field, causing the frontend to fall back to its default
   ('per-channel-peer'), which may not match the actual dimensions.

Fix:
- Add DeriveDmScope() to reverse-map known dimensions arrays to
  dm_scope when dm_scope is empty.
- Call it in LoadConfig(), PUT handler, PATCH handler, and
  ResetToDefaults() for consistent normalization.
- In PATCH handler, clear stale dimensions from the merge result when
  the patch contains session.dm_scope but not session.dimensions,
  allowing ApplyDmScope() to re-derive from the new scope.
- Add comprehensive unit tests for DeriveDmScope() and scope
  transition scenarios.
@SiYue-ZO
SiYue-ZO force-pushed the fix/session-dm-scope-save branch from 67fe569 to ef002d9 Compare June 11, 2026 08:13
@SiYue-ZO

Copy link
Copy Markdown
Contributor Author

Hi @SiYue-ZO, thank you for the PR,

PATCH /api/config leaves session.dimensions stale Changing the "Session Scope" via the dashboard still fails to update runtime behavior. In web/backend/api/config.go:147, the merge starts from config.LoadConfig(), and this PR adds ApplyDmScope() in pkg/config/config.go:1479. This means the base merge object already contains the dimensions derived from the old dm_scope. After a patch like {"session":{"dm_scope":"per-channel"}}, newCfg.Session.Dimensions remains populated. As a result, ApplyDmScope() exits early (pkg/config/config.go:349), causing the saved file to end up with a mismatched configuration (e.g., dm_scope: "per-channel" but dimensions: ["chat","sender"]). Since routing relies on Dimensions (pkg/routing/route.go:101), the UI will display the new scope while the runtime continues to use the old one.

Inconsistent scope mapping for legacy/fresh configs in UI Existing or fresh configurations still reload with a UI-displayed scope that doesn't match reality. handleGetConfig() directly serializes the output of LoadConfig() (web/backend/api/config.go:36), but ApplyDmScope() only handles the dm_scope -> dimensions conversion; it lacks the inverse mapping when only dimensions are present. Therefore, for legacy configs—or even DefaultConfig() which initializes with ["chat"] (pkg/config/defaults.go:47)—the API response might completely omit session.dm_scope. The frontend form then falls back to its default (per-channel-peer in web/frontend/src/components/config/form-model.ts:376). Consequently, the page can display an incorrect scope until the configuration is explicitly rewritten.

maybe you can review again?thanks!

@afjcjsbx afjcjsbx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@afjcjsbx
afjcjsbx merged commit f8472d6 into sipeed:main Jun 11, 2026
5 checks passed
@SiYue-ZO
SiYue-ZO deleted the fix/session-dm-scope-save branch June 22, 2026 00:42
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.

2 participants