toga-ai 1.0.646 → 1.0.647

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.
@@ -132,6 +132,16 @@ can keep using `sendEmail($api, ...)`.
132
132
  predated the commit. That is a **deploy gap, not a code bug** — a redeploy fixes it, and no code
133
133
  change should be made. Same known-issue as
134
134
  [api2 environment-variable-drives-underscore-branch](../../api2/features/environment-variable-drives-underscore-branch.md).
135
+ - **⚠ An explicit `null` recipient is a 400, not a 500 (fixed 2026-08-25).** `$to`/`$cc`/`$bcc` on
136
+ `sendEmail()`/`send()`/`dispatch()` are typed `string|array|null` (a scripted-API caller can pass an
137
+ argument that is present-but-`null` — the `[]` default only applies when the arg is **omitted**).
138
+ Before the fix they were non-nullable `string|array`, so an explicit `null` `$to` threw
139
+ `Argument #3 ($to) must be of type array|string, null given` → **HTTP 500** — this was the platform's
140
+ **largest 5xx contributor** (prod `Logs.Issue` ref `2F`, 828 occurrences). `dispatch()` now normalizes
141
+ each recipient at the top (`(array)($x ?? [])`); after merging the template's stored
142
+ `EmailTemplateOutgoingEmailAddress` rows, if `$to` is **still empty** it throws `_Exception_Validation`
143
+ (→ **HTTP 400**) rather than a TypeError-500 or sending a recipientless email. A missing recipient is
144
+ client input error, not a server fault.
135
145
  - **`sendEmail()`'s signature is load-bearing for scripted APIs** — the Record Script engine
136
146
  (`api2/Component/Api/V2/V2.php`, ~line 3594) calls the method with `api` as a named
137
147
  argument, so the first param must stay `&$api`. Do not "clean it up" by removing it.
@@ -198,6 +208,13 @@ worker method) in-process instead.
198
208
  restored it — and that `sendEmail` has been variadic since 2024-12-24, so the spread was always
199
209
  correct and May was the regression. Documented the contract on `sendEmail()`'s docblock plus a marker
200
210
  comment at each of the four Quad call sites. (apeterson)
211
+ - 2026-08-25 — Made `$to`/`$cc`/`$bcc` nullable (`string|array|null`) on `sendEmail()`/`send()`/
212
+ `dispatch()` and normalized each to an array at the top of `dispatch()` (`(array)($x ?? [])`),
213
+ fixing the platform's **largest 5xx contributor** (prod `Logs.Issue` ref `2F`, 828 occurrences): a
214
+ scripted-API caller passing an explicit `null` recipient hit `Argument #3 ($to) must be of type
215
+ array|string, null given` → HTTP 500. After merging the template's stored recipient addresses, an
216
+ empty `$to` now throws `_Exception_Validation` (HTTP 400) instead of a TypeError-500 or a
217
+ recipientless send. Added the corresponding gotcha. (jcardinal)
201
218
  - 2026-08-18 — Fixed the Quad order-approval/rejection emails' production 500
202
219
  (`str_replace(): Argument #2 must be string`, EO-1): the four `Model/Quad/ApprovalDecision.php`
203
220
  + `Model/Quad/SalesOrder.php` call sites passed the template-vars map as a bare positional
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-08-20
9
+ updated: 2026-08-25
10
10
  owners: [jcardinal, bala, mhammontree, dfranks]
11
11
  files:
12
12
  - api2/Controller/Index.php
@@ -114,14 +114,22 @@ One ~2,000-line `execute()` then `processRoutePairs()`:
114
114
  5. **Transaction logging** — every request logged (to client/core Logs DB, or as a JSONL
115
115
  line shipped by CloudWatch when `[api] log_filepath` is set).
116
116
 
117
- > **⚠ Auto-generated `Api.transactionId` collides under concurrency → 1062 → HTTP 500.**
117
+ > **Auto-generated `Api.transactionId` used to collide under concurrency → 1062 → HTTP 500 (FIXED 2026-08-25).**
118
118
  > Separate from the *client-supplied* `transactionId` uniqueness check (EV-5, above): the inbound
