You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`ssid`| string or null | latest connection's SSID at open time; null if not yet connected |
145
145
|`gateway_ip`| string or null | latest connection's gateway IP; null if not yet known |
@@ -162,3 +162,19 @@ When `diting monitor` is invoked (stdout mode), the same `session_meta` line SHA
162
162
#### Scenario: session_meta when SSID is unknown at start
163
163
-**WHEN** diting launches without a current Wi-Fi connection
164
164
-**THEN** the session_meta line still emits with `"ssid": null` and `"gateway_ip": null`; subsequent per-event lines may carry the SSID once it's known
165
+
166
+
The `scene_source` field's expanded value set lets analyzers distinguish:
167
+
168
+
-`cli` — user explicitly passed `--scene SCENE`.
169
+
-`env` — `DITING_SCENE` env var.
170
+
-`yaml` — `scenes.yaml` matched the current network.
171
+
-`auto` — heuristic classified from active connection signals.
172
+
-`default` — nothing decided; fell to `home`.
173
+
174
+
#### Scenario: yaml-resolved scene records source `yaml`
175
+
-**WHEN**`scenes.yaml` matches the current SSID, diting launches the TUI with `--log /tmp/x.jsonl`
176
+
-**THEN** the first line of `/tmp/x.jsonl` has `"scene_source": "yaml"`
177
+
178
+
#### Scenario: auto-detected scene records source `auto`
179
+
-**WHEN** the user has no `--scene` / no env var / no yaml match, and the active connection is WPA2 Enterprise
180
+
-**THEN** the session_meta line has `"scene": "office"` and `"scene_source": "auto"`
Copy file name to clipboardExpand all lines: openspec/specs/scenes/spec.md
+98-14Lines changed: 98 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,27 +33,39 @@ diting SHALL pick the active scene at startup using this precedence (highest fir
33
33
34
34
1. CLI flag `--scene SCENE` (explicit per-session)
35
35
2. Env var `DITING_SCENE=SCENE` (shell-level persistent preference)
36
-
3. Default `home`
36
+
3.`scenes.yaml` lookup (per-network persistent assignment by SSID or gateway MAC)
37
+
4. Auto-detect heuristic (classify from active connection's security mode + visible BSSID density)
38
+
5. Default `home`
37
39
38
-
A blank env var (set but empty) is treated as absent so a parent shell can clear it with `DITING_SCENE= diting`. An invalid env-var value SHALL print a stderr warning and fall back to the default (not exit), so a broken shell rc doesn't break startup.
40
+
A blank env var (set but empty) is treated as absent so a parent shell can clear it with `DITING_SCENE= diting`. An invalid env-var value SHALL print a stderr warning and fall back to the next tier (yaml, then heuristic, then default), not exit; a broken shell rc should not break startup.
39
41
40
-
The CLI flag wins over an env var even when both are set; the env var wins over the default. The resolved scene's **source**(`cli` / `env` / `default`) SHALL be retrievable separately from the scene name — downstream consumers (the JSONL `session_meta`, the analyzer's report header) record this source so users can later distinguish "I explicitly chose this" from "the default kicked in".
42
+
The CLI flag wins over an env var even when both are set; the env var wins over the yaml; the yaml wins over the heuristic. The resolved scene's **source** SHALL be retrievable separately from the scene name and SHALL be one of: `cli`, `env`, `yaml`, `auto`, `default`. Downstream consumers (JSONL `session_meta`, the analyzer's report header) record this source so users can later distinguish "I explicitly chose this" from "diting guessed".
41
43
42
-
#### Scenario: CLI flag wins over env var
43
-
-**WHEN**`DITING_SCENE=office diting --scene home` is invoked
44
-
-**THEN** the active scene is `home` and the source is `cli`
44
+
When no Wi-Fi connection is available at startup (`get_connection()` returns None), the yaml and heuristic tiers are both skipped and the resolution falls straight to `default`.
45
45
46
-
#### Scenario: env var fills in when no flag
47
-
-**WHEN**`DITING_SCENE=office diting` is invoked
48
-
-**THEN** the active scene is `office` and the source is `env`
46
+
#### Scenario: CLI flag wins over yaml + heuristic
47
+
-**WHEN**`scenes.yaml` says the current SSID maps to `home` AND the user runs `diting --scene office`
48
+
-**THEN** the active scene is `office` and the source is `cli`
49
49
50
-
#### Scenario: blank env var falls to default
51
-
-**WHEN**`DITING_SCENE= diting` is invoked (env var set to empty string)
50
+
#### Scenario: yaml lookup wins over heuristic
51
+
-**WHEN** no `--scene` flag, no `DITING_SCENE` env var, AND `scenes.yaml` has an entry matching the current SSID
52
+
-**THEN** the active scene is the yaml-assigned value; the source is `yaml`; the heuristic does NOT run
53
+
54
+
#### Scenario: heuristic fires when no higher tier decides
55
+
-**WHEN** no `--scene` flag, no `DITING_SCENE` env var, no `scenes.yaml` match, AND the active connection has security `"WPA2 Enterprise"`
56
+
-**THEN** the active scene is `office` and the source is `auto`
57
+
58
+
#### Scenario: heuristic falls to home for sparse RF
59
+
-**WHEN** no flag, no env, no yaml, AND security is `"WPA2 Personal"`, AND only 5 BSSIDs are visible
60
+
-**THEN** the active scene is `home` and the source is `auto`
61
+
62
+
#### Scenario: no Wi-Fi connection falls straight to default
63
+
-**WHEN** the machine has no associated Wi-Fi (`get_connection()` returns None)
52
64
-**THEN** the active scene is `home` and the source is `default`
53
65
54
-
#### Scenario: invalid env var warns and falls to default
55
-
-**WHEN**`DITING_SCENE=shop diting` is invoked
56
-
-**THEN** a stderr warning is printed; the active scene is `home`; source is `default`; the process continues to launch (does NOT exit)
66
+
#### Scenario: invalid env var warns and falls through (NOT to default)
67
+
-**WHEN**`DITING_SCENE=shop diting` is invoked on an enterprise network
68
+
-**THEN** a stderr warning is printed; the yaml and heuristic tiers are evaluated; the active scene reflects whichever tier resolves first (likely `office` from auto-detect)
57
69
58
70
### Requirement: `scene_defaults(scene)` SHALL return a stable mapping of knobs
59
71
The function `scene_defaults(scene: str) -> dict[str, Any]` SHALL return a dict keyed by knob name. The dict MUST include `ble_presence_gate_s` (float seconds) and `llm_prior` (string for LLM prompt injection). Other keys MAY be present in future phases without breaking callers — callers SHALL read keys defensively (use `.get(name, default)`).
@@ -78,3 +90,75 @@ Per-scene values for the keys defined in this phase:
78
90
#### Scenario: callers read knobs defensively
79
91
-**WHEN** code reads `scene_defaults("home").get("future_knob", "fallback")` against a build that doesn't yet implement `future_knob`
80
92
-**THEN** the call returns `"fallback"` and does NOT raise
93
+
94
+
### Requirement: `scenes.yaml` SHALL map networks to scenes
95
+
A user-curated `scenes.yaml` file SHALL be an optional resolution input for the active scene. The file lives at `./scenes.yaml` by default (resolved against cwd at startup, matching the `aps.yaml` pattern); the path is overridable via `DITING_SCENES_FILE`.
96
+
97
+
The file format is a top-level mapping with a single `networks` key whose value is a list of entries. Each entry SHALL carry exactly one of `ssid` or `gateway_mac` as the match key, plus a `scene` field naming one of the four canonical scenes.
98
+
99
+
```yaml
100
+
networks:
101
+
- ssid: HomeNet
102
+
scene: home
103
+
- ssid: Meituan
104
+
scene: office
105
+
- gateway_mac: 14:51:7e:71:5a:1a
106
+
scene: office
107
+
```
108
+
109
+
Resolution semantics:
110
+
111
+
- A missing file SHALL be treated as an empty registry (no error, no warning).
112
+
- A malformed top-level (not a mapping, or `networks` not a list) SHALL print a stderr warning and behave as an empty registry; diting SHALL continue to launch.
113
+
- An individual entry with an invalid `scene` value SHALL be skipped with a stderr warning naming the offending entry; the rest of the file SHALL still load.
114
+
- When BOTH an `ssid` entry AND a `gateway_mac` entry could match the current connection, the `gateway_mac` match wins (more specific).
115
+
- The loader is read-only — diting SHALL NOT write back to `scenes.yaml` based on auto-detect results. The file is human-curated.
116
+
117
+
#### Scenario: SSID match returns the assigned scene
118
+
- **WHEN** `scenes.yaml` contains `{ ssid: Meituan, scene: office }` AND the current connection's SSID is `Meituan`
119
+
- **THEN** the lookup returns `office`
120
+
121
+
#### Scenario: gateway_mac wins over ssid when both match
122
+
- **WHEN** `scenes.yaml` contains `{ ssid: eduroam, scene: home }` AND `{ gateway_mac: 14:51:..., scene: office }`, AND the current connection has SSID `eduroam` AND gateway MAC `14:51:...`
123
+
- **THEN** the lookup returns `office`
124
+
125
+
#### Scenario: invalid scene name in yaml is skipped
- **THEN** a stderr warning identifies the offending entry; the resolution proceeds to the next tier (heuristic)
128
+
129
+
#### Scenario: missing yaml is silent
130
+
- **WHEN** no `scenes.yaml` file exists
131
+
- **THEN** the yaml tier returns no match; no warning is printed
132
+
133
+
### Requirement: Auto-detect heuristic SHALL classify from observable network signals
134
+
When no higher tier (CLI / env / yaml) decides the scene AND a Wi-Fi connection is available, diting SHALL run a pure-function heuristic against the connection's security mode and visible BSSID density.
`public`is intentionally NOT auto-classified — open Wi-Fi exists in homes (neighbour's), offices (guest networks), and public spaces; without active probing diting cannot distinguish them. Public is opt-in via `--scene public` or `DITING_SCENE=public`.
143
+
144
+
The 30-BSSID threshold is a constant; tuning is a future concern.
145
+
146
+
#### Scenario: WPA2 Enterprise classifies as office regardless of BSSID count
147
+
- **WHEN** `classify_environment("WPA2 Enterprise", 5, "Meituan")` is called
Copy file name to clipboardExpand all lines: openspec/specs/tui-shell/spec.md
+30Lines changed: 30 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -364,3 +364,33 @@ The subtitle SHALL re-render when the active view or scan interval changes; the
364
364
#### Scenario: Audit scene visible in title
365
365
-**WHEN**`diting --scene audit` is launched
366
366
-**THEN** the subtitle includes `[audit]` (EN) or `[排查]` (ZH) — a fast visual indicator that all gating is disabled
367
+
368
+
### Requirement: Scene classification SHALL print a one-line banner at startup
369
+
When the scene was resolved by `scenes.yaml` lookup or by the auto-detect heuristic (i.e. `scene_source ∈ {yaml, auto}`), diting SHALL print exactly one line to **stderr** before launching the TUI / monitor, explaining the choice. The banner format is:
370
+
371
+
EN:
372
+
-`auto-detected scene: <scene> (<reason>)` — for source `auto`
373
+
-`pinned scene: <scene> (matched "<key>" in scenes.yaml)` — for source `yaml`
374
+
-`scene: home (default — no Wi-Fi connection at startup)` — for source `default` when no connection is available
375
+
376
+
ZH equivalents in the matching catalog. The banner SHALL go to **stderr** (not stdout) so that `diting monitor > out.jsonl` streams stay clean. When `DITING_SCENE_QUIET=1` is set, the banner SHALL be suppressed (for users / scripts that want silent startup).
377
+
378
+
When the scene was resolved by `--scene` flag or `DITING_SCENE` env var (source `cli` or `env`), NO banner is printed — the user already knows what they asked for.
379
+
380
+
The banner SHALL fire exactly once per session, before the TUI's alt-screen takes over (so it stays visible in the shell's scroll-back after diting exits).
381
+
382
+
#### Scenario: auto-detect banner names the reason
383
+
-**WHEN** diting launches on a WPA2 Enterprise network without `--scene` / env / yaml
0 commit comments