toga-ai 1.0.642 → 1.0.644

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@
16
16
  | [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
17
17
  | [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant | library/app/netsuite.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php |
18
18
  | [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w | library/app/api/netsuite/rest.php, library/ssl/netsuite_ec_key.pem, test/@dave/Junk Drawer/nsq.php |
19
- | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php |
19
+ | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php, library/app/netsuite.php |
20
20
  | [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`, title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watc | library/app/systemmonitor/netsuiteintegration.php, worker/crons/infrastructure/system_monitors.php |
21
21
  | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. | library/app/api/toga2.php, library/app/api/startechticket.php, worker/crons/toga2/startech/common_import_supporting_records.php |
22
22
  | [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross | library/app/api/toga2.php, worker/crons/toga2/aig/sync_togasupply_aig.php, worker/crons/toga2/wje/sync_togasupply_wje.php, test/@Mark/AIG/test_multi_email.php |
@@ -6,14 +6,16 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-11
10
- owners: [dfranks]
9
+ updated: 2026-08-25
10
+ owners: [dfranks, jcardinal]
11
11
  files:
12
12
  - library/app/api/netsuite/rest.php
13
+ - library/app/netsuite.php
13
14
  related:
14
15
  - netsuite-suiteql-api-reference.md
15
16
  - ../architecture.md
16
17
  - ../../worker/features/forecast2-netsuite-reconciliation.md
18
+ - ../../../2.0/apps/_underscore/features/netsuite-rest-client.md
17
19
  ---
18
20
 
19
21
  ## Summary
@@ -90,6 +92,35 @@ Forecast2 tables already store.
90
92
  - **TO_DATE rejects impossible dates** (e.g. `2026-06-31`) with an opaque HTTP 400 — validate
91
93
  calendar dates before building SuiteQL.
92
94
 
95
+ ## Transaction status sourcing (1.0) — and the 2026.2 REST `status.id` change
96
+
97
+ 1.0 reaches NetSuite over **two transports, and neither reads status off a REST record GET
98
+ body**:
99
+
100
+ - **REST adapter (`App_Api_Netsuite_Rest`, `rest.php`).** It *does* issue `/record/v1/...`
101
+ GETs, but it sources `status` from the **SuiteQL header row** (`$h->status`, from the
102
+ `SELECT ... status ... FROM ...` at ~L381/574/754 — already a letter) and maps that letter
103
+ to the SOAP-era text label via an internal **`$statusMap`** (~L471-472, L625-626,
104
+ L853-854/863). The REST GET body (`$rec`) is only read for shipMethod / opportunity / terms /
105
+ createdFrom / country — **never status**. Downstream forecast2 crons
106
+ (`worker/crons/toga2/forecast2/*`) compare `$nsRecord->status` to text, but that text is
107
+ *adapter-produced* (from the SuiteQL letter via `$statusMap`), not from the changing REST
108
+ field.
109
+ - **SOAP (`App_NetSuite`, `library/app/netsuite.php`).** Always uses `new NetSuiteService()`
110
+ (SuiteTalk toolkit) and reads transaction status off the SOAP record as text (e.g.
111
+ `$items->status != 'Billed'/'Cancelled'/'Closed'` ~L1151/L1434; `$nsInvoice->status ==
112
+ 'Paid In Full'` ~L2546). SOAP is a different API and is unaffected. (The many
113
+ `->status->isSuccess` / `statusDetail` reads elsewhere are the SOAP **response envelope**,
114
+ not the record's business status.)
115
+
116
+ > **NetSuite 2026.2 impact: none for 1.0.** 2026.2 standardizes the `status.id` returned by
117
+ > **REST record GETs** for transactions to single-letter codes across all types (some types
118
+ > return it as text like `"Open"` today); `status.refName` and SuiteQL are unchanged, and
119
+ > SOAP is a separate API. Since no 1.0 code reads `status.id` off a REST GET body, no patch is
120
+ > needed. NetSuite SuiteAnswers ID **89313**. The 2.0 side of the same conclusion (shared REST
121
+ > client + worker2 consumers) is in
122
+ > [`netsuite-rest-client.md`](../../../2.0/apps/_underscore/features/netsuite-rest-client.md).
123
+
93
124
  ## Data model
94
125
 
95
126
  Reads NetSuite `transaction`, `transactionline`, `previoustransactionlinelink` via SuiteQL and
@@ -108,6 +139,12 @@ None — uniform across clients (NetSuite is a single shared account).
108
139
 
109
140
  ## Change history
110
141
 
142
+ - 2026-08-25 — Documented **1.0 transaction-status sourcing across both transports** and the
143
+ conclusion that the **NetSuite 2026.2 REST `status.id` text→letter standardization does not
144
+ affect 1.0** (SuiteAnswers 89313). `App_Api_Netsuite_Rest` derives status from the SuiteQL
145
+ letter and maps it via an internal `$statusMap` — never from the REST GET body; forecast2
146
+ crons see that adapter-produced text, not the changing field; `library/app/netsuite.php` is
147
+ pure SOAP (a different API). Discovery/impact-analysis only — no code changed. (jcardinal)
111
148
  - 2026-06-11 — Documented bulk SuiteQL field semantics (tl.id==line, ShipItem NULL cost, iscogs
112
149
  COGS filter, createdFrom non-determinism, foreignamountunpaid, sign conventions) surfaced while
113
150
  moving the Forecast2 trueup tools off per-id REST GETs. (dfranks)
@@ -30,7 +30,7 @@
30
30
  | [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php, _underscore/Model.php, _underscore/Model/Rate/Subscription.php |
31
31
  | [_Model::save() parent FK cascade — stored-SQL-field recompute deadlocks](features/model-save-parent-cascade-stored-field-deadlock.md) | `_Model::save()` runs a **generic parent foreign-key cascade**: inserting (or saving) a child row that carries an FK to a parent causes `_Model` to **re-load an | _underscore/Model.php, _underscore/Model/Client/PurchaseOrder.php, _underscore/Model/Client/AdvanceShippingNotice.php |
32
32
  | [_Model::save() vs raw _Query — no atomic conditional update](features/model-save-vs-query-atomic-update.md) | `_Model::save()` is a plain load-then-write ORM primitive and **cannot express an atomic conditional update** (an optimistic-concurrency / row-claim guard such | _underscore/Model.php, _underscore/Query.php, _underscore/Model/Rate/Subscription.php |
33
- | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
33
+ | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php, _underscore/Trait/Netsuite/SalesOrder.php, worker2/Worker/Netsuite/SalesOrder.php, worker2/Component/Forecast/SaleImport/SaleImport.php, worker2/Worker/Netsuite/Opportunity.php |
34
34
  | [NetSuite Sales Order sync — ship-to address, phone, and PO reference sourcing](features/netsuite-salesorder-address-phone-sync.md) | `_Trait_Netsuite_SalesOrder` is the **shared** sales-order importer composed into **22 client models** (every client on the dbchanges2 `netsuite` module). | _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Model.php, dbchanges2/_modules/netsuite/2026-08-10a - AddressPhoneNumberApiRoleAcl.sql |
35
35
  | [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | `_Model_Core_Page::meta()` is the **legacy** page-meta resolver behind `GET /pages/meta?slug=<slug>` — still the live path for `toga2-supply` and other pre-Surf | _underscore/Model/Core/Page.php, _underscore/Model/Client/TableView.php, toga2-supply/src/components/ui/Tables/PrimaryTable/PrimaryTable.tsx |
36
36
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/Model.php, _underscore/Query.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php, api2/Controller/Index.php |
@@ -6,14 +6,19 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-20
9
+ updated: 2026-08-25
10
10
  owners: ["dfranks", "jcardinal", "bala"]
11
11
  files:
12
12
  - _underscore/Component/Api/Netsuite/Netsuite.php
13
+ - _underscore/Trait/Netsuite/SalesOrder.php
14
+ - worker2/Worker/Netsuite/SalesOrder.php
15
+ - worker2/Component/Forecast/SaleImport/SaleImport.php
16
+ - worker2/Worker/Netsuite/Opportunity.php
13
17
  related:
14
18
  - ../../worker2/features/netsuite-salesorder-open-orders-sync.md
15
19
  - ../../worker2/features/netsuite-salesorder-outbound-push.md
16
20
  - ../../worker2/features/netsuite-opportunity-sync.md
21
+ - ../../library/features/netsuite-suiteql-rest-shim.md
17
22
  - ../architecture.md
18
23
  ---
19
24
 
@@ -167,6 +172,40 @@ rather than a bad query. Confirmed live 2026-07-28.
167
172
  Surfaced building the NYCHH asset-tag backfill (see
168
173
  [nychh-asset-tag-backfill](../../worker2/features/nychh-asset-tag-backfill.md)).
169
174
 
175
+ ## NetSuite transaction status sourcing (2.0) — and the 2026.2 REST `status.id` change
176
+
177
+ **`fetchRecord()` returns the REST record UNTOUCHED — there is no central status
178
+ normalization in the shared client.** `fetchRecord()` (`Netsuite.php` ~L215-223) does
179
+ `send('GET', route)`, json-decodes, and hands the raw record back; it never reads or maps
180
+ `status`. So whether a 2.0 consumer is exposed to a change in the REST record's `status`
181
+ field depends entirely on *that consumer*, not on the client. Audited 2026-08-25 — every 2.0
182
+ consumer sources transaction status from a change-safe field:
183
+
184
+ - **`worker2/Worker/Netsuite/SalesOrder.php`** reads `status->refName` (the human-readable
185
+ label) and compares it to `OPEN_STATUSES` text — never `status.id`.
186
+ - **`worker2/Component/Forecast/SaleImport/SaleImport.php`** — `statusName()` (~L845-853) reads
187
+ `status->refName`, matched against `EXCLUDED_STATUSES` text. This is the shared engine that
188
+ Invoice / CashSale / CreditMemo / CashRefund import all delegate to.
189
+ - **`worker2/Worker/Netsuite/Opportunity.php`** — `fetchStatus()` (~L431-444) gets transaction
190
+ status via **SuiteQL** (`SELECT status FROM transaction`), which already returns the letter
191
+ and maps `A`/`C`/`D`. (Its `entityStatus.id` at ~L197 is a *different* field — an opaque
192
+ lookup key, not the transaction status.)
193
+ - **`_underscore/Trait/Netsuite/SalesOrder.php`** — the one text `switch ($nsOrder->status)`
194
+ (~L589-620) is driven by a **SOAP** `$service->get()` record, not REST.
195
+ - **`api2`** makes no NetSuite calls of its own — it only registers the SDK at boot and writes
196
+ one hardcoded outbound status literal (`PENDING_FULFILLMENT`); its `->status` reads are its
197
+ own HTTP response envelope. Not a NetSuite status consumer.
198
+
199
+ > **NetSuite 2026.2 impact: none for 2.0.** NetSuite 2026.2 standardizes the `status.id` field
200
+ > returned by **REST record GETs** for transaction records to single-letter codes (A/B/D/E/V)
201
+ > across all transaction types (today some types return `status.id` as text like `"Open"`);
202
+ > `status.refName` and SuiteQL are unchanged, and SOAP/SuiteTalk is a different API. Because no
203
+ > 2.0 code reads `status.id` off a REST GET body as text, no patch is needed. NetSuite
204
+ > SuiteAnswers ID **89313**. (Not audited: other 2.0 apps that may consume this client —
205
+ > `toga2-supply`, `toga25-supply`, `tools` — check them the same way if touched.) The 1.0 side
206
+ > of the same conclusion is in
207
+ > [`netsuite-suiteql-rest-shim.md`](../../library/features/netsuite-suiteql-rest-shim.md).
208
+
170
209
  ## Logging
171
210
 
172
211
  `send()` calls `setLogging(false)`, so **NetSuite REST request/response bodies are NOT written to
@@ -192,6 +231,14 @@ doc.)
192
231
 
193
232
  ## Change history
194
233
 
234
+ - 2026-08-25 — Documented **2.0 NetSuite transaction-status sourcing** and the conclusion that
235
+ the **NetSuite 2026.2 REST `status.id` text→letter standardization does not affect 2.0**
236
+ (SuiteAnswers 89313). `fetchRecord()` returns the REST record raw (no central status
237
+ normalization), and every audited consumer reads status from `status.refName` or SuiteQL, or
238
+ over SOAP — none reads REST `status.id` as text: worker2 SalesOrder/SaleImport (`refName`),
239
+ worker2 Opportunity (`fetchStatus()` via SuiteQL), `_underscore` SalesOrder trait (SOAP),
240
+ api2 (no NetSuite calls). Discovery/impact-analysis only — no code changed. (jcardinal)
241
+
195
242
  - 2026-08-20 — Added **idempotent creates via `externalId`**: `createRecord()` has no idempotency, so
196
243
  stamp an `externalId` (NetSuite rejects a duplicate within the account) and read the record back
197
244
  with `GET /record/v1/<record>/eid:<externalId>` — the non-SuiteQL fallback, which matters because
@@ -0,0 +1,84 @@
1
+ ---
2
+ type: session
3
+ slug: bdr-landing-chat
4
+ title: BDR /landing mobile pass + Talos chat drawer wired to real campaign-chat backend
5
+ author: tcox
6
+ repos: [bdr]
7
+ framework: "2.0"
8
+ client: shared
9
+ status: active
10
+ created: 2026-08-25
11
+ updated: 2026-08-25
12
+ ---
13
+
14
+ # Session: bdr-landing-chat
15
+ **Date:** 2026-08-25
16
+ **Project/Repo:** bdr (2.0)
17
+ **Task:** On the BDR cybersecurity landing page (C:\WWW\BDR, route `/landing`): did the pending mobile CSS pass, then built the right-side Talos chat drawer (opened by the header "Ask Alex Anything" CTA and the hero "Learn more" button) wired to the REAL api.togaiq.com campaign-chat backend behind a blox-shaped adapter, captured everything to the KB, and retired the now-deleted `AI BDR - Security/` mockup from docs + eslint config. Nothing is committed in the BDR repo.
18
+
19
+ ---
20
+
21
+ ## What WORKED
22
+ - **Mobile pass** appended to `src/app/security.css`: `@media(max-width:560px)` (hero title `clamp(29px,8.4vw,35px)`, hero CTAs stack full-width at 54px, `.lp-value` padding 20px, `.lp-value-motes` inset `-30px -10px` to stop the mote spill opening horizontal scroll at phone width, card/stat padding trims, section spacing) and `@media(max-width:420px)` (`.wordmark-tech` hidden, `.lp-stats` gap 12px). All scoped under `.security-shell`. Evidence: dev server compiled clean, `next build` clean, `/landing` still prerendered static.
23
+ - **Chat drawer built and wired.** New `src/components/ChatPanel.tsx` (drawer shell ported from the mockup's `ChatPanel`), `src/lib/talosChat.ts` (adapter: 64-hex fingerprint in `localStorage["talos_fp"]`, 15-min session token from `POST /api/ai/talos-session` re-minted once on 401, `POST /api/ai/talos-chat` with `campaign_id 43fdf2b6-7a5d-4e85-be6b-911f42aa787f`, message-shape enforcement in `buildApiMessages()`, scripted fallback when unreachable), `src/components/ChatMarkdown.tsx` (safe bold+bullets renderer, text nodes only), `.chatveil/.chatpanel` CSS block in `security.css`, `talos` content group in `src/content/security.ts`, `chatOpen` state + `<ChatPanel>` mount in `src/flow/screens/SecurityLanding.tsx`. Evidence: `npx tsc --noEmit` clean, `npm run lint` clean (incl. no-em-dash rule), `npx vitest run` **367/367** (30 new tests in `test/talosChat.test.ts` + `test/chatMarkdown.test.ts`), `npx next build` clean, `curl` `/` -> 307 -> `/landing` -> 200.
24
+ - **KB captured and pushed** (team repo `_main`): `campaign-chat-handoff.md` created (f27d7f5, API key VALUE deliberately omitted); `/capture` wrote `landing-chat-drawer.md` (new) + updated `security-landing-page.md`, `campaign-chat-handoff.md`, `toga-blox/features/talos-assistant.md` (bfd7ea8); mockup-deletion follow-up pushed (595620a).
25
+ - **Dead eslint ignore removed**: `"AI BDR - Security/**"` dropped from `eslint.config.mjs` `globalIgnores` after the developer deleted that mockup folder; lint still clean (the 96 errors it used to hide are gone with the folder).
26
+ - **Decision path confirmed with the developer via AskUserQuestion**: "Bridge now, blox later" + "Real backend now" (both recommended options accepted).
27
+
28
+ ## What did NOT work — DO NOT RETRY THESE
29
+ - **Spawning the `context-primer` subagent at kickoff Step 4**: hard-blocked by the `kickoff-gate.js` hook (stage 2) with "prime it YOURSELF, IN THE MAIN SESSION — do NOT spawn a subagent". The kickoff skill text says to spawn it; the hook wins. Correct flow: Read the preflight `reads[]` docs directly in the main thread, then run `node "<TEAM_REPO>/knowledge.js" kickoff-primed` to release the gate.
30
+ - **`git push origin _main` in the team knowledge repo without rebasing first**: rejected non-fast-forward TWICE this session (CI "chore: bump version" commits land constantly). Always `git pull --rebase origin _main` immediately before the push, as a separate command; a one-shot `push || (pull --rebase && push)` bash chain did not run the fallback as expected.
31
+ - **Writing a long markdown file via a Bash single-quoted heredoc**: failed with "unexpected EOF while looking for matching `''" (the body's backticks/quotes tripped the shell). Use the Write tool for multi-line files.
32
+ - **Glob on `c:\WWW` or the BDR repo root** (carried forward): ripgrep 20s timeout on the huge tree. Use Bash `ls`/`find` with explicit paths.
33
+ - **Grep content-mode showing `\*` for CSS `/*` comment openers** (carried forward): display artifact, NOT the mockup's literal-`\n` corruption. Do not "fix" those lines.
34
+ - **Consuming blox Talos directly ("force blox now")**: rejected before trying, for the record — blox Talos is unpublished to npm (supply uses a local symlink) and blox peers cap React at `^18` while BDR is React `19.2.4`; would also drag react-router-dom/FA-Pro registry auth into a zero-dep Next app. Do not attempt until blox publishes with React 19 peers.
35
+
36
+ ## Not tried yet (candidates for next session)
37
+ - **Confirm the backend is actually reachable from a browser**: api.togaiq.com endpoints are origin/UA-gated; `localhost:3000`, `bdr.dev.sandbox.togatech.com`, and the prod domain must be allowlisted by the Talos backend owner. Live replies carry suggestion chips; scripted-fallback replies carry none — that is the tell.
38
+ - Render the reply's `cta` button (`book_call`/`contact`/`learn_more`) and `cited_slides` thumbnails (needs `GET /api/ai/campaign/{id}`; presigned image URLs expire ~7 days, re-fetch on load/error). Both are typed in `TalosResponse` but unrendered.
39
+ - Visual QA of the drawer at phone widths (`width:min(400px,92vw)`) — only desktop was smoke-tested via curl/build; no screenshot pass done.
40
+ - Verify the Calendly slug `toga-technology/risk-assessment` is live (still a mockup placeholder).
41
+ - Graft `src/content/security.ts` into the `CampaignBundle` schema when the landing becomes campaign-driven.
42
+ - Contribute `talosChat.ts` (+ `ChatMarkdown.tsx`) upstream to blox for TRUE-80692's deferred "real onSend backend adapter" ticket — tell apeterson; make `baseUrl` a required option there (blox consumers are Vite, `process.env.NEXT_PUBLIC_TALOS_API_BASE` is a Next-ism).
43
+ - **Commit**: everything is working-tree only. Branch creation needs tcox's go-ahead (team rule). `git status` shows the deleted mockup files as `AD` (staged-added, then deleted) — `git add -A` resolves that.
44
+ - Backend owner should rotate the exposed `campaign:manage` key (key_id `2c2a4389-272f-4f05-b36f-f9b6f3de033f`) before go-live — value lives in the secret manager / source handoff, never in the KB.
45
+ - KB hygiene (unrelated to BDR): 20+ knowledge docs end with stray `</content>` / `</invoke>` lines (`grep -rln "^</invoke>$" knowledge/`); `validate` does not catch it. Needs a dedicated cleanup pass.
46
+
47
+ ## Current file state
48
+ | File | Status | Notes |
49
+ |------|--------|-------|
50
+ | src/app/security.css | modified | + mobile pass (560px/420px blocks) + `.chatveil/.chatpanel` drawer block + `.chatmd-*` rules, all under `.security-shell` |
51
+ | src/components/ChatPanel.tsx | created | Right-side drawer shell; Escape/scrim close, focus in/restore; one standing action "Book a meeting" -> Calendly; deliberately disposable (blox TalosPanel later) |
52
+ | src/lib/talosChat.ts | created | `createTalosChatAdapter()` -> `TalosAdapter { onSend(text), reset() }`; fingerprint, token mint/re-mint-once, `buildApiMessages()`, `toTalosResponse()`, `scriptedReply()` fallback; `fetchImpl` test seam |
53
+ | src/components/ChatMarkdown.tsx | created | `toBlocks()` + `renderBoldSpans()`; p/ul/li/strong only, no HTML/links |
54
+ | src/content/security.ts | modified | + `talos` group (title, greeting, chips, placeholder, bookCta, disclaimer, campaignId, fallback entries); dev re-indented to tabs and renamed header CTA to "Ask Alex Anything" |
55
+ | src/flow/screens/SecurityLanding.tsx | modified | `chatOpen` state; header CTA + "Learn more" `onClick` open drawer; `<ChatPanel>` mounted inside `.security-shell`; header comment updated |
56
+ | eslint.config.mjs | modified | removed dead `"AI BDR - Security/**"` ignore (`mockup/**` kept) |
57
+ | test/talosChat.test.ts | created | 23 tests: fingerprint, message shaping (incl. 30-cap alternation), scripted matcher, response filtering, adapter mint/cache/401-re-mint/fallback/reset |
58
+ | test/chatMarkdown.test.ts | created | 7 tests: blocks, CRLF, bullets, html-looking text stays literal, bold spans, unclosed `**` |
59
+ | AI BDR - Security/ (folder) | deleted by dev | mockup prototype removed from repo after port completed; shows as `AD` in git status |
60
+ | mockup/ (studio flow prototype) | untouched | still in repo |
61
+ | src/proxy.ts | untouched | matcher still only `/` + 5 API routes; drawer talks to a third-party origin, limiter not applicable |
62
+
63
+ All changes are **uncommitted, working tree only** — awaiting tcox's branch/commit go-ahead.
64
+
65
+ ## Decisions made
66
+ - **"Bridge now, blox later" (TRUE-80692)** — build the drawer shell now with the adapter deliberately mirroring blox's `TalosAdapter` seam (`onSend(text) => Promise<TalosResponse>`), so the later swap to blox `TalosPanel` is a re-wire. Rejected: waiting for blox (CTAs would stay inert indefinitely); forcing blox in (unpublished + React 18 peer cap + heavy peer deps).
67
+ - **Wire the real backend now, scripted answers as fallback only** — the campaign-chat contract was already in the KB; the mockup's `askAlex` matcher became data in `security.ts` `talos.fallback`. Rejected: shipping stub-only.
68
+ - **Hand-rolled `ChatMarkdown` instead of `@assistant-ui/react-markdown` / react-markdown** — zero new deps, emits text nodes only, trivially satisfies the handoff's no-HTML/no-unsafe-link rule. Markdown fails soft so unsupported constructs render as text.
69
+ - **"Call me now" NOT rendered in the drawer** — its mockup destination is the parked studio call screen. Standing action is "Book a meeting" -> the page's Calendly href. `cta`/`cited_slides` deferred.
70
+ - **Never read the backend's silent lead/qualification fields** — only `message/suggestions/cta/cited_slides` cross into `TalosResponse` (per the handoff; no CRM sink exists yet).
71
+ - **`campaignId` lives in content (`security.ts`), base URL has an env override** (`NEXT_PUBLIC_TALOS_API_BASE`, default `https://api.togaiq.com`). Campaign id is not a secret; per-landing-page config belongs with the page's content.
72
+ - **Browser-direct calls to api.togaiq.com, not a Next route proxy** — the session token is bound to client IP + UA; proxying server-side would break that binding. The flow is keyless by design so no secret crosses to the client.
73
+ - **API key VALUE kept out of the KB** — the source handoff embeds the `campaign:manage` key in plaintext; the knowledge repo is on GitHub and team rules forbid committing secrets. Recorded key_id + location + rotate-before-go-live only.
74
+ - **KB filing stays under `2.0/apps/ai-bdr/`** — no `2.0/apps/bdr/` tree; that split remains a deliberate future elevated decision.
75
+ - **Mockup deletion (dev's call) reflected in docs, not erased** — provenance kept as history; live-path references, the porting gotchas, and the eslint ignore retired.
76
+
77
+ ## Blockers
78
+ None hard. Pending external inputs: (1) Talos backend owner to allowlist BDR origins on api.togaiq.com (until then the drawer silently uses scripted fallback); (2) design decisions on rendering `cta`/`cited_slides` and on the Calendly link; (3) tcox's go-ahead to branch/commit.
79
+
80
+ ## Exact next step
81
+ > In `C:\WWW\BDR` run `npm run dev`, open http://localhost:3000/landing, click "Ask Alex Anything", and send "What does the free assessment scan?". If the reply shows suggestion chips, the backend is live from localhost; if it shows none (scripted fallback), message the Talos backend owner to allowlist `localhost:3000` and `bdr.dev.sandbox.togatech.com` on api.togaiq.com. Then get tcox's branch go-ahead and commit the working tree (`git add -A` also resolves the `AD` mockup deletions).
82
+
83
+ ---
84
+ _Saved by /session-save on 2026-08-25_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.642",
3
+ "version": "1.0.644",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",