119
- > request-logger inserts its `Api` log row with a UNIQUE `transactionId` set to a
120
- > millisecond-precision timestamp (`Y-m-d H:i:s.v`). Concurrent nested writes generated within the
121
- > same millisecond collide on that UNIQUE key → MySQL **1062** → **HTTP 500**. This breaks ingestion
122
- > for high-volume senders (seen on the Compass/Veyer ASN feed, `sourceIp 34.232.23.158`) and is
123
- > platform-wide. Fix direction: make the logged `transactionId` unique-enough (uuid) or
124
- > retry-on-1062. Until fixed, a burst of concurrent posts can intermittently 500 with no app-level cause.
119
+ > request-logger inserted its `Api` log row with a UNIQUE `transactionId` (`Logs.Api.transactionId`,
120
+ > `varchar(255) UNIQUE`, in both Core `Logs` and `Logs_<Client>`) set to a millisecond-precision
121
+ > timestamp (`Y-m-d H:i:s.v`). Concurrent inserts in the same millisecond collided on that UNIQUE key
122
+ > → MySQL **1062** → the logger threw and turned a *successful* request into **HTTP 500 (EO-1)**. This
123
+ > broke ingestion for high-volume senders (seen on the Compass/Veyer ASN feed, `sourceIp 34.232.23.158`;
124
+ > confirmed in prod `Logs.Issue` reference `1Z`, 304 occurrences, `Duplicate entry
125
+ > '<ms-timestamp>' for key 'Api.transactionId'`, stack `/v2/users/me → _Model_Client_User::me() →
126
+ > internalApiRequest() → _Model->save()`).
127
+ > **Fix:** the two DB-insert log sites now set the log-row `transactionId` to a random uuid
128
+ > (`_String::generateUuid()`) so it cannot collide — `V2.php` `internalApiRequest()` logger (~L2370)
129
+ > and `Controller/Index.php` uncaught-`Throwable` error-recovery logger (~L456). The request's own base
130
+ > `transactionId` was already a uuid when auto-generated (`V2.php` ~L2130); only the bare-ms log-row id
131
+ > was at fault. The **sibling CloudWatch JSONL file-append logger** (`Controller/Index.php` ~L433) is
132
+ > **intentionally left bare-ms** — a JSONL append has no UNIQUE key and cannot collide.
125
133
 
126
134
  ## CRUD engine — `processRoutePairs()`
127
135
 
@@ -216,6 +224,34 @@ IMDSv2 token on the `curl` command line (visible in `ps`/`/proc/<pid>/cmdline`,
216
224
  back silently to IMDSv1 — fix both alongside the key. *(Location + remediation only; no key material
217
225
  is recorded anywhere.)*
218
226
 
227
+ ## EB "Degraded" health alerts vs. the ALB's own view (monitoring — read before triaging)
228
+
229
+ An Elastic Beanstalk enhanced-health transition — *"Environment health transitioned Ok→Degraded …
230
+ One or more TargetGroups … in a reduced health state: awseb-AWSEB-<id> - Degraded"* — **does NOT
231
+ mean the ALB marked a target unhealthy.** On `api-production-1` (AWS account `654654170868`), during
232
+ such an event the ALB's own view was **fully healthy throughout**: `HealthyHostCount=2`,
233
+ `UnHealthyHostCount=0`, ELB `5xx=0`, `TargetConnectionErrorCount=0`.
234
+
235
+ The two subsystems measure different things and legitimately disagree during any transient:
236
+
237
+ - **EB enhanced health** rates the target group from its **own per-instance request-latency/status
238
+ sampling on a point-in-time snapshot** — a short traffic burst (observed `RequestCount ~10x`
239
+ baseline, peaking ~1,936/min, with a `TargetResponseTime` max outlier ~7.7 s) is enough to flip it
240
+ to *Degraded*.
241
+ - **The target-group console** shows **current ALB health**, which recovers within ~45 s (3 checks).
242
+ So when you open the console after the alert, it "looks perfectly healthy" — because it is, now.
243
+
244
+ api2's EB target group health-checks **`/health`** (interval 15 s, timeout 5 s, unhealthy threshold
245
+ 5 ≈ **75 s to trip**, healthy threshold 3 ≈ **45 s to recover**). A transient burst is **expected
246
+ behavior, not an ALB/target-group defect** — do not chase a phantom target failure.
247
+
248
+ **The exception:** some *Degraded* events are instead genuine **"X% HTTP 5xx"** — those are real
249
+ code bugs, not transient bursts. The 2026-08 batch traced to the `transactionId`-1062 collision
250
+ (gotcha above), the platform-wide EmailTemplate null-recipient TypeError, and the Pcmaticb2b ticket
251
+ interceptor's plain-`Exception` rejections — all now returning 4xx/fixed. Distinguish the two by
252
+ reading the ALB 5xx metric and `Logs.Issue`: zero 5xx + a traffic spike = transient EB snapshot;
253
+ a sustained 5xx rate = a code bug to fix.
254
+
219
255
  ## Known issues / accepted risks
