toga-ai 1.0.966 → 1.0.968

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.
@@ -12,6 +12,7 @@
12
12
  | [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | TOGa Refresh 2026 "View Recommended Services" routing — per-device diagnostic `resultCode` decides serviceselection vs techsupport; open when touching that rout |
13
13
  | [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | Elite bidirectional ticket/attachment sync between TOGA 2 and TOGaDesk (`App_Api_Toga2::syncWithTogadesk`); open for the sync mechanics, watermarks, and Elite-s |
14
14
  | [App_Email Queued Sending & Attachments (Common.EmailsQueued)](features/email-queue-attachments.md) | 1.0 `App_Email` queued sending + attachment embedding via `Common.EmailsQueued`; open when a queued email loses attachments or when deploying worker/togadesk to |
15
+ | [App_Email SES Sender Identity (every From address must be verified in SES first)](features/email-ses-sender-identity.md) | 1.0 `App_Email` sends through Amazon SES SMTP; any new From address must be a verified SES identity first or SES rejects the send. |
15
16
  | [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | Branded HTML email base class `App_Email_Template` (header/body/footer); open when building or fixing a 1.0 branded email or its Outlook rendering. |
16
17
  | [App_Email Side Effects & Test Mode (how to send a real email that writes nothing)](features/email-test-mode-and-write-free-sends.md) | 1.0 `App_Email::send()` side effects (log/queue writes, test-mode recipient rewrite) and how to send a genuinely write-free test; open before writing any 1.0 dr |
17
18
  | [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | 1.0 error-reporting into the shared 2.0 Logs DB (`App_Error_Capture::captureException`), mirroring 2.0's Issue/Event; open when adding a 1.0 capture path or deb |
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: App_Email SES Sender Identity (every From address must be verified in SES first)
3
+ framework: "1.0"
4
+ repo: library
5
+ project: Library
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-07
10
+ owners: ["bala"]
11
+ files:
12
+ - library/app/email.php
13
+ related:
14
+ - ./email-queue-attachments.md
15
+ - ./email-test-mode-and-write-free-sends.md
16
+ - ../../tools/features/legacy-email-notifier.md
17
+ - ../../test/features/goagilant-to-togatech-email-migration.md
18
+ - ../../../../clients/compass-canada/profile.md
19
+ ---
20
+
21
+ 1.0 `App_Email` sends through Amazon SES SMTP; any new From address must be a verified SES identity first or SES rejects the send. Open before changing a `sendFromEmailAddress` / `fromEmailAddress` to a new address or a customer domain.
22
+
23
+ ## Summary
24
+
25
+ `App_Email` (`library/app/email.php`) sends via PHPMailer to SES SMTP at `email-smtp.us-west-2.amazonaws.com`, in the **toga-legacy** AWS account, region **us-west-2** (not us-east-1). SES only accepts a From address that is a verified identity in that account and region. The account is out of the SES sandbox, so To/CC recipients need no verification. Only the sender does.
26
+
27
+ ## How it works
28
+
29
+ - **Domain identity:** `togatech.com` is verified as a whole domain, so any `@togatech.com` From works.
30
+ - **Customer-domain addresses are single EMAIL_ADDRESS identities**, not domains. Verified so far: `techhub@compass-usa.com`, `Techhub.Canada@compass-canada.com`.
31
+ - **Verifying one address:** SES mails a link from `no-reply-aws@amazon.com`, subject "Amazon Web Services – Email Address Verification Request in region US West (Oregon)". The customer's mailbox owner must click it within **24h**. Until then the identity is pending and sends fail.
32
+ - **Alternative:** a domain identity for the customer domain. Needs the customer's IT to add DKIM DNS records. Use it when several addresses on that domain will send.
33
+ - **Order of work:** verify the identity in SES, confirm it shows verified, *then* run the SQL that switches `EmailTemplates.sendFromEmailAddress` / `IntegrationsEmail.fromEmailAddress`. Flipping the data first breaks every send from those templates.
34
+
35
+ ## Gotchas
36
+
37
+ - **The AWS verification email often lands in the customer's quarantine or spam.** Tell the mailbox owner to look there, and to release it within the 24h window. If it expires, re-send the verification.
38
+ - **A verified customer address is not proof it is used as a sender.** Compass USA vendor PO emails send from `compasssupport@togatech.com` (`Client_Compass.IntegrationsEmail`, 6 rows). `techhub@compass-usa.com` appears there only as To/CC.
39
+ - Apps that cannot run `App_Email::send()` (the tools app) still hit SES with the same rule. See [legacy email notifier](../../tools/features/legacy-email-notifier.md).
40
+
41
+ ## Change history
42
+ - 2026-10-07 — Documented the SES sender-identity rule and customer-address verification flow, after moving the Compass Canada TechHub sender to `Techhub.Canada@compass-canada.com` (bala)
@@ -6,8 +6,8 @@ project: Test
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-22
10
- owners: [jcardinal]
9
+ updated: 2026-10-07
10
+ owners: [jcardinal, mhammontree]
11
11
  files:
12
12
  - test/team/2.0 deployment/generate_client_sql.php
13
13
  - test/team/2.0 deployment/INPUT.sql
@@ -31,6 +31,12 @@ Use [Tools Multi-Client SQL](../../tools/features/multi-client-sql.md) instead (
31
31
  2. Reads one static list file; switch lists by commenting between `Clients_Db.txt` / `Logs_Clients_Db.txt` / `NetSuite_Clients_Db.txt` in the script.
32
32
  3. Per DB: banner comment, `` USE `<db>`; ``, then the SQL. Prints the combined script with a header and DB count.
33
33
 
34
+ ## The three list files (verified prod 2026-10-07)
35
+
36
+ - **`Clients_Db.txt` — 35 entries, and all 35 match the live tenant databases exactly. It is NOT stale.** The 11 extra `Core.Clients` rows have no database (see [per-client DB connections](../../../../2.0/apps/_underscore/features/per-client-database-connections.md)); adding them would point the fan-out at databases that do not exist.
37
+ - **`NetSuite_Clients_Db.txt` — 22 clients, an exact duplicate** of the clients whose `dbchanges2/Client_<Name>/_modules.txt` names `netsuite`. Same list kept twice; `_modules.txt` is the better source because it sits next to the client it describes.
38
+ - Retiring these files means retiring `generate_client_sql.php` (it reads all three, including a commented-out `Logs_Clients_Db.txt`) — not just deleting text files. Production already uses tools `/developers/multi-client-sql`.
39
+
34
40
  ## Gotchas
35
41
 
36
42
  - Lists are static — a stale/missing DB is silently skipped. The Tools tool reads them live.
@@ -42,4 +48,6 @@ Use [Tools Multi-Client SQL](../../tools/features/multi-client-sql.md) instead (
42
48
 
43
49
  ## Change history
44
50
 
51
+ - 2026-10-07 — Verified the list files: `Clients_Db.txt` is accurate (35/35); `NetSuite_Clients_Db.txt` duplicates dbchanges2 `_modules.txt`. (mhammontree)
52
+
45
53
  - 2026-09-22 — Marked superseded by Tools Multi-Client SQL; corrected input file (`INPUT.sql`) and list-switching. (jcardinal)
@@ -6,8 +6,8 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-24
10
- owners: [jcardinal]
9
+ updated: 2026-10-07
10
+ owners: [jcardinal, mhammontree]
11
11
  files:
12
12
  - tools/_/app/developers/multiclientsql.php
13
13
  - tools/mvc/developers/multi-client-sql/get.php
@@ -38,8 +38,19 @@ Replaces `test/team/2.0 deployment/generate_client_sql.php`. Development Team pe
38
38
  5. Every list returns a `skipped` array, shown on the page ("Skipped N (database not built — no DatabaseHosts row): …").
39
39
  6. Every DB/folder name is checked against `^[A-Za-z0-9_]{1,64}$` before it goes into GraphQL or a `USE` line. Page writes via `textContent`/`.value` only.
40
40
 
41
+ ## Reusing it from another tools page
42
+
43
+ - `token()` is already `public static` and reads `GITHUB_TOKEN` from `getenv()` + `$_SERVER` + `$_ENV`. That token already has read access to `agilantsolutions/dbchanges2` in production — a second page needs **no new PAT and no new EB property**.
44
+ - `graphql(string $fields, string $step)` is `private static` and carries the whole transport (curl, host pinned to `api.github.com`, bearer token, 30s timeout, one retry on a dropped connection, GraphQL error handling, and never logging the request because it holds the Authorization header). Making it `public static` is a one-keyword change with no behavior change — do that rather than re-implementing curl.
45
+ - `GITHUB_BRANCH` is a const `_main` baked into expressions inside private methods. A page needing another branch builds its own field strings and borrows only the transport — do **not** thread a `$branch` parameter through the shipped class.
46
+ - `DB_NAME_PATTERN` is `^[A-Za-z0-9_]{1,64}$` and **rejects dbchanges2 filenames** (`2026-06-03- BLANK_CLIENT_DATABASE.sql` has dashes, spaces, a dot). A page listing files needs its own filename pattern.
47
+ - `treeFolders()` keeps only `type === 'tree'` — folders only, never files.
48
+ - The `_modules.txt` parse (lowercase, trim, skip blank lines) already tolerates the trailing blank lines in `Client_Spglobal/_modules.txt`.
49
+
41
50
  ## Gotchas
42
51
 
52
+ - Nested module folders are not handled: `_modules/Ai/Bdr/` makes the module list show `Ai` with zero member clients. Pre-existing live limitation, not a regression.
53
+
43
54
  - `Databases` is a MySQL reserved word: `FROM Databases` / `JOIN Databases AS X` fails with error 1064. Backtick it (`` `Databases` ``) or qualify it (`Core.Databases`).
44
55
  - dbchanges2 has **no** `_production` branch; its deployed branch is `_main`.
45
56
  - A missing `[database_...]` config section is not catchable in 1.0 — `App_Error` renders its HTML error page and exits. The tool pre-checks `config['database_toga2core']` and throws a user-safe message. Local `config.dev-*.ini` files may lack that section.
@@ -52,5 +63,7 @@ Replaces `test/team/2.0 deployment/generate_client_sql.php`. Development Team pe
52
63
 
53
64
  ## Change history
54
65
 
66
+ - 2026-10-07 — Added the reuse map for other tools pages (`token()`, `graphql()`, the branch const and name patterns) and the nested `_modules/Ai/Bdr` limitation. (mhammontree)
67
+
55
68
  - 2026-09-22 — Created; shipped and verified in production. (jcardinal)
56
69
  - 2026-09-24 — Lists filtered by `Core.DatabaseHosts` for `ENVIRONMENT`; `skipped` list shown; verified prod 35 of 46. (jcardinal)
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-02
9
+ updated: 2026-10-07
10
10
  owners: [jcardinal, ajean, mhammontree]
11
11
  files:
12
12
  - tools/_/app/auth.php
@@ -88,6 +88,7 @@ The platform's rotating key/value parameter store, on the **2.0 PROD Core DB** (
88
88
  - **Failure path also fataled → fixed (2026-06-30).** `tools_ssoFail()` called `http_response_code(401)` to render the "Authentication failed" page, which itself fataled with "headers already sent" and the error handler escalated it to an uncaught `ErrorException` → a bare **500** instead of the intended 401 page. Root cause: `App_FrameworkIndex::render()` turns on the preloader and runs `App_Page::flushCapture()` (`ob_end_flush + flush`, `library/app/page.php:213`) **before** `body()/App_MVC::loadFile()` runs the mvc page. Fixed with the same `if (!headers_sent()) { http_response_code(401); }` guard (matches `App_MVC::routeTo`'s own check).
89
89
  - **`App_Sso::initiate()` hard-requires a non-empty `apiSecretAccessToken`** (`library/app/sso.php:64-65`, `empty()` guard → `InvalidArgumentException`; the token encrypts the RelayState). An empty/absent token surfaces as a redirect to **`/login?error=1` ("Sign in failed. Please try again.")** — the initiate leg catches the exception (`sso/initiate/get.php:30-34`) and routes to `/login?error=1`. This fails **before** the IdP round-trip, so it never reaches the consumer's **"Authentication failed"** (`tools_ssoFail`) page. **The two error UIs tell you which leg failed:** `/login?error=1` = initiate (RelayState encryption / token), "Authentication failed" = consumer (decrypt). This was the 2026-06-30 regression — removing the config token per the (wrong) earlier note made initiate throw.
90
90
  - **Both legs depend on `db_toga2core` + a populated `Core.Parameters` token.** If `[database_toga2core]` is not configured, `currentApiSecret()`/`handoffKeys()` runs `App_Database::query` against an unregistered alias and **throws**. Because initiate only catches `InvalidArgumentException`/`RuntimeException`, that surfaces as a **500** — not the login banner. **Diagnostic:** a 500 at initiate = missing `db_toga2core`; `/login?error=1` = empty/absent `API_SECRET_ACCESS_TOKEN`.
91
+ - **Local dev-bypass login fails with a bare "Sign in failed." — the cause is usually a missing config section, not SSO.** `tools/mvc/login/post.php` routed an invalid/empty email **and** a not-found user to `/login?error=1` with no logging, so three different causes looked identical. Each failure branch now writes one `error_log` line naming which branch fired (the not-found branch points at `[database_true]`). Root cause in practice: `config.dev-<name>.ini` is missing **`[database_true]`** and/or **`[database_toga2core]`**. Those two sections are commonly absent from local `config.dev-*.ini` files; without `[database_toga2core]` the Multi-Client SQL and Central IDs pages also fail. Copy them from a config that has both.
91
92
  - **No app-side replay defense.** The handoff token carries no nonce/timestamp the app verifies. Recommend the gateway embed `iat` + `jti`.
92
93
 
93
94
  ## Related
@@ -99,4 +100,6 @@ The platform's rotating key/value parameter store, on the **2.0 PROD Core DB** (
99
100
  - [TOGa Desk staff SSO (same pattern)](../../togadesk/features/staff-sso-login.md)
100
101
 
101
102
  ## Change history
103
+
104
+ - 2026-10-07 — Dev-bypass login now logs which failure branch fired; documented the missing `[database_true]` / `[database_toga2core]` local-config cause. (mhammontree)
102
105
  - 2026-10-02 — Clarified sign-in is one click on an SSO button; no auto-detect of a Microsoft session (mhammontree)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-09-23
9
+ updated: 2026-10-07
10
10
  owners: ["jcardinal", "rgirish", "mhammontree", "ajean"]
11
11
  files:
12
12
  - _underscore/_underscore.php
@@ -177,22 +177,32 @@ string** has a single leading capital, rest lower-case (e.g. `Towfoundation`):
177
177
  | Tenant logs | `Logs_<Tenant>` | Per-client activity/audit logs. |
178
178
  | Framework logs | `Logs` | Generic 2.0 logs not tied to any client. |
179
179
 
180
- **Resolving a tenant string from a client name:** query `Core.Clients`. The
181
- `clientIdentifier` field **is** the exact suffix used in `Client_`, `Archive_`, and
182
- `Logs_` database names; the `name` field is the human-readable label. Look up by either
183
- to find the other:
180
+ **Resolving a tenant's real database names:** query `Core.Clients`, then follow the id
181
+ columns into `Core.Databases` — **never build the name as `'Client_' . $clientIdentifier`.**
184
182
 
185
183
  ```sql
186
184
  SELECT
187
- name,
188
- clientIdentifier
189
- FROM Clients
185
+ c.name,
186
+ c.clientIdentifier,
187
+ cd.name AS clientDatabase,
188
+ ld.name AS logDatabase,
189
+ ad.name AS archiveDatabase
190
+ FROM Clients AS c
191
+ LEFT JOIN `Databases` AS cd ON cd.id = c.clientDatabaseId
192
+ LEFT JOIN `Databases` AS ld ON ld.id = c.logDatabaseId
193
+ LEFT JOIN `Databases` AS ad ON ad.id = c.archiveDatabaseId
190
194
  WHERE
191
- name LIKE '%<keyword>%'
195
+ c.name LIKE '%<keyword>%'
192
196
  ```
193
197
 
194
- A client whose `clientIdentifier` is `Towfoundation` therefore lives in
195
- `Client_Towfoundation`, `Archive_Towfoundation`, and `Logs_Towfoundation`.
198
+ `clientIdentifier` usually matches the suffix (`Towfoundation` → `Client_Towfoundation`,
199
+ `Archive_Towfoundation`, `Logs_Towfoundation`), but that is a **convention, not a rule**.
200
+ Verified in prod: `Compass_Usa` → `Client_Compass` / `Logs_Compass` / `Archive_Compass`, and
201
+ `Compass_Canada` → `Client_CompassCanada` / `Logs_CompassCanada` / `Archive_CompassCanada`.
202
+ There is no `Client_Compass_Usa`. A string-built name passes local testing and breaks in
203
+ production for exactly those clients. Precedent that does it right:
204
+ `App_Developers_MultiClientSql::coreLists()` in 1.0 `tools`. See
205
+ [per-client database connections](features/per-client-database-connections.md).
196
206
 
197
207
  #### Non-production environments
198
208
 
@@ -428,3 +438,7 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
428
438
  message/trace to the client if `display_errors` is on unconditionally. Confirm `display_errors` is
429
439
  disabled in production and that the framework's error surface returns a sanitized envelope (not a raw
430
440
  stack trace) to API consumers. Not yet done — tracked as a follow-up.
441
+
442
+ ## Change history
443
+
444
+ - 2026-10-07 — Corrected tenant DB-name resolution: resolve `Core.Clients` id columns into `Core.Databases`; `clientIdentifier` is not the suffix (Compass). (mhammontree)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-06
9
+ updated: 2026-10-07
10
10
  owners: ["dfranks", "jcardinal", "mhammontree", "apeterson", "kyalamarthi", "bala"]
11
11
  files:
12
12
  - _underscore/Database.php
@@ -59,7 +59,7 @@ When `_underscore` serves a request for a client it opens **three distinct per-c
59
59
 
60
60
  ## Gotchas
61
61
 
62
- - **`Core.Clients` has rows for clients whose DBs were never built** (prod 2026-09-24: 46 rows, 35 built). "DB exists" = a `Core.DatabaseHosts` row (`databaseId` + `environmentId` [+ `regionId`]) for the current `Environments.slug` — the same rows `registerClientDatabases()` resolves. Any fan-out over all clients must filter `EXISTS` on `DatabaseHosts` for the env, not `Core.Clients` alone; `isActive`-style flags are wrong (inactive clients still have DBs). Host-row count varies (e.g. `Archive_Nychh` has 1, others 4) — still built. Example: [Tools Multi-Client SQL](../../../../1.0/apps/tools/features/multi-client-sql.md).
62
+ - **`Core.Clients` has rows for clients whose DBs were never built** (prod 2026-09-24: 46 rows, 35 built). "DB exists" = a `Core.DatabaseHosts` row (`databaseId` + `environmentId` [+ `regionId`]) for the current `Environments.slug` — the same rows `registerClientDatabases()` resolves. Any fan-out over all clients must filter `EXISTS` on `DatabaseHosts` for the env, not `Core.Clients` alone; `isActive`-style flags are wrong (inactive clients still have DBs). Host-row count varies (e.g. `Archive_Nychh` has 1, others 4) — still built. The 11 registered-but-never-provisioned clients (prod 2026-10-07): **Agilant, Athena, Cloudcsp, Companycom, Eclinicalworks, Ims, Newegg, Officedepot, Staples, Synnex, Walmart** — each has a `Core.Databases` row naming `Client_<Name>`, but no schema. Do **not** add them to any fan-out list. Also on the prod client cluster: a stray `Client_Compass_20260831_120000` schema (looks like an Aug-31 manual backup copy) — confirm with the owner before dropping. Example: [Tools Multi-Client SQL](../../../../1.0/apps/tools/features/multi-client-sql.md).
63
63
  - **⚠ NEVER read `LAST_INSERT_ID()` (or any other per-connection session value) through `_Query`.** `_Query` defaults `_isReadHostEnabled = true` (`Query.php:8`) and routes SELECTs to `CONNECTION_READ` (`Query.php:273-276`). `LAST_INSERT_ID()` is scoped to the connection that did the INSERT, so a read-host SELECT returns **0** — silently wrong, and now an FK violation wherever the id is used as a foreign key. Use `mysqli_insert_id` on the write/transaction connection and reject `<= 0`. It is not biting today only because most client registrations leave `writeHost`/`readHost` null, and `Database.php:488` then points READ and WRITE at the **same** `mysqli` handle (so raw `mysqli_query` and `new _Query(...)` share one connection and one transaction). Configuring a real read host for any client would break every such call at once. Worked example: [Compass PEOPLE-file user lifecycle](../../../clients/compass-usa/features/people-file-user-lifecycle.md).
64
64
  - **Locks and just-committed rows: force the writer and skip the cache.** A `_Query` SELECT goes to the read host and may be served from the query cache. For `GET_LOCK`/`RELEASE_LOCK` (session-scoped — must be on the writer) or reading a row another job just committed, call `$query->setisReadHostEnabled(false)` and `_Database::useQueryCache(false)`. Worked example: NYCDOE `Sftp/Import` per-checksum lock in [2.0 ASN SFTP Ingestion](../../../../clients/nycdoe/features/asn-sftp-ingestion-2-0.md).
65
65
  - **`_Database::transactionCommit()` / `transactionRollback()` with NO database argument leave each alias's "started" flag `true`.** A later `transactionStart()` in the same PHP process then never begins a transaction — writes autocommit and a rollback undoes nothing. Prod worker jobs and api2 requests are safe (one job/request = fresh process state); it bites long-lived processes and test harnesses. Reset with `\_Database::$_transactionStarts = []` before `transactionStart()` (see `runAsWorkerJob` in [worker2 Codeception tests](../../worker2/features/codeception-integration-tests.md)).
@@ -94,6 +94,7 @@ Methodology lesson — why the wrong conclusion was reached: the model-class com
94
94
  - [Re-pointing a DB alias mid-request](./database-alias-repointing.md)
95
95
 
96
96
  ## Change history
97
+ - 2026-10-07 — Named the 11 registered-but-unprovisioned prod clients and the stray `Client_Compass_20260831_120000` backup schema. (mhammontree)
97
98
  - 2026-10-06 — Writer+no-cache reads for locks/just-committed rows; no-arg commit/rollback leaves the transaction-started flag set (mhammontree)
98
99
  - 2026-09-23 — Added the read-host routing trap: `_Query` sends SELECTs to `CONNECTION_READ`, so `LAST_INSERT_ID()` read through `_Query` can return 0; noted that null `writeHost`/`readHost` currently collapses both to one handle, and that raw `mysqli_query` needs a manual `resetQueryCache()`. (mhammontree)
99
100
  - 2026-09-24 — Added gotcha: filter client fan-outs by `DatabaseHosts` for the environment; `Core.Clients` includes never-built clients. (jcardinal)
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-07
10
10
  owners: ["mhammontree"]
11
11
  files:
12
12
  - Client/
@@ -51,6 +51,21 @@ Order matters — each step assumes the prior one ran.
51
51
  - **api2 / _underscore** — the API the client calls; verify here.
52
52
  - **Bastion** — production deploy path (upload SQL, source per cluster).
53
53
 
54
+ ## Planned onboarding tool — settled design (TRUE-79864)
55
+
56
+ Platform-level and host-independent. Decided 2026-10-07; **host moved to Desk 2.0 (`toga25-desk`)**, which already plans an "Add Client" button. The 1.0 `tools` hosting plan is dead (no `App_Devops_*` classes, no 1.0 MVC page, no persona nav entry, and `App_Developers_MultiClientSql` is a 1.0 class Desk cannot reach). Builds on the 2026-06-22 Jeff design spec (ticket TRUE-79702) at `2.0-new-client-onboarding-consolidation.md` — read that file for the spec; it is not copied here.
57
+
58
+ - **Generates SQL and guides. It never executes SQL and never writes to a repository.** Because it never writes, the "two onboardings at once" branch-lock problem does not exist.
59
+ - **Output is split by cluster:** (A) `CREATE DATABASE` Client_/Logs_/Archive_; (B) the apply order for `Client_<Id>` — newest-dated blank, then loose `Client/*.sql` in filename order, then the chosen modules' files, **file names and order only, not the SQL text**; (C) the `Logs_Client/` blank for `Logs_<Id>`; (D) Core registration inserts (secret-free, committable); (E) `Apis` + `Apis_Roles` (secrets, shown once, never committed).
60
+ - **`Logs_<Name>` is IN scope** even though the 2026-06-22 spec deferred it: the API logs there and checks the auth rate limit against it, so deferring it leaves a client that registers fine and then cannot authenticate.
61
+ - **Branch is picked from a drop-down**, excluding the repo default branch (`_main`) and any protected branch — onboarding always runs against a ticket branch. Read the default branch name from the API; never hardcode it.
62
+ - **Modules: selection only** (spec step 5 — read `_modules/`, tick what the client uses, fold its files into the apply order, write the client's `_modules.txt` line). Module **consolidation** (spec step 4) is OUT; it is a write. **Blank consolidation is also OUT** — `Client/HISTORIC` shows ~15 rewrites over ~2 years, so it is periodic maintenance on its own clock, not part of onboarding.
63
+ - **The `Apis` secret is generated in the browser** with `crypto.getRandomValues` and the INSERT is built client-side, so plaintext never reaches the server or a log. (`crypto.randomUUID` and `crypto.subtle` are undefined on a non-secure origin; `getRandomValues` is not.)
64
+ - **Run history: two new prod `Core` tables** — `ClientOnboardings` (client, branch, commit SHA, blank filename, loose-file count, ticket ref, who, status, email domains, modules, the ordered file list as TEXT) and `ClientOnboardingEnvironments` (FK to `Core.Environments.id`, isApplied, dtApplied, appliedByEmail). `Core.Environments` exists in prod with 27 rows (production = id 1). Two constraints: **no FK to the user** (`Client_True` is on the client cluster, `Core` on the core cluster), and **never store the `Apis` secret**.
65
+ - **The internal API-key row is named `TOGA Technology`, not `Agilant`** — the old company name is not used for new work. The existing generator still emits `Agilant`.
66
+ - **The one real correctness risk is a stale local checkout.** If the developer's checkout is ahead of the branch the tool reads, the tool lists *fewer* files than exist and the new client silently misses migrations, with no error. Mitigation: print the branch, the commit SHA, the file count, each file's blob SHA, and one verify command.
67
+ - **Open question:** Desk is React talking to api2, so the GitHub read must move server-side into api2 or worker2 — a long read inside a web request hits a timeout problem.
68
+
54
69
  ## Gotchas
55
70
 
56
71
  - **🚨 The blank can be WRONG, not just behind — an open example (2026-08-28).** `Client/2026-06-03- BLANK_CLIENT_DATABASE.sql` creates `TransferOrderItems` (`CREATE` at ~line 9462) **without `dtCreated`/`dtUpdated`**, but `_Model_Client_TransferOrderItem` declares them as `FIELD_DATETIME_CREATED`/`FIELD_DATETIME_UPDATED`, so `_Model` puts them in **every SELECT it builds**. Every tenant provisioned from this blank has a table the model cannot read — nothing in a request has to ask for the columns. `Client/2026-08-28b - TransferOrderItemsTimestamps.sql` repairs existing tenants; **the blank itself is still unfixed**, so the next onboarding reproduces it. When a model-vs-blank mismatch is found, fix **both**: a catch-up `Client/` file for live tenants *and* the blank.
@@ -69,3 +84,7 @@ Order matters — each step assumes the prior one ran.
69
84
  - [TOGa 2.0 client onboarding SQL generator](../../../1.0/apps/test/features/toga2-client-onboarding-sql.md)
70
85
  - [TOGa 2.0 Client Onboarding Wizard](../../../1.0/apps/test/features/toga2-onboarding-wizard.md)
71
86
  - [Tickets API](../../api2/features/tickets-api.md)
87
+
88
+ ## Change history
89
+
90
+ - 2026-10-07 — Recorded the settled design for the onboarding tool (TRUE-79864) and the decision to host it in Desk 2.0, not 1.0 tools. (mhammontree)
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-10-06
8
+ updated: 2026-10-07
9
9
  owners: [jcardinal, mhammontree, dfranks, bala, ajean]
10
10
  files: []
11
11
  related:
@@ -666,6 +666,9 @@ can follow. Do not type the child alone.
666
666
 
667
667
  * Use comments to explain the "why" behind the code, not the "how".
668
668
  * Inline comments should be used sparingly.
669
+ * Keep a file docblock short: what the file is, plus the one or two traps that bite the next
670
+ editor. Design history, scope decisions, reuse notes and rejected approaches go to the
671
+ knowledge base at `/capture`, not into the file — they bury the code and go stale.
669
672
 
670
673
  #### **DocBlocks**
671
674
 
@@ -1023,3 +1026,7 @@ Existing static-key paths (ZoomInfo, VipSupport, `_Cloud` CLI) are grandfathered
1023
1026
  is deferred, not sanctioned for new work. Never record access-key values anywhere but the ini.
1024
1027
 
1025
1028
  See: 2.0/apps/worker2/features/cross-account-aws-access.md
1029
+
1030
+ ## Change history
1031
+
1032
+ - 2026-10-07 — Added the short-file-docblock rule (design history belongs in the knowledge base). (mhammontree)
@@ -4,7 +4,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
4
4
 
5
5
  ## 1.0 framework
6
6
 
7
- - **library** (Library) _(framework core)_ — 32 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
7
+ - **library** (Library) _(framework core)_ — 33 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
8
  - **worker** (Worker) — 42 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
9
  - **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
10
10
  - **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
@@ -18,7 +18,7 @@ project: _Underscore
18
18
  client: compass-canada
19
19
  type: profile
20
20
  status: active
21
- updated: 2026-10-05
21
+ updated: 2026-10-07
22
22
  owners: [jcardinal, bala, tcox, apeterson, ajean, mhammontree, rgirish]
23
23
  files: []
24
24
  related:
@@ -43,6 +43,7 @@ related:
43
43
  - ../compass-usa/features/saml-sso.md
44
44
  - ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
45
45
  - ../../2.0/apps/_underscore/features/surface-resolver.md
46
+ - ../../1.0/apps/library/features/email-ses-sender-identity.md
46
47
  ---
47
48
 
48
49
  ## Summary
@@ -81,6 +82,19 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
81
82
  CXML). Prod `Vendors` 4 COMPASS CANADA and 5 AGILANT SOLUTIONS INC have no integration, and the 1.0
82
83
  constants for them (`'2'`, `'3'`) do not match prod. PO transmit cron quirks and the OneUptime monitor
83
84
  (cron 87): [PO-to-vendor monitor](features/oneuptime-po-transmissions-to-vendors-monitor.md).
85
+ - **TechHub sender address is `Techhub.Canada@compass-canada.com` (prod, 2026-10-07).** Was
86
+ `TechHubCanada@togatech.com`. Changed on 28 `EmailTemplates.sendFromEmailAddress` rows (EN+FR
87
+ order / approval / transit / delivered / reminder / auto-cancel) and `IntegrationsEmail` id 1
88
+ (integration 2, "Grand & Toy - Email") by
89
+ `dbchanges2/Client_CompassCanada/2026-10-07a - TechHubCanadaFromEmail.sql`. Kept on togatech.com
90
+ on purpose: Request Submitted (`compasssupport@`), Get Support (`donotreply@`), Forgot Password x2
91
+ (`noreply@`). It is a single verified SES identity, so any new sender needs SES verification
92
+ first: [SES sender identity](../../1.0/apps/library/features/email-ses-sender-identity.md).
93
+ The hardcoded TechHub CC in 4 worker crons had a typo (`TechHub@compass-canada.com`, missing
94
+ `.Canada`); now `Techhub.Canada@compass-canada.com` in `workflow/2_transmit_mits_purchase_orders_to_vendors.php`
95
+ (G&T PO email CC), `workflow_beta/2_…`, `compass_email_reminders.php` and
96
+ `compass_cancel_pending_approval_orders.php` (all under `worker/crons/toga2/compasscanada/`).
97
+ **Not yet deployed.**
84
98
  - **Grand & Toy (G&T)** — primary hardware vendor. SOs flow toga → MITS → PO to G&T; G&T sends
85
99
  back ASNs. ASN ingestion (email CSV + the auto-created ItemFulfillment chain + bilingual
86
100
  in-transit email) is documented in [Grand & Toy ASN Import](features/grand-and-toy-asn-import.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.966",
3
+ "version": "1.0.968",
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",