toga-ai 1.0.638 → 1.0.640

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.
@@ -5,6 +5,7 @@
5
5
  | [AI-BDR Architecture](architecture.md) | **AI-BDR** is TOGA's outbound AI sales-development representative. | ai-bdr/README.md, ai-bdr/requirements.txt, ai-bdr/.env.example, ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/vapi/templates/assistant.template.json, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py |
6
6
  | [BDR Web Funnel — Full Implementation Plan](features/bdr-web-funnel-plan.md) | > **Execution status (2026-07-16):** PLAN **Phases 0–5 are BUILT and QA'd**, Phase 6 > was **mostly already built** (the `info` contract was implemented at comm | bdr/PLAN.md, bdr/mockup/app.jsx, bdr/mockup/screens.jsx, bdr/mockup/components.jsx |
7
7
  | [Call Orchestration — PHP Worker ↔ Vapi (the integration seam)](features/call-orchestration.md) | The **PHP worker** is the orchestrator; **Vapi** is the actor. | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/scripts/update_structured_output.py |
8
+ | [AI-BDR Campaign Chat — Backend Contract + Frontend Widget Handoff](features/campaign-chat-handoff.md) | The consolidated backend + frontend handoff for the **AI-BDR campaign chat** — a grounded AI-BDR chat widget for landing pages, powered by the Talos backend at | ai-bdr-campaign-handoff.md |
8
9
  | [Live Call Status — Vapi call API → funnel Success screen lifecycle](features/live-call-status.md) | The BDR funnel's Success screen shows the **real** state of the call it just requested — dialing / ringing / answered-and-talking / ended / not-answered — inste | bdr/src/server/vapiCall.ts, bdr/src/server/callStatus.ts, bdr/src/server/statusToken.ts, bdr/src/server/callbackService.ts, bdr/src/server/workerDialer.ts, bdr/src/lib/callStatus.ts, bdr/src/app/api/call-status/route.ts, bdr/src/app/api/call-now/route.ts, bdr/src/flow/useCallStatusViewModel.ts, bdr/src/flow/callStatusApi.ts, bdr/src/flow/screens/Success.tsx, bdr/src/content/schema.ts, bdr/src/content/default.ts, bdr/src/proxy.ts |