220
256
 
221
257
  Open items a maintainer should know before changing this tier. None are "bugs to fix right now" —
@@ -289,6 +325,18 @@ they are the known sharp edges. Do not re-discover these from scratch.
289
325
  single-caller branches in `V2.php` as unverified until exercised directly.
290
326
 
291
327
  ## Change history
328
+ - 2026-08-25 — **Resolved the auto-generated `Api.transactionId` 1062 collision** (was the 2026-07-28
329
+ gotcha): the two DB-insert log sites now uuid the log-row `transactionId` (`_String::generateUuid()`)
330
+ — `V2.php` `internalApiRequest()` logger (~L2370) and `Controller/Index.php` error-recovery logger
331
+ (~L456); the CloudWatch JSONL append (~L433) is intentionally left bare-ms (no UNIQUE key). Confirmed
332
+ the production symptom (`Logs.Issue` ref `1Z`, 304 occurrences, `/v2/users/me` → `_Model->save()`).
333
+ Also added a **monitoring section**: an EB enhanced-health *Ok→Degraded* "TargetGroups … reduced
334
+ health state" transition does **not** mean the ALB marked a target unhealthy — on `api-production-1`
335
+ (acct `654654170868`) the ALB view stayed fully healthy (HealthyHostCount=2, 5xx=0) while a short
336
+ ~10x traffic burst (~1,936/min, TargetResponseTime max ~7.7 s) flipped EB's point-in-time snapshot;
337
+ the target-group console recovers in ~45 s so it "looks healthy" after the fact. Recorded api2's
338
+ `/health` check timing (15 s / 5 s / trip 75 s / recover 45 s) and how to tell a transient burst
339
+ (0 ALB 5xx) from a genuine "X% HTTP 5xx" code bug. (jcardinal)
292
340
  - 2026-07-28 — Added Known issue #10: cross-client page-number paging is inherently O(page) (deep pages materialize every preceding row into the Cache cluster), with caller-facing keyset cursors identified as the durable fix but left unbuilt/unscoped, plus the `DEEPEN_CHUNK_MAX_RECORDS = 500` tuning tradeoff against `getFullModelData()` expansion and `CURL_TIMEOUT_SECONDS = 30`. Added a change-guidance bullet that single-caller branches in the untested `V2.php` monolith get zero incidental coverage (the keyset `LIMIT` syntax error shipped invisibly for a full cycle). (jcardinal)
293
341
  - 2026-07-28 — Documented the previously unrecorded `060_register_instance_to_shared_application_load_balancer` postdeploy hook pair in the Deployment section (non-prod self-registration into the same-named ALB target group; production skipped), and recorded a **committed IAM access key** in `ebs/register_instance_to_shared_application_load_balancer.php` as a security note + Known issue #9 — location, line range, commit subject, and remediation only (rotate, audit CloudTrail, move to the instance profile; history rewrite is a separate sign-off). Flagged api2's copy as the unhardened original vs. the new worker2 reference implementation. (jcardinal)
294
342
  - 2026-07-28 — Added a consolidated **Known issues / accepted risks** section (8 items), absorbing the previously free-floating deferred raw-exception-disclosure follow-up as item 1, so the tier's sharp edges (unrotated committed secrets, pre-execute phase still outside the main guard, local Logs DB name mismatch, permissive CORS, unpinned `_underscore` build clone, untested `V2.php` monolith, JWT rotation overlap window) are in one place instead of scattered. Recorded that `DB_CACHE` is resolved by name (`Databases.name = 'Cache'`), never by a hardcoded id, which differs per Core instance. (jcardinal)
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-08-20
8
+ updated: 2026-08-25
9
9
  owners: [jcardinal, mhammontree, dfranks, bala]
10
10
  files: []
11
11
  related:
