Skip to content

feat(v0.13.0): per-target KWP response deadline (1s default, 3s slow) - #153

Merged
ohgeeceee merged 1 commit into
mainfrom
feat/v0.13.0-kwp-timeout
Jul 23, 2026
Merged

feat(v0.13.0): per-target KWP response deadline (1s default, 3s slow)#153
ohgeeceee merged 1 commit into
mainfrom
feat/v0.13.0-kwp-timeout

Conversation

@ohgeeceee

Copy link
Copy Markdown
Owner

⚠️ Tier A + Tier B — protected path touched

This PR adds new methods + a new struct field to KdcanTransport in
src-tauri/src/transport/kdcan.rs. Per CLAUDE.md §1, transport/ is a
protected path because the IPC surface is the trust boundary — any
mistake here becomes a silent data-corruption bug the user only sees
weeks later when their car behaves oddly. The change is small (one
hardcoded constant → a per-target lookup) and well-tested (4 new
inline tests), but the protected-path exposure is real.

Wait for human merge per CLAUDE.md §1. The CI auto-merge workflow
does not apply to Tier B PRs.

What

Per-target response-deadline in KdcanTransport::request. Slow
KWP2000 modules (CIC, CAS, ...) get a raised 3 s deadline; everything
else keeps the historical 1 s default.

Why

The K+DCAN transport has hardcoded a 1 s response deadline since
v0.1.0. That deadline times out on every E-series owner's first
fault read: real CIC (0x01) and CAS (0x40) modules take 1.5–3 s
to answer their first KWP frame after fast-init because their
boot-up sequence is longer than the DME's. Every other KWP tool
uses 3 s for these targets; beemuu silently fails.

This is the 🟢 Ready item on ROADMAP.md line 52 that has been open
since v0.3.0.

What changed

src-tauri/src/transport/kdcan.rs+156 / -2 lines, single
file:

  • Two new constants at the top of the file:
    • DEFAULT_RESPONSE_DEADLINE = 1 s (the historical value)
    • SLOW_RESPONSE_DEADLINE = 3 s (matches every other KWP tool)
  • default_slow_modules() helper returning a HashSet<u8>
    initialized to {0x01 /* CIC */, 0x40 /* CAS */}.
  • New slow_modules: HashSet<u8> field on KdcanTransport,
    populated in open().
  • resolve_deadline(slow_modules, target) — free helper factored
    out so the lookup is unit-testable without a real serial port.
  • pub fn deadline_for(&self, target) -> Duration — public API
    for future introspection.
  • pub fn set_slow_module(&mut self, target, slow) -> bool
    hook for a future Settings UI to add user-discovered slow
    modules.
  • request() uses Self::resolve_deadline(&self.slow_modules, target) instead of the hardcoded Duration::from_millis(1000).
  • 4 new tests inline in mod tests at the bottom of the file.

Tests

All inline in src-tauri/src/transport/kdcan.rs::tests:

Test What it guards
default_slow_modules_contains_cic_and_cas The default list contains CIC + CAS but NOT DME (otherwise every healthy DME read would wait the full 3 s)
resolve_deadline_picks_slow_for_known_targets CIC/CAS get SLOW_RESPONSE_DEADLINE, DME/unknown get DEFAULT_RESPONSE_DEADLINE
resolve_deadline_with_empty_set_uses_default_for_all Empty slow-modules list is a safe fallback — every address gets the default
deadline_constants_are_what_e_series_needs The actual values (1 s, 3 s) are locked in; silent changes to either constant get caught at test time

The 4 tests cover both halves of the contract: the values themselves
(deadline_constants_are_what_e_series_needs) and the dispatch
logic (resolve_deadline_*). The "DME should NOT be in the slow
list" assertion in test 1 is the regression guard against the most
likely future mistake — someone adding 0x12 to the default list
would degrade every healthy DME read.