9
10
  | [Cybersecurity Landing Page (/landing — the site's current main page)](features/security-landing-page.md) | A single-page **cybersecurity services marketing landing page** at **`/landing`** in the `bdr` repo — and, as of 2026-08-24, **the site's main page**: `/` **307 | bdr/src/app/landing/page.tsx, bdr/src/app/security.css, bdr/src/flow/screens/SecurityLanding.tsx, bdr/src/content/security.ts, bdr/src/components/LogoSymbol.tsx, bdr/src/components/AssistantMark.tsx, bdr/src/components/Ico.tsx, bdr/src/components/Particles.tsx, bdr/next.config.ts, bdr/eslint.config.mjs |
10
11
  | [Vapi Integration — Assistants, Tools, Structured Output](features/vapi-integration.md) | Everything inside Vapi: the three assistants, the three shared tools, the shared structured-output schema, the Liquid-templated system prompt, and the Python sc | ai-bdr/vapi/templates/assistant.template.json, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md |
@@ -0,0 +1,190 @@
1
+ ---
2
+ title: AI-BDR Campaign Chat — Backend Contract + Frontend Widget Handoff
3
+ framework: "2.0"
4
+ repo: ai-bdr
5
+ project: AI-BDR
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-08-24
10
+ owners: [tcox]
11
+ files:
12
+ - ai-bdr-campaign-handoff.md
13
+ related:
14
+ - security-landing-page.md
15
+ - web-funnel-app.md
16
+ - ../architecture.md
17
+ - ../../talos/features/aegra-api.md
18
+ ---
19
+
20
+ ## What this is
21
+
22
+ The consolidated backend + frontend handoff for the **AI-BDR campaign chat** — a
23
+ grounded AI-BDR chat widget for landing pages, powered by the Talos backend at
24
+ **`https://api.togaiq.com`**. A landing page passes an opaque `campaign_id`; the
25
+ backend resolves the owning tenant, grounds the answer in that campaign's
26
+ distilled PDF collateral, and returns **markdown prose + a structured `data`
27
+ object** carrying the interactive UI (quick-reply chips, one CTA button, cited
28
+ deck slides) and a **silent lead/qualification signal**.
29
+
30
+ **Authoritative source:** `ai-bdr-campaign-handoff.md` (the single consolidated
31
+ handoff; supersedes `docs/talos-bdr-generative-ui-contract.md` and
32
+ `docs/ai-bdr-campaign-backend-handoff.md`, which are retained for history;
33
+ architecture deep-dive in `docs/ai-bdr-campaign-chat-architecture.md`). This
34
+ knowledge doc distills the contract and records the load-bearing decisions and
35
+ gotchas — the source handoff carries the full copy-paste implementation code.
36
+
37
+ **Direct relevance to BDR:** the cybersecurity landing page's inert
38
+ **"Ask Me Anything"** header CTA (see [security-landing-page.md](security-landing-page.md) —
39
+ the mockup's un-ported chat drawer) is exactly the kind of consumer this widget
40
+ was specified for, and the first live campaign is **"External Pen Testing"** —
41
+ the same offer the landing page sells.
42
+
43
+ ## Design decision (settled — do not re-litigate)
44
+
45
+ Response format is **markdown + structured action data** (Option A), chosen over
46
+ XML component tags / component trees / AG-UI: markdown fails soft and fits a
47
+ non-streaming reply; the interactive actions live in a reliable JSON side-channel
48
+ (`data`), never embedded in the prose. Consequences:
49
+
50
+ - **No XML parser and no tag allowlist** on the frontend (the prior XML-tag
51
+ version required both).
52
+ - **No server-side normalization** — `message` is returned exactly as the model
53
+ produced it.
54
+
55
+ ## Backend (what exists, verified live 2026-08-21)
56
+
57
+ - **Model:** Claude Sonnet 4.6 (`us.anthropic.claude-sonnet-4-6`), server-pinned,
58
+ via tenant **True**'s Application Inference Profile (cost-attributed Bedrock
59
+ spend). Temp 0.7, max tokens 1200. **Non-streaming direct Bedrock** via
60
+ `ai_chat_service` (not `talos_agent`, not `deep_agent`).
61
+ - **Grounding:** per campaign = a few PDFs → distilled markdown (≤8k chars/doc,
62
+ ≤24k aggregate) + per-page rendered images. **No KB / vectors / RAG.** Injected
63
+ server-side; the browser never sees or supplies it.
64
+ - **Tenant resolution:** `campaign_id` → control-plane `campaign_registry` →
65
+ `org_id` → tenant DB. Only `status='active'` campaigns resolve. No
66
+ `campaign_id` → falls back to `TALOS_CHAT_ORG_ID=True` (plain togatech.com
67
+ chat). The browser cannot forge a tenant.
68
+ - **Prompt + schema in one place:**
69
+ `libs/aegra-api/src/aegra_api/services/talos_chat_prompt.py`
70
+ (`TALOS_SYSTEM_PROMPT` + `TALOS_OUTPUT_SCHEMA`). To add an interactive element:
71
+ add a field to the schema there + a renderer in the app's allow-list.
72
+
73
+ ## Endpoints (base `https://api.togaiq.com`)
74
+
75
+ | Method & path | Auth | Called by |
76
+ |---|---|---|
77
+ | `POST /api/ai/talos-session` | none (origin/UA gated) | Browser |
78
+ | `POST /api/ai/talos-chat` | `Bearer <session_token>` | Browser |
79
+ | `GET /api/ai/campaign/{campaign_id}` | none (origin/UA gated) | Browser |
80
+ | `POST /api/ai/campaigns` | `X-API-Key` (`campaign:manage`) | **Backend/ops only** |
81
+
82
+ - **Session:** `talos-session` mints a **15-min JWT** bound to a browser
83
+ `fingerprint` (exactly 64 lowercase hex) + IP + User-Agent. Anti-abuse only —
84
+ carries no tenant. Bot UAs (curl/postman/axios) are denylisted; requests need
85
+ real browser `Origin`/`Referer`/`Sec-Fetch-*` headers.
86
+ - **Chat request:** `messages` (1..30, alternating, starts AND ends with `user`),
87
+ `fingerprint`, optional `campaign_id`. Body is `extra="forbid"` — sending
88
+ `model`, `system_prompt`, `temperature`, etc. returns **422** (all model params
89
+ server-pinned). Unknown/draft campaigns fall back gracefully to the base
90
+ prompt, never a chat error.
91
+ - **Campaign read:** resolves `cited_slides` page numbers to deck images.
92
+ Unknown/draft/archived → 404.
93
+
94
+ ## Response contract
95
+
96
+ ```jsonc
97
+ { "message": "<markdown>", "data": { … } | null }
98
+ ```
99
+
100
+ `data` fields — every one optional/nullable:
101
+
102
+ | Field | Rendered? | Meaning |
103
+ |---|---|---|
104
+ | `name`, `email`, `company`, `phone` | **NO** | Silent lead capture |
105
+ | `intent`, `use_case`, `timeline`, `qualified` | **NO** | Silent qualification |
106
+ | `suggestions` (string[], 2–4) | Yes | Quick-reply chips; click sends chip text as next user message |
107
+ | `cta` (`{label, action}`) | Yes | One CTA; `action` ∈ `book_call` \| `contact` \| `learn_more` |
108
+ | `cited_slides` (number[]) | Yes | Deck pages backing the answer; resolve via the campaign read |
109
+
110
+ **Never render the lead/qualification fields** — they feed CRM/lead routing
111
+ only. Only `suggestions`, `cta`, `cited_slides` are rendered, from an app-owned
112
+ allow-list.
113
+
114
+ ## Frontend build (for the widget consumer, e.g. the bdr landing page)
115
+
116
+ - **Stack:** React 18/19, `@assistant-ui/react` + `@assistant-ui/react-markdown`
117
+ (install latest, then **pin exact versions** — same pin discipline as blox).
118
+ - **Runtime:** `useLocalRuntime` + a custom `ChatModelAdapter.run()` that (1)
119
+ lazily mints/refreshes the session token (stable 64-hex fingerprint persisted
120
+ in `localStorage`; re-mint on 401), (2) POSTs the chat turn, (3) returns
121
+ `message` as markdown text and stashes `data` for the action row + silent lead
122
+ collection. Full working adapter code is in the source handoff §6.2.
123
+ - **`cta.action` wiring:** `book_call` → campaign's Calendly `cta.url` from the
124
+ campaign read (**currently `null`** on the first campaign — hide or fall back
125
+ to a contact prompt); `contact` → site contact form; `learn_more` → relevant
126
+ page; unknown action → render nothing. Keep booking behind one function — a
127
+ future backend tool-call will replace the `book_call` CTA with direct
128
+ auto-booking.
129
+ - **Lead handling:** silently accumulate the lead fields across turns;
130
+ auto-submit to CRM when enough is present (e.g. `email` + `qualified: true`).
131
+
132
+ **SAFETY (required):** render `message` only through the markdown renderer with
133
+ raw HTML disallowed (never `dangerouslySetInnerHTML`); links allow only `https:`
134
+ and same-origin relative (deny `javascript:`, `data:`, `http:`); `data` is
135
+ untrusted — `suggestions` render as plain text, only the three known
136
+ `cta.action` values are acted on; no `eval`, no dynamic component construction
137
+ from `data`.
138
+
139
+ ## First live campaign (real values)
140
+
141
+ - **campaign_id:** `43fdf2b6-7a5d-4e85-be6b-911f42aa787f` — "External Pen
142
+ Testing", org `True`, 3 documents / 3 pages, verified live 2026-08-21.
143
+
144
+ ## The `campaign:manage` API key (ingest) — VALUE NOT RECORDED HERE
145
+
146
+ `POST /api/ai/campaigns` (ingest) is gated by an `X-API-Key` with scope
147
+ `campaign:manage` (tenant True), **key_id `2c2a4389-272f-4f05-b36f-f9b6f3de033f`**.
148
+
149
+ - **The key VALUE is deliberately not in this doc** (team rule: no secrets in
150
+ git). It lives in the **secret manager** and in the source handoff file.
151
+ - The scope can only create/ingest campaigns — no data/agent/model/tenant-admin
152
+ access.
153
+ - **The source handoff embeds the value in plaintext per an owner decision** —
154
+ the handoff itself says to **rotate this key (revoke key_id `2c2a4389-…`,
155
+ re-mint) before go-live**. Treat any copy of that file as secret-bearing.
156
+ - New campaigns: ingest via that endpoint, or on the box via
157
+ `scripts/create_campaign_api_key.py` + `scripts/seed_campaign.py`.
158
+
159
+ ## Gotchas
160
+
161
+ - **Presigned deck-image URLs EXPIRE (~7 days).** `image_url` is a presigned S3
162
+ URL because `CAMPAIGN_ASSET_CDN_BASE_URL` is currently unset — do not cache
163
+ long-term; re-fetch the campaign read on page load / image error. Once the
164
+ CloudFront+OAC base URL is set, these become stable CDN URLs.
165
+ - **Rate limits are currently LIFTED (accepted risk, owner decision).**
166
+ Per-visitor limiting is effectively off while `TALOS_CHAT_TRUSTED_PROXY_COUNT`
167
+ is unset and XFF handling is unfixed. Do not revert until the owner revisits.
168
+ (The proper fix mirrors BDR's own trusted-XFF-position lesson — see
169
+ [web-funnel-app.md](web-funnel-app.md)'s rate-limiter gotchas.)
170
+ - **`extra="forbid"` on the chat body** — any speculative extra field (a model
171
+ override, a temperature) is a 422, not ignored.
172
+ - **Message list shape is strict:** alternate user/assistant, start and end with
173
+ `user`, max 30.
174
+ - **`book_call` CTA can arrive while the campaign's Calendly URL is null** —
175
+ the widget must degrade gracefully (the first campaign currently has
176
+ `cta: null`).
177
+ - **Never advance the prod DB ahead of the deployed image** —
178
+ `verify_tenant_schemas` blocks startup on a head mismatch and crash-loops the
179
+ fleet (`campaign_tables_v1` is operator-run per tenant DB before rollout).
180
+ - **The `X-Internal-Service-Key` bypass in `handle_chat` must remain
181
+ internal-only** now that campaign traffic rides the same path.
182
+
183
+ ## Change history
184
+ - 2026-08-24 — Initial doc: captured the consolidated AI-BDR campaign chat
185
+ handoff (backend contract at api.togaiq.com + frontend widget spec on
186
+ assistant-ui). Markdown + structured-data format settled over XML tags;
187
+ keyless browser auth via 15-min fingerprint-bound session tokens; silent
188
+ lead/qualification side-channel; first live campaign "External Pen Testing".
189
+ The `campaign:manage` key VALUE deliberately omitted (no secrets in git) —
190
+ key_id recorded, rotate-before-go-live flagged. (tcox)
@@ -28,7 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
28
28
  - **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
29
29
  - **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
30
30
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
31
- - **ai-bdr** (AI-BDR) — 10 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
31
+ - **ai-bdr** (AI-BDR) — 11 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
32
32
  - **toga2-commerce** (TOGa Commerce) — 19 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
33
33
  - **toga25-supply** (TOGa 2.5 Supply) — 12 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
34
34
  - **toga-blox** (TOGa Blox) — 10 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
@@ -0,0 +1,91 @@
1
+ ---
2
+ type: session
3
+ slug: surface-missed-migrations
4
+ title: Recover surface-layer migrations missed in the first prod push
5
+ author: apeterson
6
+ repos: [dbchanges2, _underscore, api2, toga25-supply]
7
+ framework: "2.0"
8
+ client: shared
9
+ status: active
10
+ created: 2026-08-24
11
+ updated: 2026-08-24
12
+ ---
13
+
14
+ # Session: surface-missed-migrations
15
+ **Date:** 2026-08-24
16
+ **Project/Repo:** dbchanges2 (+ _underscore, api2, toga25-supply) (2.0)
17
+ **Task:** Find and re-land the surface-layer dbchanges2 migrations that were skipped in the 2026-08-21 first prod push of the Surface layer, and write cluster-safe prod ports for the ones that can never run on prod as originally authored.
18
+
19
+ ---
20
+
21
+ ## What WORKED
22
+
23
+ - **Diagnosed the Quad Approve "PO entered" gate from data, not assumption.** Query on local `Client_Quad.SurfaceOverrides` returned only two `VISIBILITY_RULE` rows (elements 32, 124) and **zero** `ENABLED_RULE` rows; `SHOW COLUMNS ... LIKE 'attribute'` confirmed the ENUM already contained `VISIBILITY_RULE`/`ENABLED_RULE`. This ruled out the documented Quad ENUM-coercion trap and the junk-`attribute=''` scenario, leaving "the file simply never ran."
24
+ - **`Client_Quad/2026-08-24 - ProdPortApprovePoNumberEnabledRule.sql` — applied locally, verified working.** Approve is now disabled when no PO is present on both the record-actions bar and the listing row menu, and the tooltip reads "Add a PO Number to Approve". Merged file contains four statements: two `ENABLED_RULE` inserts (surface 8/element 17, surface 3/element 29, `{"type":"poNumberEntered"}` in `c_longValue`) plus an `INSERT`+`UPDATE` pair on `MessageTranslations` messageId 25.
25
+ - **Verified the inlined Core ids against live local Core** before trusting the port: query returned exactly `(surface 8, element 17)` and `(surface 3, element 29)` for `actionSlug = salesOrder.approve`. Also revealed element 17 carries a base `enabledRule` and element 29 does not.
26
+ - **Cluster-safety audit of all four prod-port files passed.** A grep for foreign-database qualifiers in executable (non-comment) SQL across `Client_Quad`, `Client_Compass`, `Client_CompassCanada`, `Client_Nychh` 2026-08-24 files returned **zero** references outside each file's own database.
27
+ - **Confirmed `Client_Nychh/2026-08-24` had already been applied locally** — `SELECT surfaceId, surfaceElementId FROM SurfaceOverrides WHERE attribute='CONFIG'` returned `40 / 120` with nothing at `41 / 125`.
28
+ - **Confirmed Quad's decision-summary hides landed correctly** — elements 125, 131, 132 all at `IS_VISIBLE = '0'` locally.
29
+ - **Traced the tooltip failure to the framework, by reading code.** `Surface.php:937` guards the `MessageTranslations` overlay on `$languageId > 0`; `V2.php:5741-5745` carries an explicit TODO that the JWT never populates `id.client.languageId`. Developer then found they were on a stale `_underscore` branch; switching branches fixed it locally.
30
+ - **Found the missing Core file.** `Core/2026-08-24 - RestoreApproveDenyRowActionsVisibility.sql` re-lands `Core/2026-07-20e`, which never reached prod — elements 29/30 were left at `isVisible=0` by `2026-07-20d` for every tenant.
31
+ - **Rewrote the comments on 9 migration files** to the `backend-php.md` standard (no prose preamble, one short `#` comment per statement). Preambles of 45-68 lines cut to 6-25. **No SQL altered** — verified by line/comment counts and a zero-hit grep for leftover `-- ---` banners.
32
+ - **`/capture` published 5 doc UPDATEs** to the team KB (`PUSHED to _main`, validate OK, 410 docs indexed).
33
+
34
+ ## What did NOT work — DO NOT RETRY THESE
35
+
36
+ - **Do NOT tell anyone to clear `~/toga/cache/surface_meta/*.json`.** I gave this instruction from a stale KB claim. It is wrong: `_underscore/Model/Core/Surface.php:55` states *"build the bundle fresh on every request — the frontend caches via React Query; the backend intentionally holds no surface cache"*, and the directory is empty, last modified 2026-08-21, with a `surface_meta_bak` sibling. There is no backend surface cache to clear.
37
+ - **Do NOT tell anyone a hard reload clears the FE surface-meta cache.** It does not. `toga25-supply/src/App.tsx` wraps the app in `PersistQueryClientProvider` + `createSyncStoragePersister` writing to localStorage key `supply-chain-query-cache`, and `useFetchSurfaceMeta.ts` sets `staleTime`/`gcTime` to `Infinity`. A reload **restores** the stale bundle. Only **logout** clears it (`AuthContext.tsx:124-126` → `localStorage.clear()` then `queryClient.clear()`), or manually deleting that key.
38
+ - **Do NOT conclude the missing `branch` key in `.ebextensions/git.sandbox-client.json` is a misconfiguration.** I claimed this was the root cause of sandbox running old framework code. It is wrong. `.ebextensions/git.php` resolves `$branchName = property_exists($git,'branch') ? $git->branch : ('_' . strtolower($env))` — omitting the key correctly derives `_sandbox-client` from the EB `ENVIRONMENT` variable. Removing it was intentional and correct.
39
+ - **Do NOT conclude `_resolveLanguageId()` was never implemented, or that the knowledge base was wrong about it.** I asserted both, with three pieces of corroborating evidence (`Surface.php:54/73` reading `id.client.languageId` with `returnIfNotSet: 0`, a repo-wide grep returning zero matches, and the `V2.php` TODO). All three were accurate **for the branch that happened to be checked out**, and the conclusion was still wrong. `_resolveLanguageId` exists on `_sandbox-client`, `_sandbox-dev`, `surface-layer` and `origin/_beta`, merged into `_sandbox-client` on **2026-07-22** via `6809c6b9`. Because `_underscore` is pulled at deploy from a moving per-environment branch, always run `git branch --show-current` before declaring framework code absent.
40
+ - **Do NOT propose changing `Surface.php` or `V2.php` to fix the `languageId` overlay.** I recommended this (option B: resolve a fallback from the client `Languages` row). It is unnecessary — the fix already exists and shipped over a month ago.
41
+ - **Do NOT redact secrets with an ad-hoc shell one-liner.** I ran `sed 's/\("\(token\|password\)"...\)/.../gi'` over `.ebextensions/git.*.json` intending to mask the value. **BSD `sed` on macOS does not support `\|` alternation**, so the pattern never matched and the GitHub PAT printed in full into the transcript. Use the `Read` tool for credential-bearing files.
42
+
43
+ ## Not tried yet (candidates for next session)
44
+
45
+ - Applying the four prod-port files to **sandbox-client**: `Core/2026-08-24`, `Client_Compass/2026-08-24`, `Client_CompassCanada/2026-08-24` (Quad's already ran there). Then to prod.
46
+ - The three **pre-flight prod checks** drafted but never run against prod Core: verify inlined ids 17/24/29/31/120 resolve (and that 125 does **not** exist yet); verify `SELECT id, name FROM Roles WHERE name='Admin'` returns exactly one row in `Client_Compass` and `Client_CompassCanada`; check `SHOW WARNINGS` after the `INSERT IGNORE` Core reseed.
47
+ - **Finding the executor's applied-file log.** The highest-leverage unfinished thread: the executor records which files it has applied ("won't replay `07-21a/b`"), so if that record is queryable, the whole missed-migration hunt becomes a list diff instead of per-file state inference. `SHOW TABLES LIKE '%hange%' / '%igration%' / '%cript%'` against Core was drafted but never run. **Start here** — it would make the remaining client-DB sweep exact.
48
+ - The **per-client sweep** of the 6 client DBs (the last session swept Core only, 17 files, found 1 gap). No equivalent sweep has been done for `Client_Compass`, `Client_CompassCanada`, `Client_Quad`, `Client_Nychh`, `Client_Elite`, `Client_Prudential`.
49
+ - **Enter PO modal from the listing row menu** — FE gap, not data. `EnterPoNumberModal` is mounted only inside `SalesOrderRecordModalLayout`/`SalesOrderView`, so it is unreachable from the listing row-action dispatch.
50
+ - **NYCHH inventory topology behavior change** needs developer sign-off before shipping.
51
+ - Deciding whether the **reserved-Core-id-literals rule** should be promoted from the two feature docs into `2.0/standards/backend-php.md` (an ELEVATED doc — needs review).
52
+ - Renaming `Client_Nychh/2026-08-24 - Fix...` to add its missing `a` letter suffix (deliberately deferred — see Decisions).
53
+
54
+ ## Current file state
55
+
56
+ | File | Status | Notes |
57
+ |------|--------|-------|
58
+ | `dbchanges2/Client_Quad/2026-08-24 - ProdPortApprovePoNumberEnabledRule.sql` | Modified, applied local + sandbox | Merged the enable rule and the tooltip into one file; unqualified tables; comments trimmed. `08-24b` was created then deleted after merging. |
59
+ | `dbchanges2/Core/2026-08-24 - RestoreApproveDenyRowActionsVisibility.sql` | Modified (comments only), applied local | Not yet on sandbox or prod. |
60
+ | `dbchanges2/Client_Compass/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql` | Modified (comments only) | Applied to sandbox this session; not prod. |
61
+ | `dbchanges2/Client_CompassCanada/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql` | Modified (comments only) | Applied to sandbox this session; not prod. |
62
+ | `dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql` | Modified (comments only), applied local | Filename missing its `a` letter suffix. MUST ship with `Core/2026-08-21`. |
63
+ | `dbchanges2/Core/2026-08-21 - SalesOrderDecisionSurfacesReseed.sql` | Modified (comments only) | Applied local + sandbox. |
64
+ | `dbchanges2/Client_Compass/2026-08-21 - SalesOrderDecisionSummaryTotalConcat.sql` | Modified (comments only) | Applied local + sandbox. |
65
+ | `dbchanges2/Client_CompassCanada/2026-08-21 - SalesOrderDecisionSummaryTotalConcat.sql` | Modified (comments only) | Applied local + sandbox. |
66
+ | `dbchanges2/Client_Quad/2026-08-21 - SalesOrderDecisionSummaryOverride.sql` | Modified (comments only) | Applied local + sandbox. Comment now notes the `NOT EXISTS` guard cannot correct a wrong value. |
67
+ | `toga-tech/knowledge/` (5 docs) | Updated + pushed to `_main` | surface-resolver, surface-frontend, api2 branch doc, surface-layer-schema, nychh profile. |
68
+
69
+ **Nothing is committed in `dbchanges2`** — all changes are working-tree only, on branch `TRUE-81190`.
70
+
71
+ ## Decisions made
72
+
73
+ - **Merge the tooltip fix into `08-24a` rather than keep a separate `08-24b`.** Rationale: both were unapplied everywhere, same client, same feature (the gate and the tooltip explaining it). Rejected: two files, which would have needed independent tracking for no benefit.
74
+ - **Handle the tooltip with `INSERT ... NOT EXISTS` *plus* `UPDATE`.** Rationale: prod has no row, local/beta has one with the older "Enter PO Details First" text; the pair converges on the correct value from either starting state. Rejected: insert-only, which would silently no-op on local.
75
+ - **Leave `Client_Quad/2026-08-21`'s insert-only `NOT EXISTS` guard alone.** Rationale: it is already applied to local and sandbox, and the repo rule forbids editing an already-run migration. Verified all three elements hold the correct `'0'` value, so there is no live defect. If prod turns out to hold a wrong row, that becomes a new dated file.
76
+ - **Do not rename `Client_Nychh/2026-08-24`** to add its missing letter suffix. Rationale: the executor tracks applied files by name, so renaming would present it as a new file and re-run it. It is idempotent so that would be harmless, but there is no upside.
77
+ - **Keep two things when trimming the verbose comments:** the Core reseed's element id map (client override files hardcode those numbers) and the NYCHH ship-together warning. Rationale: both are expensive to rediscover and neither is inferable from the SQL.
78
+ - **Keep the Admin `roleId` as a runtime lookup** in the Compass/CC ports rather than inlining it. Rationale: role ids are per-client and per-environment (Compass Admin=4, Compass Canada=5 locally) and `Roles` lives in the same database being written to, so the lookup is cluster-safe. Every statement is guarded on `@adminRoleId IS NOT NULL` so a missing role is a no-op rather than a bad write.
79
+
80
+ ## Blockers
81
+
82
+ - **The GitHub PAT in `api2/.ebextensions/git.*.json` must be rotated.** It is committed across ~20 per-environment files and in git history, and received additional exposure this session via the failed-redaction incident above. Treat as compromised; rotate and move to SSM Parameter Store / EB environment properties. This is the one open action from this session that lives outside a file.
83
+ - **NYCHH inventory topology change awaits developer sign-off**, and its fix cannot ship without `Core/2026-08-21` in the same release (or NYCHH's Assigned Manager breaks).
84
+ - Not blocking, but unresolved: the sandbox tooltip was still showing the Core default at the point the session ended. Data was verified correct in sandbox (`MessageTranslations` messageId 25 = "Add a PO Number to Approve", `Languages` has `en` at id 1) and the `_sandbox-client` branch has carried `_resolveLanguageId` since 2026-07-22 — so the open question is whether that environment has been **redeployed** since, or whether the reading was taken from a persisted FE cache.
85
+
86
+ ## Exact next step
87
+
88
+ > Log out of sandbox-client supply and log back in (this is the only thing that clears the persisted `supply-chain-query-cache` localStorage entry), then re-open a Quad order with no PO and read the `salesOrder.action.approve.disabled` value in the `/v2/surfaces/meta` response **in the Network tab**, not from cached state. If it still reads "Assign a Manager to Approve", check when the api2 sandbox-client environment last deployed — `_underscore` is cloned at deploy time, so an environment that has not deployed since 2026-07-22 is running framework code without `_resolveLanguageId()`.
89
+
90
+ ---
91
+ _Saved by /session-save on 2026-08-24_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.638",
3
+ "version": "1.0.640",
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",