@@ -686,10 +686,19 @@ notified**. Pick deliberately:
686
686
 
687
687
  | Throw this | When | HTTP | Sentry | `Logs.Issue` | Goes to |
688
688
  |---|---|---|---|---|---|
689
- | `_Exception_Validation` | bad **client input** — a hard-block in a pre/post interceptor or model op | **400** | no | no | the caller, via the response envelope (front end can toast it) |
689
+ | `_Exception_Validation` | bad **client input** — a hard-block in a pre/post interceptor, a model op, **or a scripted-API method** | **400** | no | no | the caller, via the response envelope (front end can toast it) |
690
690
  | `_Exception_Business` | a **business condition a business user can act on and a developer cannot fix** | 500 | yes | yes, keyed by a stable `issueKey` | business recipients configured in the Tools `/errors` console |
691
691
  | plain global `Exception` | a genuine **code defect / unexpected state** | 500 | yes | yes, fingerprinted by **trace** | a developer ClickUp task |
692
692
 
693
+ > **`_Exception_Validation` yields a 400 from a scripted-API method too, not only from an interceptor.**
694
+ > Verified 2026-08-25 in `api2/Controller/Index.php` (~L317): `$isClientError = ($e instanceof
695
+ > \_Exception_Validation)` is evaluated at the **outer `execute()`/`api()` catch** that wraps **both**
696
+ > the pre/post interceptor dispatch **and** the scripted-API method call — so a scripted API that throws
697
+ > `_Exception_Validation` for bad client input returns a clean 400 (no Issue/Sentry), same as an
698
+ > interceptor. (Confirmed while fixing `_Model_Client_EmailTemplate::sendEmail()`, a scripted API, to
699
+ > throw `_Exception_Validation` on an empty recipient.) Note `_Exception_Business` **extends**
700
+ > `_Exception_Validation`, so it also satisfies this `instanceof` test.
701
+
693
702
  `_Exception_Business`'s constructor is `(issueKey, message, minimumUrgency)`. The `issueKey` is
694
703
  the fingerprint, so the Issue's identity survives refactoring; a plain `Exception`'s
695
704
  trace-based fingerprint changes when the code moves.
@@ -937,6 +946,13 @@ See: 2.0/apps/worker2/features/cross-account-aws-access.md
937
946
 
938
947
  ## Change history
939
948
 
949
+ - 2026-08-25 — Clarified the exception-routing rule: `_Exception_Validation` is a valid 400 throw
950
+ site from a **scripted-API method** too, not only a pre/post interceptor or model op. Verified at
951
+ the outer `execute()`/`api()` catch in `api2/Controller/Index.php` (~L317) where
952
+ `$isClientError = ($e instanceof \_Exception_Validation)` wraps both interceptor dispatch and the
953
+ scripted-API method call; noted `_Exception_Business` extends `_Exception_Validation` (satisfies the
954
+ same `instanceof`). Confirmed while fixing `_Model_Client_EmailTemplate::sendEmail()` (a scripted
955
+ API) to throw `_Exception_Validation` on an empty recipient. (jcardinal)
940
956
  - 2026-08-20 — Added "A `LIMIT 1` on a non-unique key MUST have a deterministic `ORDER BY`": an
941
957
  unordered `LIMIT 1` (and `fetchRow()` on an unordered result, which has no `LIMIT 1` to grep for)
942
958
  returns an arbitrary row, so any lookup on a non-unique key needs a tie-break ending in a unique
@@ -4,5 +4,5 @@
4
4
  |-----|-----------|---------|-------|
5
5
  | [PC Matic B2B — Startech Entitlement Provisioning (customer + SKU)](features/startech-entitlement-provisioning.md) | 2.0 | When a PC Matic B2B entitlement is created in TOGA 2.0 (`POST /entitlements`), the client registers the customer on StarTech's OptimumDesk platform and assigns | _underscore/Trait/Startech/Entitlement.php, _underscore/Model/Pcmaticb2b/Entitlement.php, _underscore/Config.php, api2/Config/production.ini, api2/Config/beta.ini, api2/Config/sandbox-client.ini, test/@srija/Startech Testing/PC Matic B2B/test_e2e_pcmaticb2b.sh |
