toga-ai 1.0.603 → 1.0.604

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.
@@ -7,7 +7,7 @@ client: shared
7
7
  type: feature
8
8
  status: active
9
9
  updated: 2026-08-03
10
- owners: ["jcardinal", "bala", "mhammontree"]
10
+ owners: ["jcardinal", "bala", "mhammontree", "apeterson"]
11
11
  files:
12
12
  - _underscore/Model/Client/EmailTemplate.php
13
13
  - _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php
@@ -98,6 +98,16 @@ can keep using `sendEmail($api, ...)`.
98
98
  2026-06-15 the only entry point was `sendEmail(&$api, ...)`, so callers with no API
99
99
  context faked one: `$api = (object)['client' => (object)['clientIdentifier' => …]]`.
100
100
  That hack is obsolete — pass the identifier to `send()` instead.
101
+ - **⚠ Template vars MUST be spread into the variadic, never passed as a bare array.** Both
102
+ `sendEmail(&$api, $uuid, $to, $cc, $bcc, ...$args)` and `send(...)` collect the template
103
+ variables via `...$args`. Pass them **spread** so PHP 8.1+ named-argument collection yields
104
+ string keys/string values (`sendEmail($api, $uuid, $to, [], [], ...['userName'=>$name, ...])`
105
+ → `$args = ['userName'=>$name, ...]`). Passing the map as a single positional argument
106
+ (`sendEmail($api, $uuid, $to, [], [], $vars)`) makes `$args = [0 => [...]]` — a numeric key
107
+ whose value is an *array*. `replaceTemplateVariables()` then runs `str_replace('{0}', <array>, …)`
108
+ → `TypeError: str_replace(): Argument #2 must be of type string, array given`, surfacing as a
109
+ production 500 / **EO-1**. `Model/Compass/*` gets this right (spread); the four `Model/Quad/*`
110
+ call sites were copied from Compass but dropped the `...` — fixed 2026-08-18.
101
111
  - **`sendEmail()`'s signature is load-bearing for scripted APIs** — the Record Script engine
102
112
  (`api2/Component/Api/V2/V2.php`, ~line 3594) calls the method with `api` as a named
103
113
  argument, so the first param must stay `&$api`. Do not "clean it up" by removing it.
@@ -151,6 +161,12 @@ worker method) in-process instead.
151
161
 
152
162
  ## Change history
153
163
 
164
+ - 2026-08-18 — Fixed the Quad order-approval/rejection emails' production 500
165
+ (`str_replace(): Argument #2 must be string`, EO-1): the four `Model/Quad/ApprovalDecision.php`
166
+ + `Model/Quad/SalesOrder.php` call sites passed the template-vars map as a bare positional
167
+ array, so `...$args` collected `[0 => [...]]` and `replaceTemplateVariables()` fed an array to
168
+ `str_replace`. Fix = spread the array (`...[...]`) to match the working `Model/Compass/*`
169
+ pattern. Added the **"template vars must be spread, never a bare array"** gotcha. (apeterson)
154
170
  - 2026-08-03 — Refined the `CharSet` gotcha: the **transmit** side doesn't set it either, so the
155
171
  effective wire charset is **iso-8859-1**, and a body that needs accents should escape with
