typescript-api-wiring-clientid-threading
Solution written to /workspace/solution.md (321 lines) and the underlying algorithm is genuinely verified — the standalone simulation exits 0 with 11/11 checks passing.
Problem class: typescript-api-wiring-clientid-threading
Root cause (three compounding bugs):
1. Wiring — POST /api/chat parsed body.clientId but createOrchestratorSession hardcoded clientId: null, so explicit ids never reached the session.
2. Matching — IDENTIFY_CLIENT fed the full sentence to CRM searchClients, whose needle-in-name semantics (name.includes(needle)) can never match a sentence — it never tried the reverse name-in-needle direction.
3. Result — client stayed null on 100% of requests → the preferences layer always hit its skip branch.
The fix (4 changes, all in the doc):
- createOrchestratorSession accepts optional clientId: string | null, defaults to null
- chat.ts threads body.clientId through
- identifyClient does explicit-id short-circuit → sentence-first search (both directions) → per-token fallback with a stopword guard (function words plus organizational suffixes like family/group/company — my verification caught that family alone false-matches "The Al-Rashid Family" just like the does)
- matches() supports both search directions: name.includes(needle) and needle.includes(name)
Verification included:
- 8 test specs (unit wiring, route, matching semantics, e2e skipped → succeeded) formatted for vitest/jest
- A dependency-free Node simulation proving: the root-cause repro (test 4: CRM store rejects full sentences), sentence-first match, CRN per-token fallback, stopword guard passing while distinctive tokens still win, and the e2e preferences flip (all green)
Key lesson documented: provider search direction differs between stores — support both when matching free text (needle-in-name vs name-in-needle), and always prefer explicit clientId over textual derivation.
# Evidence - Problem class: typescript-api-wiring-clientid-threading - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-09-03T03:44:13.994Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "HEADING: POST /api/chat accepted body.clientId but createOrchestratorSession hardcoded clientId:null \u2014 IDENTIFY_CLIENT re-derived the client from free text via CRM searchClients whose needle must be a substring of the NAME, so full sentences never matched and the preferences layer skipped on every request. Fix: optional clientId param on createSession threaded from chat.ts; identify-client does sentence-first search then per-token fallback (name.includes(token)) with a stopword guard to prevent common-word false matches (the vs The Al-Rashid Family). Lesson: provider search direction differs between stores (needle-in-name vs name-in-needle) \u2014 support both when matching free text. 8 tests incl. e2e preferences skipped->succeeded.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "typescript-api-wiring-clientid-threading", "provider": "openrouter", "solved_at": "2026-09-03T03:44:13.994Z", "version": ""}