toga-ai 1.0.550 → 1.0.552

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.
@@ -226,6 +226,16 @@ metrics**, not the database.
226
226
  ## Gotchas
227
227
 
228
228
  - Class must be **`abstract`** and methods **`public static`** or routing fails.
229
+ - **A JSON-object parameter arrives as `stdClass`, not `array` — type it `object|array` and
230
+ cast.** The dispatcher decodes `Core.CronJobs.parameters` and casts **only the top level** to
231
+ an array (`$parameters = (array) $message->parameters;` in `Controller/Index.php`, ~L455/692),
232
+ then spreads it as named args. So a **nested JSON object** value stays a PHP `stdClass`; a
233
+ method that declares that parameter as strict `array` throws a `TypeError`
234
+ (*"must be of type array, stdClass given"*) at dispatch. Declare such a parameter as
235
+ `object|array` and `(array)`-cast it at the top of the method. (Nested JSON **arrays** decode
236
+ to PHP arrays fine — only JSON **objects** become `stdClass`.) This bit
237
+ `_Worker_Infrastructure_CloudWatch::ElasticBeanstalkHealth`'s `environments` region-map in
238
+ production — see [cross-account-aws-access.md](./cross-account-aws-access.md).
229
239
  - Webhook headers are **lowercased** by API Gateway (`X-GitHub-Event` → `x-github-event`).
230
240
  - If the action needs a client DB, register it in `initialize()` — it runs before the method.
231
241
  - **Worker actions do NOT auto-register `DB_CLIENT`.** The dispatcher registers only
@@ -259,6 +269,12 @@ metrics**, not the database.
259
269
  commit-before-SQS transaction pattern that the worker relies on.
260
270
 
261
271
  ## Change history
272
+ - 2026-08-11 — Added the **nested-JSON-object → `stdClass` dispatch gotcha**: the dispatcher casts
273
+ only the top level of `CronJobs.parameters` to an array before spreading as named args
274
+ (`Controller/Index.php` ~L455/692), so a nested JSON object stays `stdClass` and a strict
275
+ `array` param throws a `TypeError` at dispatch — type such params `object|array` and
276
+ `(array)`-cast. Surfaced fixing `ElasticBeanstalkHealth`'s `environments` region-map in
277
+ production. (jcardinal)
262
278
  - 2026-08-10 — Documented that **`debug_mode` changes `runTask`'s dispatch** (1 = synchronous
263
279
  `_ApiRequest` POST to `_Config::api('worker')`, running the action inside the caller's open
264
280
  transaction so any read-back fails; 0 = SQS enqueue), plus the debug-mode side effects
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-10
9
+ updated: 2026-08-11
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - worker2/Component/Aws/Workloads/Workloads.php
@@ -88,20 +88,49 @@ it**.
88
88
 
89
89
  ## First consumer — ElasticBeanstalkHealth (worked example)
90
90
 
