toga-ai 1.0.646 → 1.0.648
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/2.0/apps/_underscore/features/email-template-sending.md +17 -0
- package/knowledge/2.0/apps/ai-bdr/features/landing-chat-drawer.md +18 -5
- package/knowledge/2.0/apps/api2/architecture.md +56 -8
- package/knowledge/2.0/standards/backend-php.md +18 -2
- package/knowledge/clients/pcmaticb2b/INDEX.md +1 -1
- package/knowledge/clients/pcmaticb2b/features/startech-ticket-sync.md +5 -2
- package/knowledge/clients/pcmaticb2b/profile.md +4 -2
- package/package.json +1 -1
|
@@ -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
|
|
@@ -139,11 +139,20 @@ drawer; focus moves into the composer on open and is restored to the opener on c
|
|
|
139
139
|
|
|
140
140
|
## Gotchas
|
|
141
141
|
|
|
142
|
-
- **`api.togaiq.com` chat endpoints are origin/UA-gated
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
- **`api.togaiq.com` chat endpoints are origin/UA-gated — verified 2026-08-25 with
|
|
143
|
+
browser-shaped `curl` probes:** from origin `http://localhost:3000` the whole flow
|
|
144
|
+
works end-to-end (token minted, real Sonnet reply with `suggestions`/`cta`/
|
|
145
|
+
`cited_slides`), so **local dev gets live answers**. From origin
|
|
146
|
+
`https://bdr.dev.sandbox.togatech.com` the session mint returns **`403 Forbidden`**
|
|
147
|
+
— the sandbox origin is **not allowlisted**, so the deployed drawer silently answers
|
|
148
|
+
from the scripted fallback and looks like it works. The eventual production domain
|
|
149
|
+
will need the same. **Fix = the Talos backend owner adds the origin to the
|
|
150
|
+
talos-chat allowlist (aegra-api); no BDR change.** CORS is not the problem
|
|
151
|
+
(preflight returns `access-control-allow-origin: *` with `authorization,content-type`
|
|
152
|
+
allowed; one 502 on a preflight was transient). Diagnose from the browser console:
|
|
153
|
+
`talos-chat unavailable, using scripted fallback: session mint failed: 403` = origin
|
|
154
|
+
not allowlisted; `...: Failed to fetch` = a CORS/preflight failure. Check for
|
|
155
|
+
suggestion chips before concluding the backend is live.
|
|
147
156
|
- **Do not "fix" the transcript by sending the visible message list.** The greeting and
|
|
148
157
|
any merged/trimmed turns would break the strict alternating shape and return a 422.
|
|
149
158
|
- **The chat is fully client-side**, which is why `/landing` still builds as a
|
|
@@ -163,6 +172,10 @@ drawer; focus moves into the composer on open and is restored to the opener on c
|
|
|
163
172
|
the developer's branch/commit go-ahead.
|
|
164
173
|
|
|
165
174
|
## Change history
|
|
175
|
+
- 2026-08-25 — Verified the origin gate with browser-shaped probes: `localhost:3000` is
|
|
176
|
+
allowed and returns live replies end-to-end; `bdr.dev.sandbox.togatech.com` gets 403 at
|
|
177
|
+
session mint (not allowlisted → deployed drawer runs on scripted fallback). Recorded the
|
|
178
|
+
console-message → cause mapping. (tcox)
|
|
166
179
|
- 2026-08-24 — Initial doc. BUILT the `/landing` Talos chat drawer wired to the real
|
|
167
180
|
campaign-chat backend: `ChatPanel.tsx` (right-anchored drawer ported from the security
|
|
168
181
|
mockup), `lib/talosChat.ts` (fingerprint + 15-min session token + message-shape
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
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
|
-
>
|
|
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
|
|
120
|
-
>
|
|
121
|
-
>
|
|
122
|
-
>
|
|
123
|
-
>
|
|
124
|
-
>
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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