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
refactor(auto-responder): extract Meshtastic DSL matcher — tests now guard production code (#3962) (#4011)
* refactor(auto-responder): extract Meshtastic DSL matcher into matchAutoResponderPattern
The ~130-line placeholder-DSL matcher ({name}/{name:regex} brace-parse,
capture-regex build, param extraction with Unicode preservation) was
duplicated verbatim from checkAutoResponder in meshtasticManager.ts into
its own test file. Any change to production silently diverged from 60+
assertions.
WP1: Move the pure logic into matchAutoResponderPattern(patternStr,
messageText): AutoResponderMatch in src/utils/autoResponderUtils.ts,
next to its pure siblings (splitTriggerPatterns, normalizeTriggerPatterns,
normalizeTriggerChannels). Wire checkAutoResponder to call it — behavior
byte-identical (homoglyph normalization, DSL, capture regexes, Unicode
param extraction, case handling, compileUserRegex ReDoS guard all
preserved). Remove orphaned normalizedText variable.
WP2: Retarget meshtasticManager.autoresponder-regex.test.ts — delete the
verbatim copy (extractParameters + testTriggerMatch, lines 16-155),
import matchAutoResponderPattern, adapt all ~61 assertions to
{matched,params}. Tests now guard production code, not a local copy.
MeshCore checkAutoResponder: intentionally untouched (different algorithm,
different schema, no shared executable logic with Meshtastic).
Epic bookkeeping: check off 2.2c in REMEDIATION_EPIC.md; log 2.2b entry
(PR #4009 / hotfix PR #4007).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
* fix(build): house the DSL matcher server-side — safeRegex's native re2 broke the Vite client build
src/utils/autoResponderUtils.ts is imported by frontend TriggerItem.tsx;
adding homoglyph/safeRegex imports there pulled the native re2 binary
into the client bundle graph (UNLOADABLE_DEPENDENCY). The matcher now
lives in src/server/utils/autoResponderMatcher.ts with a comment
explaining the constraint; autoResponderUtils.ts restored to its
import-free pre-PR state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/internal/dev-notes/REMEDIATION_EPIC.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,7 +34,7 @@ Phase 2 started 2026-07-07. Decisions carried forward from the Phase 0+1 intervi
34
34
-[x]**2.1** — `MeshCoreManager implements ISourceManager`: one unified `sourceManagerRegistry` for all source types; `meshcoreRegistry.ts` reduced to a `@deprecated` shim (delete after one release). WP1 (interface+guards+config move) → WP2+WP3a (lifecycle+loop hardening) → WP3b (read-site migration) → WP4 (shim+tests+docs). See `task21_spec.md` and PR on branch `feature/3962-p2-meshcore-isourcemanager`.
35
35
-[x]**2.2a** — Heartbeat/status-probing: extract shared service parameterized by `ISourceManager` (plan §2.2 ¶1).
36
36
-[x]**2.2b** — Auto-announce: MeshCore + Meshtastic cycles → one `autoAnnounceService` with per-protocol adapters (plan §2.2 ¶2).
37
-
-[]**2.2c** — Auto-responder: `checkAutoResponder` in both managers → shared service (plan §2.2 ¶3).
37
+
-[x]**2.2c** — Auto-responder: `checkAutoResponder` in both managers → shared service (plan §2.2 ¶3).
38
38
-[ ]**2.2d** — Distance-delete scheduling: MeshCore's `DistanceDeleteScheduler` construction → unified ownership in `services/` (plan §2.2 ¶4).
39
39
-[ ]**2.3** — Singleton retirement: enumerate legacy-singleton branches in `meshtasticManager.ts`; create a registry-managed default source from env config; reduce `export default` to a pure alias; delete last special-casing (plan §2.3).
40
40
@@ -63,4 +63,5 @@ Record per-phase: PR link, deviations from plan, follow-ups.
63
63
-**1.6** — PR TBD (schemaDrift.test.ts + schemaDrift.allowlist.ts; 15-entry allowlist; census confirmed 15 divergences — 10 onlyInBootstrap, 3 onlyInReplay, 2 sqlMismatch — matching the architect spec exactly). Finding flagged for Phase 3.3: 7 createIndexes()-only indexes (idx_messages_createdAt/fromNodeId/toNodeId, idx_nodes_updatedAt, idx_route_segments_distance/recordholder/timestamp) are silently lost on a replay-only fresh install; Phase 3.3 must add migrations creating each before deleting createIndexes(). No deviations.
64
64
-**2.1** — PR #4004 merged (unified registry; ISourceManager on both managers; meshcoreRegistry → deprecated shim; ensureMeshCoreManagerStarted helper). Adversarial review (8 finders/11 verifiers) caught a confirmed by-id crash class (meshcore ids reaching Meshtastic-only routes via resolveSourceManager/security routes/purge-node) — fixed at shared-resolver altitude with regression tests. Backlog noted: disconnect() idempotency guard, notifyNewNodeDiscovered name fetch, start() contract hardening.
65
65
- **2.2a** — PR #4005 merged (HeartbeatScheduler leaf; MeshCore adopts; premise correction documented — no cross-protocol heartbeat duplication). Premise correction: no cross-protocol heartbeat duplication exists (Meshtastic = transport-level keepalive push; MQTT = none) — reduced scope to a HeartbeatScheduler leaf utility adopted by MeshCore only; reconnect machinery untouched (Phase 4.2). New files: `src/server/services/heartbeatScheduler.ts` (protocol-agnostic scheduler: interval/start/stop/in-flight-guard/pre+post-await connected-gate) + `heartbeatScheduler.test.ts` (8 fake-timer unit tests covering every gate). MeshCore adoption: `startHeartbeat`/`stopHeartbeat` delegate to per-instance HeartbeatScheduler; `runHeartbeatProbe` deleted; `heartbeatTimer`/`heartbeatProbeInFlight` fields removed; `heartbeatProbe`/`onHeartbeatOk` added as manager callbacks; `recordHeartbeatFailure`/`beginReconnect` chain unchanged. New integration test in meshcoreNativeBackend.test.ts: N consecutive probe failures → heartbeat_failed×N + state='reconnecting'.
66
-
- **2.2b** — PR TBD. Premise correction: plan §2.2 ¶2 premise ("one `autoAnnounceService` with per-protocol send adapters") is not supported by the code — same outcome shape as 2.2a. The two announce pipelines share exactly one mechanism: the cron-vs-interval arming skeleton (~25–30 LOC each). Everything an adapter would abstract (send call, channel identity, templating, gates, on-start semantics, follow-up burst, settings namespace, restart wiring) is genuinely protocol-specific. Reduced scope: extract a narrow `CronOrIntervalScheduler` leaf primitive (~120 LOC + 14-case fake-timer unit test) that owns arming only; both managers delegate to it. Full send-adapter service = 300+ LOC of new indirection to save the same 40 LOC — negative ROI. Files changed: `src/server/services/cronOrIntervalScheduler.ts` (NEW), `cronOrIntervalScheduler.test.ts` (NEW), `meshtasticManager.ts` (replace `announceInterval`+`announceCronJob` with `announceScheduler`; rewrite arming skeleton; on-start block untouched), `meshcoreManager.ts` (replace `autoAnnounceTimer`+`autoAnnounceCron` with `announceScheduler`; rewrite `startAutoAnnounce`/`stopAutoAnnounce`; advert timer preserved). Public method names/signatures unchanged. Behavior-preservation table (§6 of spec): all rows verified. Follow-ups: same arming skeleton copy-pasted in timer-triggers (both protocols) and distance-delete — candidates for a future CronOrIntervalScheduler adoption task (epic #3962 Phase 2).
66
+
-**2.2b** — PR #4009 merged (CronOrIntervalScheduler leaf; both managers delegate arming; premise correction: ~40 LOC shared skeleton, adapter service = negative ROI). Ride-along: hotfix PR #4007 fixed main broken by #4004/#4002 merge race.
67
+
- **2.2c** — Premise correction #3 (cross-manager duplication ≈0%; real find = Meshtastic matcher copy-pasted into its own test — now extracted so tests guard production; MeshCore intentionally separate). Extracted the ~130-line placeholder-DSL matcher (`{name}` / `{name:regex}` brace-parse → capture regex → param extraction) from `checkAutoResponder` in `meshtasticManager.ts` into `matchAutoResponderPattern()` + `AutoResponderMatch` interface in `src/utils/autoResponderUtils.ts`. `meshtasticManager.ts` now calls `matchAutoResponderPattern` (3-line loop); removed orphaned `normalizedText`. Retargeted `meshtasticManager.autoresponder-regex.test.ts` (621 lines) to import the production function — deleted local `extractParameters`+`testTriggerMatch` copy (lines 16–155), adapted all ~61 assertions to `{matched,params}`; added 1 explicit `{matched:false,params:{}}` edge-case assertion. MeshCore `checkAutoResponder` byte-for-byte unchanged. Full suite: success=true, 0 failed, 0 suitesFailed; typecheck clean; typecheck:tests 283 errors (at baseline); lint:ci exit 0.
0 commit comments