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)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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) —
|
|
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