6
6
  | [PC Matic B2B — Startech Per-Ticket-Type Ticket Stages](features/startech-per-ticket-type-stages.md) | 2.0 | Startech (OptimumDesk) exposes a **different status set per ticket type** — Support Request (workflow 56) has 15 statuses, Phone Call Support (workflow 59) has | dbchanges2/Client/2026-07-22a - TicketStageTicketTypeId.sql, dbchanges2/Core/2026-07-28 - TicketStageTicketTypeIdRecordField.sql, dbchanges2/_modules/startech/2026-07-28 - TicketStageIsSelectable.sql, dbchanges2/Client_Pcmaticb2b/2026-07-28 - pcmaticb2b ticketstages.sql, _underscore/Model/Client/TicketStage.php, _underscore/Trait/Startech/TicketStage.php, _underscore/Model/Pcmaticb2b/TicketStage.php, _underscore/Trait/Startech/Ticket.php, worker2/Worker/Startech.php, library/app/api/toga2.php, library/app/model/togadesk/ticket.php, togadesk/desk/includes/functions.php, togadesk/desk/includes/controllers/data/tickets/manage.php, togadesk/desk/template/pages/tickets/manage.php |
7
- | [PC Matic B2B — Startech Ticket Sync](features/startech-ticket-sync.md) | 1.0 | Bidirectional ticket sync between TOGaDesk 1.0 (client 177), TOGA 2.0 (client 21), and Startech (Easeedesk). | worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php, worker/crons/toga2/startech/common_import_supporting_records.php, library/app/api/toga2.php, library/app/api/startechticket.php, togadesk/desk/includes/controllers/quickactions.php, togadesk/desk/template/pages/tickets/manage.php, togadesk/desk/includes/functions.php, worker2/Worker/Startech.php, _underscore/Trait/Startech/Ticket.php, dbchanges2/Client_Pcmaticb2b/2026-06-22-pcmaticb2b-enhancements.sql |
7
+ | [PC Matic B2B — Startech Ticket Sync](features/startech-ticket-sync.md) | 1.0 | Bidirectional ticket sync between TOGaDesk 1.0 (client 177), TOGA 2.0 (client 21), and Startech (Easeedesk). | worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php, worker/crons/toga2/startech/common_import_supporting_records.php, library/app/api/toga2.php, library/app/api/startechticket.php, togadesk/desk/includes/controllers/quickactions.php, togadesk/desk/template/pages/tickets/manage.php, togadesk/desk/includes/functions.php, worker2/Worker/Startech.php, _underscore/Trait/Startech/Ticket.php, _underscore/Model/Pcmaticb2b/Ticket.php, dbchanges2/Client_Pcmaticb2b/2026-06-22-pcmaticb2b-enhancements.sql |
8
8
  | [PC Matic B2B Client Profile](profile.md) | 1.0 | PC Matic B2B is a client using TOGaDesk 1.0 for ticket management, with TOGA 2.0 as the data layer and Startech (Easeedesk) as an external ticketing system for | worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php, togadesk/desk/includes/functions.php |
@@ -5,8 +5,8 @@ project: TOGa
5
5
  client: pcmaticb2b
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-07-29
9
- owners: [snaredla]
8
+ updated: 2026-08-25
9
+ owners: [snaredla, jcardinal]
10
10
  files:
11
11
  - worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php
12
12
  - worker/crons/toga2/startech/common_import_supporting_records.php
@@ -17,6 +17,7 @@ files:
17
17
  - togadesk/desk/includes/functions.php
18
18
  - worker2/Worker/Startech.php
19
19
  - _underscore/Trait/Startech/Ticket.php
20
+ - _underscore/Model/Pcmaticb2b/Ticket.php
20
21
  - dbchanges2/Client_Pcmaticb2b/2026-06-22-pcmaticb2b-enhancements.sql
21
22
  related:
22
23
  - clients/pcmaticb2b/features/startech-per-ticket-type-stages.md
@@ -194,6 +195,7 @@ const TOGADESK_TICKET_TYPE__TOGADESK_PCS = 'TOGaDesk-PCS';
194
195
  ## Sync Prerequisites & Gotchas (2026-07-06)
195
196
 
196
197
  - **The entitlement gate blocks the 2.0 create.** A TOGaDesk ticket only syncs up to TOGA 2.0 (and can then reach Startech) if its email maps to an existing 2.0 contact **with an active entitlement**. `_Model_Pcmaticb2b_Ticket::prePost` rejects the create otherwise, so the desk ticket never gets a `referenceId` — and the escalate handler shows "Ticket is not linked to TOGa 2.0".