156
172
  `htmlentities($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', false)` (details on
@@ -6,7 +6,7 @@
6
6
  | [API Payload Interceptors (metadata-registered prePost/postPut model hooks)](features/api-payload-interceptors.md) | A `_Model`'s `prePost()` / `postPost()` / `prePut()` / `postPut()` hooks are **not** called by the model. | api2/Component/Api/V2/V2.php, api2/Controller/Index.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Compass/PurchaseOrder.php, _underscore/Model/Elite/SalesOrder.php, _underscore/Model/Elite/ServiceRequest.php, _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/SalesOrderItem.php, dbchanges2/Core/2026-06-30a - ItemFulfillmentStageDefaultInterceptor.sql, dbchanges2/Core/2026-08-06a - Service request and sales order payload interceptors.sql, dbchanges2/Client_Compass/2026-08-06 - RemoveBrokenSalesOrderItemPostPostInterceptor.sql, dbchanges2/Client_Compass/2026-08-14a - AddSalesOrderInactiveStandaloneItemPreInterceptors.sql, dbchanges2/Client_CompassCanada/2026-08-14a - AddSalesOrderInactiveStandaloneItemPreInterceptors.sql |
7
7
  | [Multi-Client (Cross-Client) Data Retrieval](features/cross-client-data-retrieval.md) | A single authenticated V2 GET listing can return records across **many** clients (designed for 1000+) that the caller is entitled to, honoring **each target cli | api2/Component/Api/CrossClient/CrossClient.php, api2/Component/Api/V2/V2.php, api2/Controller/Index.php, _underscore/Model/Cache/Table.php, _underscore/Model/Cache/Tables/Client.php, _underscore/Model/Core/Record.php, worker2/Worker/Platform/Cache.php, worker2/Controller/Index.php, worker2/_.php, dbchanges2/Cache/2026-06-30a - MultiClientCacheTables.sql, dbchanges2/Core/2026-06-30b - CacheClusterRegistrationAndRecordTtl.sql, dbchanges2/Core/2026-07-27a - PlatformCacheCleanCron.sql |
8
8
  | [Encrypted-User-UUID Auth Handoff (/auth/encrypted-user-uuid)](features/encrypted-user-uuid-auth-handoff.md) | `POST /auth/encrypted-user-uuid` is the intended **cross-client / SSO-handoff identity mechanism**: given an encrypted `{client, user}` UUID pair, it mints a fr | api2/Component/Api/CrossClient/CrossClient.php |
9
- | [ENVIRONMENT (not the EB environment name) decides the _underscore branch and Config file](features/environment-variable-drives-underscore-branch.md) | An api2 Elastic Beanstalk instance decides **which `_underscore` branch it clones** and **which `Config/<env>.ini` it loads** from the EB environment property * | api2/.ebextensions/git.php, api2/.ebextensions/php_include_underscore.config, api2/.ebextensions/git.sandbox-dev.json, api2/Config/beta.ini, api2/Config/sandbox-dev.ini, api2/Component/Api/V2/V2.php |
9
+ | [ENVIRONMENT (not the EB environment name) decides the _underscore branch and Config file](features/environment-variable-drives-underscore-branch.md) | An api2 Elastic Beanstalk instance decides **which `_underscore` branch it clones** and **which `Config/<env>.ini` it loads** from the EB environment property * | api2/.ebextensions/git.php, api2/.ebextensions/php_include_underscore.config, api2/.ebextensions/git.sandbox-dev.json, api2/.ebextensions/git.sandbox-client.json, api2/Config/beta.ini, api2/Config/sandbox-dev.ini, api2/Component/Api/V2/V2.php |
10
10
  | [Health-check endpoint (/health liveness short-circuit)](features/health-check-endpoint.md) | `_Controller_Index::api()` short-circuits **liveness/health-probe** requests to an HTTP 200 **before** any routing, DB bootstrap, or V2 engine work runs. | api2/Controller/Index.php |
11
11
  | [Language Translation Layer (audience.language + sidecar tables)](features/language-translation-layer.md) | Serves the same TOGa data (Item title/description/longDescription, plus item **feature** text — `Features.name`, `ItemCategoryFeatureGroups.name`, `ItemFeatures | api2/Component/Api/V2/V2.php, api2/Component/Api/V2/Response/Response.php, _underscore/Model/Core/Setting.php, _underscore/Model/Core/RecordField.php, _underscore/Model/Core/DefaultGlobalSetting.php, _underscore/Model/Client/ItemTranslation.php, _underscore/Model/Client/FeatureTranslation.php, _underscore/Model/Client/ItemCategoryFeatureGroupTranslation.php, _underscore/Model/Client/ItemFeatureTranslation.php, dbchanges2/Client/2026-06-23a - ItemTranslations.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-13a - FeatureTranslations.sql, dbchanges2/Client/2026-07-13b - FeatureTranslationsAcl.sql, dbchanges2/Core/2026-06-23a - RecordFieldsTranslationColumn.sql, dbchanges2/Core/2026-06-23b - ItemTranslationsRecord.sql, dbchanges2/Core/2026-07-13 - FeatureTranslationsRecord.sql |
12
12
  | [/auth/login resolves the client from the email domain, not the Bearer token (cross-client user path)](features/login-cross-client-user-resolution.md) | `POST /v2/auth/login` (email/password user login) can silently swap the target client mid-request. | api2/Component/Api/V2/V2.php, _underscore/String.php |
@@ -7,11 +7,12 @@ client: shared
7
7
  type: feature
8
8
  status: active
9
9
  updated: 2026-08-06
10
- owners: ["bala", "mhammontree"]
10
+ owners: ["bala", "mhammontree", "apeterson"]
11
11
  files:
12
12
  - api2/.ebextensions/git.php
13
13
  - api2/.ebextensions/php_include_underscore.config
14
14
  - api2/.ebextensions/git.sandbox-dev.json
15
+ - api2/.ebextensions/git.sandbox-client.json
15
16
  - api2/Config/beta.ini
16
17
  - api2/Config/sandbox-dev.ini
17
18
  - api2/Component/Api/V2/V2.php
@@ -116,12 +117,36 @@ one.
116
117
  `_underscore`. (Contrast: the 1.0 **tools** app works cleanly because its helper *and* its config
117
118
  live in the same repo, and it clones `library` from a **fixed** branch, `_production`, rather
118
119
  than an `ENVIRONMENT`-derived one.)
120
+ - **⚠ A `git.<env>.json` `"branch"` key can pin a tier to the WRONG `_underscore` branch, silently.**
121
+ When a code fix pushed to a branch does not appear after an api2 deploy, before debugging the
122
+ code verify which branch that EB environment's `.ebextensions/git.<env>.json` actually clones:
123
+ a present `"branch"` value overrides the `'_' . strtolower(ENVIRONMENT)` default. **Concrete
124
+ (fixed 2026-08-18):** `git.sandbox-client.json` had `"branch": "_production"`, so the
125
+ `sandbox-client` environment ran `_underscore` from `_production` — never `_sandbox-client` —
126
+ which is why fixes merged to `_sandbox-client` never showed up there. Confirmed two ways: the
127
+ config file value, and live runtime behavior matching `_production` (unfixed) rather than
128
+ `_sandbox-client` (fixed). Fix = remove the `"branch"` key so it falls back to `_sandbox-client`.
129
+ - **Deploy path is build-time, so a git push alone does not update running instances.** The
130
+ `_underscore` clone happens in the prebuild hook at build time: commit the fix on the feature
131
+ branch, merge that branch into the deploy-channel branch (`_` + `ENVIRONMENT`, e.g.
132
+ `_sandbox-client`), push, **then redeploy the EB environment** to re-run the clone.
133
+ - **⚠ SECURITY — every `.ebextensions/git.*.json` carries a plaintext GitHub PAT** committed in
134
+ the repo (used to clone `_underscore`). Pre-existing leak; treat the committed tokens as
135
+ compromised, rotate, and move them to EB environment properties / SSM. Do **not** copy the token
136
+ value anywhere — this note records only that the credential lives in these files.
119
137
  - **Duplicate integration code is the trap this creates.** When the same capability is needed in
120
138
  both tiers it must exist twice (once per framework) — keep the credential/config location
121
139
  documented in both places so a rotation updates both.
122
140
 
123
141
  ## Change history
124
142
 
143
+ - 2026-08-18 — Recorded a second concrete `"branch"`-pin trap: `git.sandbox-client.json` pinned
144
+ `"branch": "_production"`, so the `sandbox-client` EB environment cloned `_underscore` from
145
+ `_production` and never received `_sandbox-client` merges (confirmed via both the config value and
146
+ live runtime matching the unfixed `_production` code); fix = drop the `"branch"` key. Added the
147
+ general diagnostic ("fix not appearing after deploy → check `git.<env>.json` branch pin first"),
148
+ the build-time deploy note (merge into `_`+ENVIRONMENT then redeploy — a push alone is inert),
149
+ and the ⚠ plaintext-GitHub-PAT-in-`git.*.json` security gotcha (location only). (apeterson)
125
150
  - 2026-08-06 — TRUE-80282 deploy: independently re-confirmed the mapping from a second tier
126
151
  (`api.beta.togahub.com` = `ENVIRONMENT=beta` → `Config/beta.ini` → branch `_beta`) at a cost of two
127
152
  deploy cycles, and recorded **why** the branch derives: there is deliberately **no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.603",
3
+ "version": "1.0.604",
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",