Verification

  • cargo test --lib kdcan: 4 new tests pass.
  • cargo test (full suite): 128 + 1 + 4-ignored = all green
    (was 124 before this slice → +4 from kdcan tests).
  • cargo test --test async_commands: still passes (we did not add
    any new #[tauri::command]).
  • Diff: src-tauri/src/transport/kdcan.rs | 158 ++++++++++++++++++++++++++++++++++++++-
    with -2 deletions (the original Duration::from_millis(1000)
    and kline_ready: Default::default() lines being replaced) — no
    EOL churn.

What this slice is NOT

  • Not an ENET change. ENET's RTT is sub-100 ms on a wired
    connection; the 1 s deadline is already 10× more than needed.
    Per the plan (docs/v0.13.0_plan.md "What we will NOT do"):
    no enet.rs changes in v0.13.0.
  • Not a protocol/ change. UDS/KWP request builders don't
    move; the change is purely in the transport deadline.
  • Not a frontend change. No JS / HTML / CSS touched. The fix
    is invisible until a user reads faults on an E-series; what was
    TimeoutError on every first fault read now succeeds.
  • Not a new crate dependency. Only std::time::Duration +
    std::collections::HashSet, both already in use.

Tier

A + B — touches src-tauri/src/transport/kdcan.rs. The Rust
change is small and the 4 tests are exhaustive, but the protected
path exposure is real per CLAUDE.md §1. Wait for human merge.
The CI auto-merge workflow does not apply here.

Slice 2 of v0.13.0 — 3/4 cycle slices shipped

Unrelated working-tree noise (not in this PR)

Three files are modified locally but staged only my one intended file:

  • CLAUDE.md — your own rewrite (Tier C, yours to land)
  • frontend/index.html, frontend/schematics.html — the recurring third-party "network bar" injection (untrusted, not propagated)

Slice 2 of v0.13.0 'Real Reads, Real Long' (docs/v0.13.0_plan.md).

The K+DCAN transport's request() has hardcoded a 1s response
deadline since v0.1.0. That deadline times out on every E-series
owner's first fault read: real CIC (0x01) and CAS (0x40) modules
take 1.5-3s to answer their first KWP frame after fast-init because
their boot-up sequence is longer than the DME's. Every other KWP
tool uses 3s for these targets; beemuu silently fails.

The fix:

  - Add DEFAULT_RESPONSE_DEADLINE (1s) and SLOW_RESPONSE_DEADLINE
    (3s) constants at the top of the file.
  - Add a slow_modules: HashSet<u8> field to KdcanTransport,
    populated from default_slow_modules() in open() — initially
    just CIC and CAS, matching what every other KWP tool does.
  - Factor the per-target lookup into resolve_deadline(slow, target)
    so the resolution logic is unit-testable without a real serial
    port.
  - Replace the hardcoded 1s in request() with the per-target lookup.
  - Expose deadline_for() and set_slow_module() as the public API
    for future Settings UI to add more slow modules as users
    discover them.

Tier A + Tier B (touches src-tauri/src/transport/kdcan.rs).
Single PR. The flag at the top of the PR body flags the protected
path; wait for human merge per CLAUDE.md \u00a71.

Tests (4 new, all inline in src-tauri/src/transport/kdcan.rs::tests):

  - default_slow_modules_contains_cic_and_cas
    Asserts the default list contains CIC (0x01) and CAS (0x40)
    but NOT DME (0x12) \u2014 otherwise every healthy DME read would
    wait the full 3 seconds.

  - resolve_deadline_picks_slow_for_known_targets
    Asserts CIC/CAS get SLOW_RESPONSE_DEADLINE, DME/unknown get
    DEFAULT_RESPONSE_DEADLINE.

  - resolve_deadline_with_empty_set_uses_default_for_all
    The safe fallback \u2014 empty slow-modules list means every
    address gets the default deadline.

  - deadline_constants_are_what_e_series_needs
    Locks in the actual values (1s, 3s) so a silent change to
    either constant is caught at test time.

No frontend changes \u2014 this slice is invisible until a user reads
faults on an E-series. What was a TimeoutError on every first
fault read now succeeds.

Verification:
  - cargo test --lib kdcan: 4 new tests pass.
  - cargo test (full): 128 + 1 + 4-ignored = all green (was 124,
    now 128; +4 from this slice).
  - cargo test --test async_commands: still passes (we didn't add
    any new #[tauri::command]).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62e616a494

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/// on real E-series cars. Add new entries here as more slow modules are
/// discovered — but only after a real-car timeout on the default 1 s.
fn default_slow_modules() -> std::collections::HashSet<u8> {
[0x01 /* CIC */, 0x40 /* CAS */].into_iter().collect()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use the actual CIC target in the slow-module set

When reading CIC, this override will not select the raised deadline because the repository's canonical address model identifies 0x01 as ACSM (src-tauri/src/data/ecus.rs:36, also reflected by the ACSM fixture in transport/sim.rs:98), not CIC. Consequently this adds the three-second deadline to ACSM requests while CIC requests retain the one-second default, leaving one of the two module timeouts this change is intended to fix unresolved; configure the actual CIC diagnostic target instead.

Useful? React with 👍 / 👎.

Comment thread src-tauri/src/transport/kdcan.rs
@ohgeeceee
ohgeeceee merged commit fd9efc2 into main Jul 23, 2026
13 of 14 checks passed
@ohgeeceee
ohgeeceee deleted the feat/v0.13.0-kwp-timeout branch July 23, 2026 07:39
ohgeeceee pushed a commit that referenced this pull request Jul 23, 2026
…sion-sync rule to CLAUDE.md

README.md badge line 18 was frozen at v0.6.0 even though v0.7.0
through v0.14.0 have all shipped (or in v0.14.0's case, had their
plan merged).  Three changes:

- README.md: bump release badge to v0.14.0, point at CHANGELOG.md.
- CHANGELOG.md: backfill [0.12.0] (Fault Memory cycle, PRs #143-#149
  plus the async fix), [0.13.0] (Real Reads Real Long, PRs #150-#153
  including the plan correction), [0.14.0] (Live CAN plan only,
  PR #156).  Existing [0.11.0] and older sections untouched.
- CLAUDE.md: add golden rule #6 — every release PR must bump both
  the README badge and the CHANGELOG section in lockstep, so the
  badge can't drift again.  Plan-only cycles use CHANGELOG `### Planned`
  and leave the README badge alone.

pytest backend/tests/ → 151/151 green.  No code change, so cargo test
and the JS suites are unaffected.
github-actions Bot pushed a commit that referenced this pull request Jul 31, 2026
…moved to ✅ Done (#200)

Six items in the v0.3.0 'Real Car' historical section were
marked 🟢 Ready but have actually shipped:

| Item                  | Shipped in      | Reference                |
|-----------------------|-----------------|--------------------------|
| KWP2000 slow timeout  | v0.13.0 (PR #153) | commit fd9efc2          |
| ISO-TP multi-frame    | v0.14.x         | src-tauri/src/transport/isotp.rs (enforced by CLAUDE.md, refreshed in PR #198) |
| Dark/light theme      | v0.7.0 (PR #109) | commit afefc32           |
| Gauge theming         | v0.7.0 (PR #109) | commit afefc32           |
| Save/load workspace   | v0.7.0 (PR #109) | commit afefc32           |
| Export PNG/SVG charts | v0.11.0 (PR #136) | commit 7f92ccb          |

This is the same doc-rot pattern that PR #198 fixed in
CLAUDE.md: items the inventory still claims as 'Ready to land'
have actually shipped, but nobody re-tagged the historical
section afterward. Same 'data over invention' fix as the
CLAUDE.md refresh.

What changed:

- '⭐ Protocol & Transport' table — kept the four genuinely-🟡
  items (ENET/DoIP, BLE, WiFi, CAN-bus listener), removed the
  two stale 🟢 Ready items.
- 'UI / UX' table — kept the only genuinely-🟡 item (Mobile-
  responsive), removed the five stale 🟢 Ready items.
- New '✅ Protocol, Transport, UI/UX — historical (shipped)'
  table — pins the six shipped items with their PR references
  and code locations, following the v0.3.0 'Decode Functions'
  historical pattern that already exists just above.

What this PR does NOT change:

- No CHANGELOG.md edits (the shipped-PR references are in
  CHANGELOG.md already)
- No CLAUDE.md edits (the ISO-TP / keepalive / read_vin
  references there are already accurate post PR #198)
- No code edits
- No test edits (markdown only)

Verified locally (docs-only, no test diff expected):

- node --test src/js/**/*.test.js src/js/**/*.test.cjs: 226/226
- python -m pytest backend/tests/ -q: 166/166
- cd src-tauri && cargo test --lib --offline: 149/149
- npm run build: rc=0, both BeeEmUu_0.14.3 bundles built

Tier A docs-only per CLAUDE.md. Self-merge on CI green.

Cross-references:
- PR #198: 'docs: refresh CLAUDE.md hardware/timing invariants'
  (the same doc-rot sweep on CLAUDE.md)
- PR #183: 'docs(proposal): fix stale NOT YET IMPLEMENTED claims'
  (proposed similar fixes but never applied)

Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
github-actions Bot pushed a commit that referenced this pull request Jul 31, 2026
…moved to ✅ Done (#205)

Six items in the v0.3.0 'Real Car' historical section were
marked 🟢 Ready but have actually shipped:

| Item                  | Shipped in      | Reference                |
|-----------------------|-----------------|--------------------------|
| KWP2000 slow timeout  | v0.13.0 (PR #153) | commit fd9efc2          |
| ISO-TP multi-frame    | v0.14.x         | src-tauri/src/transport/isotp.rs (enforced by CLAUDE.md, refreshed in PR #198) |
| Dark/light theme      | v0.7.0 (PR #109) | commit afefc32           |
| Gauge theming         | v0.7.0 (PR #109) | commit afefc32           |
| Save/load workspace   | v0.7.0 (PR #109) | commit afefc32           |
| Export PNG/SVG charts | v0.11.0 (PR #136) | commit 7f92ccb          |

This is the same doc-rot pattern that PR #198 fixed in
CLAUDE.md: items the inventory still claims as 'Ready to land'
have actually shipped, but nobody re-tagged the historical
section afterward. Same 'data over invention' fix as the
CLAUDE.md refresh.

What changed:

- '⭐ Protocol & Transport' table — kept the four genuinely-🟡
  items (ENET/DoIP, BLE, WiFi, CAN-bus listener), removed the
  two stale 🟢 Ready items.
- 'UI / UX' table — kept the only genuinely-🟡 item (Mobile-
  responsive), removed the five stale 🟢 Ready items.
- New '✅ Protocol, Transport, UI/UX — historical (shipped)'
  table — pins the six shipped items with their PR references
  and code locations, following the v0.3.0 'Decode Functions'
  historical pattern that already exists just above.

What this PR does NOT change:

- No CHANGELOG.md edits (the shipped-PR references are in
  CHANGELOG.md already)
- No CLAUDE.md edits (the ISO-TP / keepalive / read_vin
  references there are already accurate post PR #198)
- No code edits
- No test edits (markdown only)

Verified locally (docs-only, no test diff expected):

- node --test src/js/**/*.test.js src/js/**/*.test.cjs: 226/226
- python -m pytest backend/tests/ -q: 166/166
- cd src-tauri && cargo test --lib --offline: 149/149
- npm run build: rc=0, both BeeEmUu_0.14.3 bundles built

Tier A docs-only per CLAUDE.md. Self-merge on CI green.

Cross-references:
- PR #198: 'docs: refresh CLAUDE.md hardware/timing invariants'
  (the same doc-rot sweep on CLAUDE.md)
- PR #183: 'docs(proposal): fix stale NOT YET IMPLEMENTED claims'
  (proposed similar fixes but never applied)

Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
ohgeeceee added a commit that referenced this pull request Jul 31, 2026
…e.rs (#201)

* docs(roadmap): audit v0.3.0 historical section — stale 🟢 Ready items moved to ✅ Done

Six items in the v0.3.0 'Real Car' historical section were
marked 🟢 Ready but have actually shipped:

| Item                  | Shipped in      | Reference                |
|-----------------------|-----------------|--------------------------|
| KWP2000 slow timeout  | v0.13.0 (PR #153) | commit fd9efc2          |
| ISO-TP multi-frame    | v0.14.x         | src-tauri/src/transport/isotp.rs (enforced by CLAUDE.md, refreshed in PR #198) |
| Dark/light theme      | v0.7.0 (PR #109) | commit afefc32           |
| Gauge theming         | v0.7.0 (PR #109) | commit afefc32           |
| Save/load workspace   | v0.7.0 (PR #109) | commit afefc32           |
| Export PNG/SVG charts | v0.11.0 (PR #136) | commit 7f92ccb          |

This is the same doc-rot pattern that PR #198 fixed in
CLAUDE.md: items the inventory still claims as 'Ready to land'
have actually shipped, but nobody re-tagged the historical
section afterward. Same 'data over invention' fix as the
CLAUDE.md refresh.

What changed:

- '⭐ Protocol & Transport' table — kept the four genuinely-🟡
  items (ENET/DoIP, BLE, WiFi, CAN-bus listener), removed the
  two stale 🟢 Ready items.
- 'UI / UX' table — kept the only genuinely-🟡 item (Mobile-
  responsive), removed the five stale 🟢 Ready items.
- New '✅ Protocol, Transport, UI/UX — historical (shipped)'
  table — pins the six shipped items with their PR references
  and code locations, following the v0.3.0 'Decode Functions'
  historical pattern that already exists just above.

What this PR does NOT change:

- No CHANGELOG.md edits (the shipped-PR references are in
  CHANGELOG.md already)
- No CLAUDE.md edits (the ISO-TP / keepalive / read_vin
  references there are already accurate post PR #198)
- No code edits
- No test edits (markdown only)

Verified locally (docs-only, no test diff expected):

- node --test src/js/**/*.test.js src/js/**/*.test.cjs: 226/226
- python -m pytest backend/tests/ -q: 166/166
- cd src-tauri && cargo test --lib --offline: 149/149
- npm run build: rc=0, both BeeEmUu_0.14.3 bundles built

Tier A docs-only per CLAUDE.md. Self-merge on CI green.

Cross-references:
- PR #198: 'docs: refresh CLAUDE.md hardware/timing invariants'
  (the same doc-rot sweep on CLAUDE.md)
- PR #183: 'docs(proposal): fix stale NOT YET IMPLEMENTED claims'
  (proposed similar fixes but never applied)

* feat(v0.14.4): story coverage — 52 unit tests for story.rs + anonymize.rs

Two user-facing Rust modules have shipped with **zero unit
tests** since the diagnostic-story + secure-snapshot-share
features landed:

- `src-tauri/src/story.rs` (350 LOC) — the Generate Story
  pipeline that powers the one-click mechanic narrative
  modal in `src/index.html:513` +
  `src/js/main.js:2961` (renderStory).
- `src-tauri/src/anonymize.rs` (113 LOC) — the VIN-stripping
  layer that powers the Secure Snapshot Share feature
  (`src/js/main.js:1071`).

Both are pure-Rust, deterministic, and snapshot-driven —
the right shape for unit tests with fixture-built
`SessionSnapshot` inputs. This PR adds 52 unit tests
covering:

**story.rs (32 tests):**
- `Severity::from_str` bucketing (critical / warning /
  unknown → info).
- `Severity` ordering (Critical > Warning > Info).
- `priority_for` severity → priority number.
- `parse_cost_range` parser: single value, tilde,
  hyphen, **en-dash** (the TOML files use en-dash —
  parser must normalise), whitespace, empty, garbage.
- `format_vehicle` for empty / VIN-only /
  mileage-only / decoded.
- `build_context` freeze-frame string assembly.
- Full `generate` pipeline:
  - empty snapshot → Info story with no findings.
  - unknown DTC → generic Info finding.
  - n55-specific DTC (2A82) → uses engine template.
  - generic DTC fallback when engine-specific missing.
  - severity = max of all findings (sorted critical-first).
  - recommendations sorted ascending by priority.
  - cost range sums across findings.
  - cost-max invariant: max >= min + 50.
  - DTC code case-insensitive lookup (TOML keys are
    uppercased on load).
  - summary text counts critical + warning correctly.
  - title uses manufacturer + VIN prefix.

**anonymize.rs (20 tests):**
- `hash_vin` properties: 16 hex chars, stable for same
  input, distinct for distinct inputs, case-sensitive
  (current behaviour pinned).
- `anonymize` pipeline:
  - VIN never leaks into the anonymized JSON.
  - VIN → fingerprint via hash_vin.
  - Missing VIN → "unknown" fingerprint.
  - engine_family preserved from suggested_profile.
  - engine_family defaults to "generic".
  - Modules / DTCs / freeze frames / ident all preserved.
  - Mileage (mileage_km) stripped (privacy).
  - Empty modules handled.
  - fault_count = None → 0 in the output.
  - recorded_at populated with "(UTC)" suffix.
  - live_data is always empty (current anonymizer
    strips it; pinning the decision).
- `export_json`:
  - No VIN leak in pretty JSON.
  - No mileage leak.
  - Pretty-printed (multi-line + indented).
  - Round-trips through serde.

## Verification

- `cd src-tauri && cargo test --lib --offline` — **201/201
  pass** (149 existing + 52 new).
- `cd src-tauri && cargo test --test async_commands
  --offline` — 1/1 (the CLAUDE.md invariant guard still
  green — no new sync commands).
- `node --test src/js/**/*.test.js src/js/**/*.test.cjs`
  — 226/226 (no JS diff).
- `pytest backend/tests/ -q` — 166/166 (no backend diff).
- `npm run build` — rc=0, 2m32s; both BeeEmUu_0.14.3
  bundles built.

## Tier

**A** — pure additions to existing Rust modules, no
`transport/**` / `protocol/**` / `commands.rs` /
`.claude/**` touches. Self-merge on CI green per
CLAUDE.md rule 2.

## Cross-references

- The Story modal UI is `renderStory` in
  `src/js/main.js:2961`, the button at
  `src/index.html:513`.
- The Secure Snapshot Share wiring is
  `doSecureShare` in `src/js/main.js:1071`, invoking
  `anonymize_snapshot` (sync, in the SYNC_ALLOWLIST).
- The story knowledge base lives in
  `community/stories/{generic,n55}.toml`; tests load it
  via `story::load()`.

* fix(ci): handle stacked PRs in autonomous auto-merge job (#204)

* Initial plan

* fix(v0.14.4): handle stacked PR auto-merge in CI

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

---------

Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
ohgeeceee added a commit that referenced this pull request Jul 31, 2026
* docs(roadmap): audit v0.3.0 historical section — stale 🟢 Ready items moved to ✅ Done

Six items in the v0.3.0 'Real Car' historical section were
marked 🟢 Ready but have actually shipped:

| Item                  | Shipped in      | Reference                |
|-----------------------|-----------------|--------------------------|
| KWP2000 slow timeout  | v0.13.0 (PR #153) | commit fd9efc2          |
| ISO-TP multi-frame    | v0.14.x         | src-tauri/src/transport/isotp.rs (enforced by CLAUDE.md, refreshed in PR #198) |
| Dark/light theme      | v0.7.0 (PR #109) | commit afefc32           |
| Gauge theming         | v0.7.0 (PR #109) | commit afefc32           |
| Save/load workspace   | v0.7.0 (PR #109) | commit afefc32           |
| Export PNG/SVG charts | v0.11.0 (PR #136) | commit 7f92ccb          |

This is the same doc-rot pattern that PR #198 fixed in
CLAUDE.md: items the inventory still claims as 'Ready to land'
have actually shipped, but nobody re-tagged the historical
section afterward. Same 'data over invention' fix as the
CLAUDE.md refresh.

What changed:

- '⭐ Protocol & Transport' table — kept the four genuinely-🟡
  items (ENET/DoIP, BLE, WiFi, CAN-bus listener), removed the
  two stale 🟢 Ready items.
- 'UI / UX' table — kept the only genuinely-🟡 item (Mobile-
  responsive), removed the five stale 🟢 Ready items.
- New '✅ Protocol, Transport, UI/UX — historical (shipped)'
  table — pins the six shipped items with their PR references
  and code locations, following the v0.3.0 'Decode Functions'
  historical pattern that already exists just above.

What this PR does NOT change:

- No CHANGELOG.md edits (the shipped-PR references are in
  CHANGELOG.md already)
- No CLAUDE.md edits (the ISO-TP / keepalive / read_vin
  references there are already accurate post PR #198)
- No code edits
- No test edits (markdown only)

Verified locally (docs-only, no test diff expected):

- node --test src/js/**/*.test.js src/js/**/*.test.cjs: 226/226
- python -m pytest backend/tests/ -q: 166/166
- cd src-tauri && cargo test --lib --offline: 149/149
- npm run build: rc=0, both BeeEmUu_0.14.3 bundles built

Tier A docs-only per CLAUDE.md. Self-merge on CI green.

Cross-references:
- PR #198: 'docs: refresh CLAUDE.md hardware/timing invariants'
  (the same doc-rot sweep on CLAUDE.md)
- PR #183: 'docs(proposal): fix stale NOT YET IMPLEMENTED claims'
  (proposed similar fixes but never applied)

* feat(v0.14.4): story coverage — 52 unit tests for story.rs + anonymize.rs

Two user-facing Rust modules have shipped with **zero unit
tests** since the diagnostic-story + secure-snapshot-share
features landed:

- `src-tauri/src/story.rs` (350 LOC) — the Generate Story
  pipeline that powers the one-click mechanic narrative
  modal in `src/index.html:513` +
  `src/js/main.js:2961` (renderStory).
- `src-tauri/src/anonymize.rs` (113 LOC) — the VIN-stripping
  layer that powers the Secure Snapshot Share feature
  (`src/js/main.js:1071`).

Both are pure-Rust, deterministic, and snapshot-driven —
the right shape for unit tests with fixture-built
`SessionSnapshot` inputs. This PR adds 52 unit tests
covering:

**story.rs (32 tests):**
- `Severity::from_str` bucketing (critical / warning /
  unknown → info).
- `Severity` ordering (Critical > Warning > Info).
- `priority_for` severity → priority number.
- `parse_cost_range` parser: single value, tilde,
  hyphen, **en-dash** (the TOML files use en-dash —
  parser must normalise), whitespace, empty, garbage.
- `format_vehicle` for empty / VIN-only /
  mileage-only / decoded.
- `build_context` freeze-frame string assembly.
- Full `generate` pipeline:
  - empty snapshot → Info story with no findings.
  - unknown DTC → generic Info finding.
  - n55-specific DTC (2A82) → uses engine template.
  - generic DTC fallback when engine-specific missing.
  - severity = max of all findings (sorted critical-first).
  - recommendations sorted ascending by priority.
  - cost range sums across findings.
  - cost-max invariant: max >= min + 50.
  - DTC code case-insensitive lookup (TOML keys are
    uppercased on load).
  - summary text counts critical + warning correctly.
  - title uses manufacturer + VIN prefix.

**anonymize.rs (20 tests):**
- `hash_vin` properties: 16 hex chars, stable for same
  input, distinct for distinct inputs, case-sensitive
  (current behaviour pinned).
- `anonymize` pipeline:
  - VIN never leaks into the anonymized JSON.
  - VIN → fingerprint via hash_vin.
  - Missing VIN → "unknown" fingerprint.
  - engine_family preserved from suggested_profile.
  - engine_family defaults to "generic".
  - Modules / DTCs / freeze frames / ident all preserved.
  - Mileage (mileage_km) stripped (privacy).
  - Empty modules handled.
  - fault_count = None → 0 in the output.
  - recorded_at populated with "(UTC)" suffix.
  - live_data is always empty (current anonymizer
    strips it; pinning the decision).
- `export_json`:
  - No VIN leak in pretty JSON.
  - No mileage leak.
  - Pretty-printed (multi-line + indented).
  - Round-trips through serde.

## Verification

- `cd src-tauri && cargo test --lib --offline` — **201/201
  pass** (149 existing + 52 new).
- `cd src-tauri && cargo test --test async_commands
  --offline` — 1/1 (the CLAUDE.md invariant guard still
  green — no new sync commands).
- `node --test src/js/**/*.test.js src/js/**/*.test.cjs`
  — 226/226 (no JS diff).
- `pytest backend/tests/ -q` — 166/166 (no backend diff).
- `npm run build` — rc=0, 2m32s; both BeeEmUu_0.14.3
  bundles built.

## Tier

**A** — pure additions to existing Rust modules, no
`transport/**` / `protocol/**` / `commands.rs` /
`.claude/**` touches. Self-merge on CI green per
CLAUDE.md rule 2.

## Cross-references

- The Story modal UI is `renderStory` in
  `src/js/main.js:2961`, the button at
  `src/index.html:513`.
- The Secure Snapshot Share wiring is
  `doSecureShare` in `src/js/main.js:1071`, invoking
  `anonymize_snapshot` (sync, in the SYNC_ALLOWLIST).
- The story knowledge base lives in
  `community/stories/{generic,n55}.toml`; tests load it
  via `story::load()`.

* fix(ci): handle stacked PRs in autonomous auto-merge job (#204)

* Initial plan

* fix(v0.14.4): handle stacked PR auto-merge in CI

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* Initial plan (#206)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ohgeecee <ohjoncurrie@gmail.com>

---------

Co-authored-by: ohgeeceee <ohgeeceee@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
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