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.
- package/knowledge/1.0/apps/library/INDEX.md +1 -0
- package/knowledge/1.0/apps/library/features/email-ses-sender-identity.md +42 -0
- package/knowledge/1.0/apps/test/features/2-0-deployment-client-sql.md +10 -2
- package/knowledge/1.0/apps/tools/features/multi-client-sql.md +15 -2
- package/knowledge/1.0/apps/tools/features/saml-sso-auth.md +4 -1
- package/knowledge/2.0/apps/_underscore/architecture.md +25 -11
- package/knowledge/2.0/apps/_underscore/features/per-client-database-connections.md +3 -2
- package/knowledge/2.0/apps/dbchanges2/workflows/client-onboarding.md +20 -1
- package/knowledge/2.0/standards/backend-php.md +8 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +15 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
181
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
`
|
|
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-
|
|
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-
|
|
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-
|
|
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)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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)_ —
|
|
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-
|
|
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