198
+ - **These prePost rejections are now HTTP 400, not 500 (fixed 2026-08-25).** `_Model_Pcmaticb2b_Ticket::prePost()` (the 2.0 pre-interceptor for `POST /v2/tickets`) previously threw a **plain `Exception`** for its four client-actionable rejections — *"Ticket requires a contact…"*, *"Contact not found for uuid=…"*, *"Contact not found for email=…"*, *"No active entitlement found for contact"* — each producing **HTTP 500 (EO-1)** plus a `Logs.Issue` and a Sentry event (the "requires a contact" one was prod `Logs.Issue` ref `FG`, 215 occurrences). All four now throw **`_Exception_Validation`**, which `Controller/Index.php` maps to **HTTP 400** — the client sees it in the response envelope with no Issue/Sentry/5xx-health impact. A client-input / business rejection belongs at 400, not 500. (See the 2.0 backend-php standard, *Choosing an exception class is a ROUTING decision*.)
197
199
  - **Staff-email collision.** `App_Api_Toga2::syncToga2ContactIntoTogadesk1People` matches `people` by email OR referenceId and **updates in place**. If the email already exists as a staff/admin account (clientid ≠ 177), it links `referenceId` onto that staff row and never creates a client-177 `type='user'` customer. Use a fresh, non-staff email for customer test tickets.
198
200
  - **The escalate button applies desk changes unconditionally.** The handler sets dept→337, `escalateToStartech=1`, Ticket Type→"Phone Call Support", and adds history **regardless of 2.0 linkage**. The `c_escalateToStartech=1` PUT (which triggers Startech) fires **only when `referenceId` is already set**; unlinked tickets are left for the sync cron to link — the button does not create the 2.0 ticket on-demand.
199
201
 
@@ -225,6 +227,7 @@ not yet resolved:
225
227
 
226
228
  ## Change history
227
229
 
230
+ - 2026-08-25: `_Model_Pcmaticb2b_Ticket::prePost()`'s four client-actionable rejections (requires a contact; contact not found by uuid; contact not found by email; no active entitlement) converted from plain `Exception` (HTTP 500 + `Logs.Issue` + Sentry) to `_Exception_Validation` (HTTP 400, no Issue/Sentry). The "requires a contact" case was prod `Logs.Issue` ref `FG`, 215 occurrences. Client-input/business rejections belong at 400. (jcardinal)
228
231
  - 2026-06-23: Initial doc — full bidirectional sync, c_escalateToStartech semantics, ticket type maps, escalate button, DB migration, constants, gotchas
229
232
  - 2026-07-06: Added sync prerequisites/gotchas (entitlement gate, staff-email collision) and clarified the escalate button's actual behavior (desk changes always; 2.0 PUT only when linked)
230
233
  - 2026-07-09: Corrected Startech ticket-type IDs (50 Support Request, 54 Phone Call Support for company 24412); added Sync Risks (escalate flag never reset, webhook 1→0 silencing, interceptor DB-gating) and Verification (OUT-log + E2E test); cross-linked entitlement provisioning.
@@ -5,8 +5,9 @@ project: TOGa
5
5
  client: pcmaticb2b
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-23
9
- owners: [snaredla]
8
+ updated: 2026-08-25
9
+ owners: [snaredla, jcardinal]
10
+ apps: [_underscore, api2, worker, worker2, togadesk, library, dbchanges2]
10
11
  files:
11
12
  - worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php
12
13
  - togadesk/desk/includes/functions.php
@@ -55,4 +56,5 @@ Calls:
55
56
 
56
57
  ## Change history
57
58
 
59
+ - 2026-08-25: Populated `apps:` scope (repos this client's docs actually span — 2.0 `_underscore`/`api2` plus the 1.0 `worker`/`togadesk`/`library` and `worker2`/`dbchanges2`). The 2.0 `_Model_Pcmaticb2b_Ticket::prePost` rejections now return HTTP 400 — see [startech-ticket-sync](features/startech-ticket-sync.md). (jcardinal)
58
60
  - 2026-06-23: Initial profile — client IDs, departments, Startech ticket type IDs, CRON entry point
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.646",
3
+ "version": "1.0.647",
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",