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
Pre-existing race shipped with v1.6.0 (when session_meta first
landed) and surfaced by the v1.7.0 release-binary smoke audit:
```jsonl
# Captured `diting monitor` against a host actually associated to tedo_5G:
{"type":"session_meta","ssid":null,"gateway_ip":null, …} ← stale at write time
{"type":"link_state","ssid":"tedo_5G","bssid":"40:fe:95:8a:3c:58", …} ← ms later, real state
```
`session_meta` is the first line of the JSONL stream and is supposed
to carry the at-launch context downstream readers (the analyzer's
"Scene + connection" header, the `--for-llm` prompt bundle, any
third-party `jq` script) use to interpret the session. Writing
`null` for SSID / gateway when the host is in fact associated made
the analyzer report "session started disassociated" — wrong by
construction.
Root cause: both call sites — `_run_monitor` in `cli.py` and
`DitingApp.__init__` in `tui.py` — invoked `emit_session_meta`
synchronously during startup, but the first WiFiPoller snapshot
(which populates `_latest_connection`) hadn't run yet. SSID +
gateway_ip were never threaded into the call, so they defaulted
to None.
Fix: both call sites now invoke `backend.get_connection()` once,
synchronously, BEFORE emitting `session_meta`. `get_connection()`
on `MacOSWiFiBackend` is sync and cheap (reads SCDynamicStore /
helper subprocess result, ~5ms). Failures (helper not ready, no
Wi-Fi yet) are absorbed via try/except → values default to None,
preserving the disassociated-at-launch path that no-Wi-Fi cold
launches need.
Verified against the live network:
```jsonl
{"type":"session_meta","scene":"home","scene_source":"auto",
"diting_version":"1.7.1","ssid":"tedo_5G","gateway_ip":"192.168.124.1",
"hostname":"…"}
```
Tests (+2 new, 1049/1049 total):
- `test_app_session_meta_carries_startup_ssid_and_gateway` — direct
regression: DitingApp with a `_FakeBackend` that returns
Connection(ssid="testnet", router_ip="10.0.0.1") writes a
session_meta carrying both.
- `test_app_session_meta_absorbs_get_connection_failure` —
adversarial: backend whose `get_connection()` raises
`OSError("helper not ready")` still produces a valid
session_meta with ssid / gateway_ip = None and doesn't crash
app startup.
CHANGELOG + ZH CHANGELOG + TESTING.md (EN + ZH) updated.
Version: `pyproject.toml` 1.7.0 → 1.7.1.
Validates: pytest 1049/1049 ✓
openspec validate --specs --strict (22/22) ✓
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: tests/TESTING.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -205,6 +205,7 @@ When a new Requirement lands in any spec, an entry MUST be added here
205
205
| Requirement | Test |
206
206
|---|---|
207
207
|`session_meta` line is first; carries scene + scene_source + diting_version + ssid + gateway_ip + hostname; emit is idempotent; disabled logger is no-op; null SSID / gateway are written through |`test_event_log.py::test_session_meta_writes_header_with_all_fields`, `::test_session_meta_is_first_when_emitted_first`, `::test_session_meta_is_idempotent`, `::test_session_meta_disabled_logger_is_no_op`, `::test_session_meta_accepts_null_ssid_and_gateway`|
208
+
|**v1.7.1** — Both `DitingApp.__init__` and `_run_monitor` synchronously fetch `backend.get_connection()` BEFORE emitting `session_meta`, so the JSONL header carries the at-launch SSID + gateway_ip rather than null. Backend failures (helper not ready, no Wi-Fi yet) are absorbed as None |`test_tui_smoke.py::test_app_session_meta_carries_startup_ssid_and_gateway`, `::test_app_session_meta_absorbs_get_connection_failure`|
208
209
|`--log` and `diting monitor` produce byte-identical streams |`test_event_log.py::test_to_path_writes_appendable_jsonl`, `::test_unicode_user_strings_survive_readable` (single shared writer class) |
209
210
| Writer flushes after every event |`test_event_log.py::test_line_buffered_writes_are_visible_before_close`|
210
211
| atexit hook closes writer cleanly | (gap — no direct test; behaviour validated by `test_line_buffered_writes_are_visible_before_close`) |
0 commit comments