91
- `_Worker_Infrastructure_CloudWatch::ElasticBeanstalkHealth(awsAccountId,
92
- elasticBeanstalkEnvironmentNames, oneuptimeUrl, region)` is a **parameterized cron** (args
93
- spread from `Core.CronJobs.parameters` as named args) that reads each EB environment's
94
- overall health through `_Component_Aws_Workloads::client(...)`, maps `HealthStatus`
95
- `Degraded`/`Severe` → `alarm:"HIGH"`, and pushes a **non-fatal** OneUptime heartbeat
96
- (`setLogging(false)`, `setThrowExceptionsOnFailure(false)` — the Compass push pattern; see
91
+ `_Worker_Infrastructure_CloudWatch::ElasticBeanstalkHealth(string $awsAccountId,
92
+ array $environments, string $oneuptimeUrl)` is a **parameterized cron** (args spread from
93
+ `Core.CronJobs.parameters` as named args) that reads each EB environment's overall health
94
+ through `_Component_Aws_Workloads`, maps `HealthStatus` `Degraded`/`Severe` →
95
+ `alarm:"HIGH"`, and pushes a **non-fatal** OneUptime heartbeat (`setLogging(false)`,
96
+ `setThrowExceptionsOnFailure(false)` — the Compass push pattern; see
97
97
  [OneUptime worker2 monitoring](./oneuptime-worker2-monitoring.md)).
98
98
 
99
- It was refactored this session to use the new helper: the prior inline STS/credential code
100
- and the `HEALTH_ASSUME_ROLE_NAME` / `HEALTH_ASSUME_ROLE_SESSION` constants were removed in
101
- favor of `_Component_Aws_Workloads` + the `[cloud]` config keys. The prior
102
- `CheckBeanstalks()` action was renamed `ElasticBeanstalkInstances()`
103
- (dbchanges2 `Core/2026-08-10c` updates the CronJobs action name; `Core/2026-08-10d` seeds
104
- the new parameterized `ElasticBeanstalkHealth` cron row, `isActive=0`).
99
+ **One monitor spans multiple regions in one account.** `$environments` is a **map of
100
+ region environment names** (each value an array or a comma-separated string), so a single
101
+ monitor can watch EB environments across different regions of the same AWS account. The old
102
+ flat `elasticBeanstalkEnvironmentNames` list plus a single scalar `region` argument (and the
103
+ `HEALTH_DEFAULT_REGION` constant) are removed with a scalar region every environment in a
104
+ monitor had to share a region. The cron `parameters` JSON therefore replaces the
105
+ `elasticBeanstalkEnvironmentNames` + `region` keys with a single `environments` object
106
+ (parameters are spread as **named** args, so the JSON key must match the parameter name):
107
+
108
+ ```json
109
+ {
110
+ "awsAccountId": "654654170868",
111
+ "environments": {
112
+ "us-east-1": ["env-a"],
113
+ "us-west-2": ["env-b"],
114
+ "eu-west-1": ["env-c"]
115
+ },
116
+ "oneuptimeUrl": "<push URL — credential, not stored here>"
117
+ }
118
+ ```
119
+
120
+ **Credential-reuse pattern (assume once, reuse across regions):** the method calls
121
+ `_Component_Aws_Workloads::assumeCredentials($awsAccountId)` **once** (STS creds are
122
+ region-agnostic) and then constructs **one `ElasticBeanstalkClient` per region reusing those
123
+ same credentials** — rather than calling the per-region `client()` helper repeatedly. Prefer
124
+ this shape whenever one cron touches several regions of a single account: one assume-role
125
+ round-trip, N clients. The OneUptime payload now carries a per-environment `region` field
126
+ plus a top-level `regions[]` array, and the WorkerJobs summary uses `name@region=status`.
127
+
128
+ The helper adoption was captured earlier: the prior inline STS/credential code and the
129
+ `HEALTH_ASSUME_ROLE_NAME` / `HEALTH_ASSUME_ROLE_SESSION` constants were removed in favor of
130
+ `_Component_Aws_Workloads` + the `[cloud]` config keys. The prior `CheckBeanstalks()` action
131
+ was renamed `ElasticBeanstalkInstances()` (dbchanges2 `Core/2026-08-10c` updates the CronJobs
132
+ action name; `Core/2026-08-10d` seeds the parameterized `ElasticBeanstalkHealth` cron row,
133
+ `isActive=0`, and now carries the `environments`-map sample parameters).
105
134
 
106
135
  ## Legacy static key — being retired
107
136
 
@@ -124,8 +153,25 @@ their only home.
124
153
  - Config keys are optional by design (in-code defaults), so a missing `[cloud]`
125
154
  `workloads_role_*` key silently uses `WorkloadsRuntime` / `worker2-workloads` — set them
126
155
  explicitly in prod ini to override.
156
+ - **`$environments` must be typed `object|array`, not `array`.** The cron dispatcher casts only
157
+ the top level of `CronJobs.parameters` to an array, so this nested JSON **object** arrives as a
158
+ PHP `stdClass` and a strict `array` type throws a `TypeError` at dispatch. Declare it
159
+ `object|array` and `(array)`-cast at the top. General rule for any object-valued cron param —
160
+ see [creating-worker-actions.md](./creating-worker-actions.md).
127
161
 
128
162
  ## Change history
163
+ - 2026-08-11 — Noted that `ElasticBeanstalkHealth`'s `$environments` must be typed `object|array`
164
+ (not `array`): the nested JSON object arrives as `stdClass` because the dispatcher casts only
165
+ the top level of `CronJobs.parameters`, so a strict `array` throws a `TypeError` at dispatch.
166
+ Cross-referenced the general rule in creating-worker-actions.md. (jcardinal)
167
+ - 2026-08-11 — `ElasticBeanstalkHealth` signature changed to
168
+ `(string $awsAccountId, array $environments, string $oneuptimeUrl)`: `$environments` is a
169
+ region → env-names map, so one monitor can watch EB environments across multiple regions
170
+ of one account. Removed the flat `elasticBeanstalkEnvironmentNames` list, the scalar
171
+ `region` arg, and the `HEALTH_DEFAULT_REGION` constant; cron `parameters` JSON now uses a
172
+ single `environments` object. Recorded the assume-once/reuse-credentials-across-regions
173
+ pattern (one `assumeCredentials()`, one `ElasticBeanstalkClient` per region) and the new
174
+ per-environment `region` + top-level `regions[]` payload fields. (jcardinal)
129
175
  - 2026-08-10 — Created. Added `_Component_Aws_Workloads` as the standard cross-account AWS
130
176
  access helper (instance-role → per-account `WorkloadsRuntime` STS assume-role), documented
131
177
  the three-account role model and `[cloud]` config keys, and recorded that the legacy static
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.550",
3
+ "version": "1.0.552",
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",