toga-ai 1.0.1045 → 1.0.1047

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.
@@ -32,6 +32,6 @@
32
32
  | [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | Working reference for the Agilant NetSuite REST/SuiteQL integration (`App_Api_Netsuite_Rest`): auth, SuiteQL mechanics, table schemas, type/status codes, perfor |
33
33
  | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | Non-obvious NetSuite SuiteQL/REST field semantics behind `App_Api_Netsuite_Rest` shims (signs, `tl.id`==line, `iscogs`, ShipItem cost, `createdFrom`, shim-shape |
34
34
  | [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | 1.0 monitor `App_SystemMonitor_NetSuiteIntegration` — flags stale per-client NetSuite sync checkpoints (>48h) to ClickUp; open when a sync-stale alert is missin |
35
- | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, and the V1 th |
35
+ | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, the per-clien |
36
36
  | [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` — the 1.0-side client for the TOGa2/api2 REST API + cross-framework sync routines (`send`/`authenticate`, options DSL, `syncWithToga`/`syncWithT |
37
37
  | [Transfer order ↔ PO link reconcile (both directions, header + line, from NetSuite)](features/transfer-order-purchase-order-link-reconcile.md) | `App_Api_Toga2::reconcileTransferOrderPurchaseOrderLinks()` makes one TO's PO links (upstream + downstream, header + line) match NetSuite; open before touching |
@@ -6,18 +6,21 @@ project: Library
6
6
  client: pcmaticb2b
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-16
10
10
  owners: [snaredla, mhammontree]
11
11
  files:
12
12
  - library/app/api/toga2.php
13
13
  - library/app/api/startechticket.php
14
+ - library/app/apitransaction.php
14
15
  - worker/crons/toga2/startech/common_import_supporting_records.php
16
+ - worker/schedules/cron.worker.sync.json
15
17
  related:
16
18
  - clients/pcmaticb2b/features/startech-ticket-sync.md
17
19
  - clients/pcmaticb2b/profile.md
20
+ - clients/imhouston/profile.md
18
21
  ---
19
22
 
20
- PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, and the V1 third leg.
23
+ PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, the per-client StarTech login (all MSP clients), an empty "StarTech auth error" in Sentry, and the V1 third leg.
21
24
 
22
25
  ## Summary
23
26
 
@@ -85,12 +88,22 @@ Imports Startech (Easeedesk V3) config into TOGA 2.0 — users, groups, devices,
85
88
  - **Groups** come from `dynamic_field_options->data->groups` (keys `ID`/`group_name`) — verified 2026-07-09 returning **16 groups** for company 24412. The earlier claim that this returns empty and groups must come from `/groups_dispatching` was **wrong**: the empty result was caused by **incorrect credentials** in `startechticket.php` (`agilant.api`), not the endpoint. Fixed by using the `pcmaticb2b@startechteam.ro` account. `/groups_dispatching` returned 0 and is not used.
86
89
  - **Categories** — `App_Api_StarTechTicket::getCategories($companyId, $ticketTypeIds)` forwards a `ticket_type` filter alongside `company`. **Verified 2026-07-09**: `company=24412&ticket_type=50,54` returns 15 categories (the scoped set); the `ticket_type` param works as intended.
87
90
  - Any single upsert failing aborts the run (all `send()` calls throw on API error); the block order is users → groups → devices → categories → ticket types.
91
+ - One wrapper per client (`import_supporting_records_<client>.php` + `sync_togadesk_startech_<client>.php`: pcmaticb2b, imhouston, towfoundation, craftex, apiary), all `*/2` in `cron.worker.sync.json` (~720 runs/day per client).
92
+
93
+ ### StarTech login per client (all StarTech clients, verified 2026-10-16)
94
+
95
+ - The StarTech (Easeedesk/OptimumDesk) login comes from **`Core.PartnerApiIdentities`** (`Partners.name='Startech'`, `partnerIdentifier` = StarTech company id), read by `App_Api_Toga2::getPartnerApiIdentity()` over db link `db_toga2core`.
96
+ - `db_toga2core` = `[database_toga2core]` in worker `config.<env>.ini`. Present on worker/alpha/beta/demo/hotfix/sprint/stage/ui; **missing from every `config.dev-*` laptop config**, so the cron stops locally with "no API identity registered". Not a code bug.
97
+ - Company ids: **24412** PC Matic B2B (retail, own login); **24414** IM Houston, **24416** TOW Foundation, **24417** Craftex, **24418** Apiary — these four MSP clients **share ONE login**.
98
+ - Shared login ⇒ tenant isolation depends on sending the company id on every call that accepts it: library `dynamic_field_options` / categories / types send `company`; `_underscore` create_ticket, ticket update and `change_ticket_status` send `ticket_company`. Calls by ticket id (`ticket/{id}`, comments) and global lists (priorities, workflows, statuses) have no company field in the API. Any new StarTech call must pass the company id where the endpoint takes one.
99
+ - Credentials: see the open hardcoded-credentials gotcha in [startech-ticket-sync](../../../../clients/pcmaticb2b/features/startech-ticket-sync.md). Never copy values.
88
100
 
89
101
  ## Gotchas
90
102
 
91
103
  - **Do not add a NULL early-return guard in this function.** NULL `c_escalateToStartech` means the ticket was created in TOGaDesk — it must still sync to TOGA 2.0. The Startech gate lives in `_Trait_Startech_Ticket::postPost`, not here.
92
104
  - **`SUPPORT REQUEST` with a space** — `TOGA2_TICKET_TYPE_CODE__SUPPORT = 'SUPPORT REQUEST'`. Using `'SUPPORT'` silently breaks type matching.
93
105
  - **`$isPcmaticb2bUuid` flag** is used for PC Matic B2B-specific branching throughout `syncToga2TicketIntoTogadesk1Ticket()`.
106
+ - **Sentry `IMPORT_SUPPORTING_RECORDS_<CLIENT>.PHP >>> StarTech auth error: ` with nothing after the colon = network failure, not bad credentials.** `App_Api_StarTechTicket::authenticate()` calls `execute(false)`, which returns false and keeps no cURL error text (10s `setTimeout` timeout, dropped connection, DNS), so response code and body are empty. A real refusal shows an HTTP code + body. These crons set `App_ApiTransaction::$isLoggingEnabled = false`, so there is no API log row either. A single event heals itself on the next 2-min run — no action. Decided 2026-10-16 (WORKER1-4DQ, IM Houston): **no login retry** for one-off blips. Only investigate if events repeat or show an HTTP code.
94
107
 
95
108
  ## Related
96
109
 
@@ -55,6 +55,7 @@
55
55
  | [SO↔PO and TO↔PO bridge tables are TWO tables in OPPOSITE directions (upstream vs downstream)](features/sales-order-purchase-order-bridge-direction.md) | The two SO↔PO (and two TO↔PO) bridge tables mean opposite things (upstream = customer PO, downstream = vendor PO); open when an SO/TO↔PO query returns an empty |
56
56
  | [Per-client sales-order status filter (Surface FILTER_SET → table meta `filterOptions`)](features/sales-order-status-filter-surface.md) | Per-client status-filter dropdown on the sales-orders table, driven by a Surface `FILTER_SET`; open when the offered statuses are wrong for a tenant or when onb |
57
57
  | [Shipping Carriers & Carrier Account Numbers (records, matching, duplication, deletion)](features/shipping-carriers-and-accounts.md) | Per-client `ShippingCarriers` / `ShippingCarrierAccountNumbers` model, how the syncs match/create carrier rows, and the duplication + blank-carrier + pagination |
58
+ | [Ticket SLA engine (_Component_Sla) — two clocks, per-stage effects, client switch + operator recompute](features/sla-engine.md) | How 2.0 ticket SLA due times, pauses and the per-client switch are computed and written (`_Component_Sla`); open when an SLA countdown is wrong, a stage does no |
58
59
  | [_String helpers — ASCII-safe HTML entity encoding (and the parseBetween trap)](features/string-html-entity-helpers.md) | `_String` static string utilities: use `encodeToAsciiHtmlEntities` for any dynamic text going into a 2.0 email; open when escaping email text or parsing substri |
59
60
  | [Surface FIELD ↔ RecordField binding (maxLength / validationType / isWritable fold-in)](features/surface-field-recordfield-binding.md) | Resolver capability that folds `Core.RecordFields` input-contract metadata (maxLength/validationType/isWritable) onto a surface FIELD element; open when a 2.5 s |
60
61
  | [DECLINED 2026-10-08 — re-key Client.SurfaceOverrides from numeric Core ids to uuids](features/surface-overrides-uuid-rekey.md) | **Decision 2026-10-08 (jcardinal): keep numeric ids; no uuid re-key.** The plan below is not scheduled; see the Layer boundary in [surface-resolver](surface-res |
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: Ticket SLA engine (_Component_Sla) — two clocks, per-stage effects, client switch + operator recompute
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-16
10
+ owners: [jcardinal]
11
+ files:
12
+ - _underscore/Component/Sla/Sla.php
13
+ - _underscore/Model/Client/Ticket.php
14
+ - _underscore/Model/Client/TicketStage.php
15
+ - _underscore/Model/Client/TicketNote.php
16
+ - _underscore/Model/Core/Client.php
17
+ - dbchanges2/Core/2026-10-16a - SlaReplyClockCore.sql
18
+ - dbchanges2/Client/2026-10-16a - SlaReplyClockFanout.sql
19
+ - dbchanges2/Client_True/2026-10-16a - SlaStageDataRepair.sql
20
+ - dbchanges2/Client_Elite/2026-10-16a - SlaReplyClockOpenStage.sql
21
+ related:
22
+ - ./ticket-status-from-stage.md
23
+ - ../../api2/features/api-payload-interceptors.md
24
+ - ../../api2/features/tickets-api.md
25
+ - ../../dbchanges2/features/rerunnable-additive-inserts.md
26
+ ---
27
+
28
+ How 2.0 ticket SLA due times, pauses and the per-client switch are computed and written (`_Component_Sla`); open when an SLA countdown is wrong, a stage does not pause, or you need to recompute a client.
29
+
30
+ ## Summary
31
+
32
+ `_Component_Sla` (`_underscore/Component/Sla/Sla.php`) is the ONLY code that writes `Tickets.sla*` / `dtSla*` / `dtFirstResponse`. It is static, shared by every 2.0 app, and called from api2 payload interceptors (not model save hooks). Two clocks — resolution and reply (first response) — each with its own pause bookkeeping, driven by per-stage effects stored as client data.
33
+
34
+ ## How it works
35
+
36
+ - **Targets:** `TicketSlas (ticketTypeId, urgencyId)` → minutes (MINUTES ×1, HOURS ×60, DAYS ×1440). Any other unit (e.g. `BUSINESS_DAYS`) = no target. No business-day math.
37
+ - **Due:** `dtCreated + targetMinutes + pausedMinutes`, per clock.
38
+ - **Resolution clock:** effect = `TicketStages.slaEffect` (`RUN`/`PAUSE`/`STOP`, no stage = RUN). Columns `dtSlaPaused`, `slaPausedMinutes`. STOP time is charged like pause time; `_slaState` shows `STOPPED`.
39
+ - **Reply clock:** effect = `TicketStages.slaFirstResponseEffect` (`RUN`/`PAUSE`, column default `PAUSE`, no stage = RUN). Columns `dtSlaFirstResponsePaused`, `slaFirstResponsePausedMinutes`. Seeded RUN only for new / open / reopened stages. Keeps tracking after the first response so stamps stay right.
40
+ - **`resolvePauseState(params)`** is clock-agnostic and called once per clock. Reuse it for the Service Request SLA clocks — do not fork the math.
41
+ - **First response** = `MIN(TicketNotes.dtStamp)` of external notes by a contact other than the ticket's own contact. Authorless notes never count.
42
+ - **Read side:** `_slaTime`, `_slaState`, `_slaPausedAt` (FIELD_SQL on `_Model_Client_Ticket`). `_slaPausedAt` returns the paused stamp of the clock being shown (reply while unanswered and a reply due exists, else resolution). The Desk countdown reads it, not the raw columns.
43
+ - **Time = DB clock** (`SELECT NOW()` on the client writer), read once, then advanced by PHP elapsed time.
44
+ - **Client switch:** `Core.Clients.isSlaEnabled`. OFF = every SLA column NULL. Flipping it in `PUT /v2/clients/{uuid}` runs `applyClientSwitch` (backfill, skips STOP-stage tickets when turning on).
45
+ - **Operator recompute (no OFF/ON):** `PUT /v2/clients/{uuid}` body `{"recomputeSla": true}`. `_Model_Core_Client::prePut` strips the key (not a column), requires boolean `true`, parks it on `$api->passThrough`; `postPut` runs `recomputeClient` (all tickets, STOP included). Allowed only for callers who can WRITE `Clients.isSlaEnabled` (same field ACL as the V2 write gate). A body that is empty after the strip becomes `{}` in V2.php and `postPut` still runs. Pattern: [command keys in interceptors](../../api2/features/api-payload-interceptors.md#passing-state-from-a-pre-hook-to-a-post-hook).
46
+ - **One run per client:** the shared backfill takes `GET_LOCK('sla-recompute-<clientId>', 0)` on the SAME connection as its writes, `RELEASE_LOCK` in `finally`. Lock busy → `_Exception_Validation` 400. Any failure throws, so the API write rolls back. Reuse this shape for any "only one at a time per client" operation.
47
+ - **Cross-client:** the switch/recompute path uses `DB_TARGET_CLIENT` (`registerTargetClientDatabase`), never the caller's `DB_CLIENT`. Core and client data are never joined.
48
+
49
+ ## Gotchas
50
+
51
+ - **⚠ A stage that "never pauses" is usually data, not code.** Check `TicketStages.slaEffect` / `slaFirstResponseEffect` per stage in that environment first. QA alpha `Client_True` (2026-10-16): every stage stayed RUN because the `Client/2026-09-29d` seed matched by slug before the slugs existed; fixed by `Client_True/2026-10-16a - SlaStageDataRepair.sql`. See [fan-out seed keyed on a slug](../../dbchanges2/features/rerunnable-additive-inserts.md#gotchas).
52
+ - **⚠ The lock is released before api2 commits.** A second recompute started right after can overlap the first's uncommitted writes. Accepted (operator-only, rare) — do not copy this gap into a high-traffic operation; hold the lock past commit there.
53
+ - **No worker job for big clients** (decided). The recompute runs inside the request, so a very large client can hit the 300 s request cap. Paged at 500 tickets.
54
+ - **`FIELD_LIST` does not validate ENUMs** — `TicketStage::save()` checks both effects explicitly. Keep that check when adding a value.
55
+ - **Changing a stage's effect does not move existing tickets** until their next write or a recompute. Run `{"recomputeSla": true}` after a stage-effect data fix.
56
+
57
+ ## Related
58
+
59
+ - [Ticket status from stage](./ticket-status-from-stage.md)
60
+ - [API payload interceptors](../../api2/features/api-payload-interceptors.md)
61
+ - [Tickets API](../../api2/features/tickets-api.md)
62
+ - [Re-runnable additive migrations](../../dbchanges2/features/rerunnable-additive-inserts.md)
63
+
64
+ ## Change history
65
+ - 2026-10-16 — Reply clock gets its own per-stage effect (`slaFirstResponseEffect`, pauses outside new/open/reopened); operator recompute via `{"recomputeSla": true}`; per-client named lock (TRUE-82849). (jcardinal)
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-15
9
+ updated: 2026-10-16
10
10
  owners: ["mhammontree", "dfranks", "bala", "snaredla", "jcardinal", "rgirish"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -254,6 +254,8 @@ A `postPut`/`postPost` runs after the save, so it cannot compare old vs new valu
254
254
 
255
255
  Main use: **fire a POST side effect only on a REAL field change.** Edit forms resend unchanged fields (TOGa Commerce resends `contact` on every order save), so "key present in `$api->httpPayload`" is not "value changed". In `prePut`, read the current DB value, compare to the payload, store a bool; `postPut` acts only when it is true. Both hooks need their own interceptor row — if the PRE row is missing, the key is absent, so fall back to the old check rather than silently skipping. Reference: `_Model_Compass_SalesOrder::rememberWhetherOrderForPersonIsChanging()` (TRUE-82727, see [Compass approval-decision flow](../../../clients/compass-usa/features/approval-decision-flow.md)). Also used to carry a non-column field past PRE: Rate's `PASSTHROUGH_KEY_FULFILLMENT_PREFERENCE` ([AIG contract creation](../../../clients/rate/features/aig-contract-creation.md)).
256
256
 
257
+ **Command keys** (a non-column payload key that asks for an action): `prePut` strips the key (api2 would reject an unknown column), validates the exact value, parks it on `$api->passThrough`; `postPut` runs the action. Gate it on write access to a real field (field ACL). A body that is empty after the strip becomes `{}` in V2.php and `postPut` still runs. Reference: `{"recomputeSla": true}` on `PUT /v2/clients/{uuid}` ([SLA engine](../../_underscore/features/sla-engine.md)).
258
+
257
259
  ### Own-row guards in a PRE interceptor
258
260
 
259
261
  A PRE hook that limits a user to their own rows must close four bypasses — each found by review on the `user-global-settings` guard (TRUE-82613):
@@ -322,3 +324,4 @@ Refuse API-key callers when the rule is "the signed-in user" (no user on the tok
322
324
  - 2026-10-08 — `internalApiRequest` "[]" throw also fires on the OUTER request's warnings (e.g. WO-1); throw site now ~L2436; Core + client duplicate rows fire twice. (rgirish)
323
325
  - 2026-10-09 — Own-row guards in PRE hooks (alias keys + parent-route bypasses, PRE before field ACL); user-global-settings reference; known open holes. (jcardinal)
324
326
  - 2026-10-15 — PRE → POST state hand-off via `$api->passThrough`; detect a real field change, not a resent one (TRUE-82727). (bala)
327
+ - 2026-10-16 — Command keys: strip in `prePut`, park on `passThrough`, act in `postPut`; empty body → `{}` (TRUE-82849). (jcardinal)
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-01
9
+ updated: 2026-10-16
10
10
  owners: [bala, kyalamarthi, apeterson, ajean, jcardinal, rgirish]
11
11
  files:
12
12
  - dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql
@@ -18,7 +18,9 @@ files:
18
18
  - dbchanges2/Client_Compass/2026-09-21a - BackfillUpstreamItemFulfillmentUnits.sql
19
19
  - dbchanges2/Client_Compass/2026-09-23a - BundlesTableViewFieldFlags.sql
20
20
  - dbchanges2/Client_Compass/2026-09-24a - DeduplicateBundleItemPrices.sql
21
+ - dbchanges2/Client_True/2026-10-16a - SlaStageDataRepair.sql
21
22
  related:
23
+ - ../../_underscore/features/sla-engine.md
22
24
  - ../architecture.md
23
25
  - ../../worker2/features/netsuite-salesorder-open-orders-sync.md
24
26
  - ../../_underscore/features/acl-permission-chain.md
@@ -534,12 +536,21 @@ an INSERT that names only `uuid`/`tableViewId`/`recordFieldId`/`isVisible`/`inde
534
536
  and a nullable FK column takes it, leaving a structurally broken row that looks inserted. Prefer a
535
537
  `JOIN` (so a missing lookup inserts **no row** at all) or add the lookup to the guard's
536
538
  `WHERE EXISTS`.
539
+ - **⚠ A `Client/` fan-out seed keyed on a slug that ANOTHER folder sets can silently no-op.** Files in
540
+ `Client/` and `Client_<Name>/` run in no fixed order across folders, so a fan-out `UPDATE … WHERE slug
541
+ = '…'` that runs before the client file back-fills the slugs matches zero rows and reports success.
542
+ Found 2026-10-16: `Client/2026-09-29d` left every QA alpha `Client_True` stage `slaEffect = RUN`
543
+ (Resolved/Closed slugs NULL); fixed forward by `Client_True/2026-10-16a - SlaStageDataRepair.sql`.
544
+ Rule: in the same file, first back-fill the slug from the stable code (guarded), then seed by slug;
545
+ end with a verify SELECT and check it per environment. See [SLA engine](../../_underscore/features/sla-engine.md).
537
546
  - **`Core.RecordFields` has NO `sql` column.** A calculated field's SQL is **not** stored in
538
547
  metadata — it lives in the `_underscore` model as a `FIELD_SQL` static method. Do not go looking
539
548
  for a migration to change a calculated field's expression; there is none. See
540
549
  [FIELD_SQL calculated fields](../../_underscore/features/calculated-sql-fields.md).
541
550
 
542
551
  ## Change history
552
+ - 2026-10-16 — Fan-out seed keyed on a slug another folder sets can silently no-op; back-fill slugs
553
+ first, verify per environment (TRUE-82849). (jcardinal)
543
554
  - 2026-10-01 — Added the `_Model` + column-DEFAULT trap: `_Model` writes every declared field, so an
544
555
  explicit NULL skips a `NOT NULL DEFAULT` clause and the insert dies 1048; a unique key over a
545
556
  nullable column enforces nothing, so the model default must ship with the ALTER
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
21
21
 
22
22
  ## 2.0 framework
23
23
 
24
- - **_underscore** (_Underscore) _(framework core)_ — 99 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
24
+ - **_underscore** (_Underscore) _(framework core)_ — 100 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
25
25
  - **worker2** (Worker) — 87 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
26
26
  - **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
27
27
  - **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
@@ -2,4 +2,4 @@
2
2
 
3
3
  | Doc | Framework | Summary |
4
4
  |-----|-----------|---------|
5
- | [IM Houston](profile.md) | 1.0 | IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules. |
5
+ | [IM Houston](profile.md) | 1.0 | IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules and its StarTech MSP sync. |
@@ -4,25 +4,28 @@ framework: "1.0"
4
4
  apps:
5
5
  - togadesk
6
6
  - library
7
+ - worker
7
8
  project: TOGa Desk
8
9
  client: imhouston
9
10
  type: profile
10
11
  status: active
11
- updated: 2026-10-01
12
- owners: ["mhammontree"]
12
+ updated: 2026-10-16
13
+ owners: ["mhammontree", "snaredla"]
13
14
  files: []
14
15
  related:
15
16
  - ../../1.0/apps/togadesk/features/ticket-assign-to-lists.md
16
17
  - ../../1.0/apps/togadesk/features/per-client-host-restriction.md
18
+ - ../../1.0/apps/library/features/startech-pcmaticb2b-sync.md
17
19
  ---
18
20
 
19
- IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules.
21
+ IM Houston TOGa Desk client (togadesk `clients.id` 167, `CLIENT_IMHOUSTON`); open for its togadesk-specific rules and its StarTech MSP sync.
20
22
 
21
23
  ## Summary
22
24
 
23
25
  - TOGa Desk (1.0) client: `TOGaDeskSupport.clients.id` **167**, constant `CLIENT_IMHOUSTON` in `desk/includes/functions.php`.
24
26
  - Has its own branded TOGa Desk host — see [per-client-host-restriction](../../1.0/apps/togadesk/features/per-client-host-restriction.md).
25
27
  - Also a 2.0 tenant (`Client_Imhouston`); 2.0 scope not yet added to `apps`.
28
+ - StarTech MSP client: company id **24414**, worker crons `toga2/startech/*_imhouston.php`; shares one StarTech login with the other MSP clients — see [StarTech login per client](../../1.0/apps/library/features/startech-pcmaticb2b-sync.md).
26
29
 
27
30
  ## Rules
28
31
 
@@ -5,7 +5,7 @@ project: TOGa
5
5
  client: pcmaticb2b
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-10-05
8
+ updated: 2026-10-16
9
9
  owners: [snaredla, jcardinal, mhammontree]
10
10
  files:
11
11
  - worker/crons/toga2/startech/sync_togadesk_startech_pcmaticb2b.php
@@ -177,7 +177,7 @@ const TOGADESK_TICKET_TYPE__TOGADESK_PCS = 'TOGaDesk-PCS';
177
177
  - **Webhook must set `c_escalateToStartech=0`** — without this, the `postPost` gate evaluates `!null = true` and would skip Startech for webhook-originated tickets on any subsequent PUT.
178
178
  - **ticketType uses `c_startechTicketTypeId` dynamically** — stored on `TicketTypes`, not hardcoded in the worker.
179
179
 
180
- - **⚠ OPEN (found 2026-10-05, not fixed): Startech credentials are exposed in two places.** (1) `_underscore/Component/Api/Startech/Startech.php` hardcodes `STARTECH_API_USERNAME` / `STARTECH_API_PASSWORD` class constants in committed source — rotate, then move to Secrets Manager. (2) `Core.PartnerApiIdentities.partnerApiPassword` holds the Startech password in plaintext and is a STRING RecordField on record 305, so the records API can read it. New partners use Secrets Manager instead (see [8x8 survey import](../../../2.0/apps/worker2/features/8x8-post-call-survey-import.md)).
180
+ - **⚠ OPEN (found 2026-10-05, still open 2026-10-16): Startech credentials are exposed.** (1) `_underscore/Component/Api/Startech/Startech.php:6-7` **and** `library/app/api/startechticket.php:6-7` hardcode `STARTECH_API_USERNAME` / `STARTECH_API_PASSWORD` (shared fallback) in committed source — rotate, then move to Secrets Manager. Deleting the constants alone is not enough (a cleanup PR was drafted 2026-10-16 and dropped). (2) `Core.PartnerApiIdentities.partnerApiPassword` holds the Startech password in plaintext and is a STRING RecordField on record 305, so the records API can read it. New partners use Secrets Manager instead (see [8x8 survey import](../../../2.0/apps/worker2/features/8x8-post-call-survey-import.md)).
181
181
 
182
182
  ### Sync prerequisites & gotchas (2026-07-06)
183
183
  - **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".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1045",
3
+ "version": "1.0.1047",
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",