toga-ai 1.0.232 → 1.0.233

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.
@@ -12,7 +12,7 @@
12
12
  | [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
13
13
  | [Etilize Catalog Item Import & Refresh](features/etilize-catalog-item-import.md) | Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. | worker2/Worker/Etilize/Items.php |
14
14
  | [Etilize Item Translation Import](features/etilize-item-translation-import.md) | The abstract worker class `_Worker_Etilize_ItemTranslations` imports **non-English** item text from Etilize into the client's `ItemTranslations` table. | worker2/Worker/Etilize/ItemTranslations.php |
15
- | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
15
+ | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Monitors/RateEntitlement.php, worker2/Worker/Notification/Email.php, worker2/Worker/Rate.php, dbchanges2/Core/2026-05-21 - Monitors.sql, dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql |
16
16
  | [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, test/@dave/test_model_load_behavior.php, dbchanges2/Forecast/2026-06-25a - Add unique index on Opportunities netsuiteOpportunityInternalId.sql, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
17
17
  | [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_open_order_gating.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
18
18
  | [DB-Driven Notification (Internal) Email](features/notification-email.md) | Internal/notification emails (merge-conflict alerts, ops notices — anything system-generated, not client-facing transactional mail) are sent through one worker | worker2/Worker/Notification/Email.php, _underscore/Model/Client/EmailTemplate.php, dbchanges2/Client/2026-06-23a - EmailTemplateWrapper.sql, dbchanges2/Client_True/2026-06-23a - EmailTemplateWrapper.sql |
@@ -6,17 +6,21 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-10
9
+ updated: 2026-06-29
10
10
  owners: [mhammontree]
11
11
  files:
12
12
  - worker2/Worker/Monitor.php
13
13
  - worker2/Worker/Monitors/
14
+ - worker2/Worker/Monitors/RateEntitlement.php
14
15
  - worker2/Worker/Notification/Email.php
16
+ - worker2/Worker/Rate.php
15
17
  - dbchanges2/Core/2026-05-21 - Monitors.sql
18
+ - dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql
16
19
  related:
17
20
  - ../architecture.md
18
21
  - ./creating-worker-actions.md
19
22
  - ../../dbchanges2/architecture.md
23
+ - ../../../clients/rate/features/aig-contract-creation.md
20
24
  ---
21
25
 
22
26
  ## Summary
@@ -26,10 +30,11 @@ Prudential asset imports, AIG closed claims, …). One orchestrator applies cons
26
30
  anti-flap logic and notification rules to any registered "is X healthy?" check. Replaces
27
31
  scattered/ad-hoc monitoring where failures surfaced only when a client complained.
28
32
 
29
- **Status (2026-06-10):** v1.0 framework + orchestrator landed; **first child monitor
30
- pending**. Migration applied to local Core_2 only **not yet on staging/production Core**
31
- (coordinate before merge). Dashboard/acknowledgment layer under discussion (see Pending
32
- scope).
33
+ **Status (2026-06-29):** v1.0 framework + orchestrator landed; **first child monitor built**
34
+ `_Worker_Monitors_RateEntitlement` (TRUE-79129, see [worked example](#worked-example--rateentitlement-the-first-child-monitor)).
35
+ `Core.Monitors` migration applied to local Core_2 only **still not on staging/production
36
+ Core** (coordinate before merge; the new Rate change-set hard-fails where the table is
37
+ absent). Dashboard/acknowledgment layer under discussion (see Pending scope).
33
38
 
34
39
  Design principles: async isolation (one cron per monitor — a slow monitor can't block
35
40
  others) · anti-flap on the recovery side only (N consecutive OKs before declaring
@@ -131,6 +136,19 @@ Index `Monitors_isActive_IDX (isActive)`. `Core.CronJobs` reused as-is — one r
131
136
  (recipients see it verbatim); register any non-Core DB connection inline at the top of
132
137
  `Run()` — the framework does **not** call `initialize()` on child monitors.
133
138
 
139
+ **Confirmed against the orchestrator (`Worker/Monitor.php`):**
140
+ - The orchestrator calls `$phpClass::Run()` with **no args** and does **not** call
141
+ `initialize()` on children. A child needing a non-Core connection (e.g. a `Logs_<Client>`
142
+ DB) must self-register it inside `Run()`, guarding against a double-register:
143
+ `if (!isset(_Database::$_registers[$alias])) { _Database::register(db, host, user, pass,
144
+ null, null, $alias); }`, with credentials from `_Config::databaseLogs(...)`. The
145
+ registration pattern mirrors `worker2/Worker/Rate.php::initialize()`.
146
+ - The orchestrator wraps the call in its own try/catch and turns any `Throwable` into an
147
+ alert — children must **not** catch (re-confirms the "fail loudly" rule above).
148
+ - A read-only `Run()` (e.g. a log scan) needs **no** `transactionCommit()`.
149
+ - **PHP heredoc gotcha:** `self::CONST` does **not** interpolate inside a heredoc. Bind
150
+ class constants to local variables before building a heredoc SQL string.
151
+
134
152
  ### Adding a new monitor (runbook)
135
153
 
136
154
  1. Write the child class (one check, returns `{isOk, message}`).
@@ -150,17 +168,59 @@ No Lambda, SQS, or orchestrator changes needed per monitor.
150
168
  - Pause a monitor: `isActive = 0` (cron row can stay active). Pause email only: `peopleToNotify = JSON_ARRAY()`.
151
169
  - Manual state reset: clear `state`/`consecutiveOkCount`/`lastNotificationDt`/`lastNotificationType` — sparingly; the state machine self-corrects.
152
170
 
171
+ ### Worked example — RateEntitlement (the first child monitor)
172
+
173
+ `_Worker_Monitors_RateEntitlement` (`worker2/Worker/Monitors/RateEntitlement.php`, the
174
+ **first** file in `Worker/Monitors/`) watches Rate's AIG warranty-contract creation
175
+ (TRUE-79129). It is the reference implementation for the **log-scan** monitor pattern:
176
+
177
+ 1. `Run()` self-registers the `Logs_Rate` connection (the guard + `_Config::databaseLogs`
178
+ pattern above), because the orchestrator never calls `initialize()`.
179
+ 2. Scans `Logs_Rate.Api` for **failed outbound** AIG contract POSTs in the last 30 minutes
180
+ — `direction = 'OUT'`, `method = 'POST'`, `route LIKE '%/contract'`, and
181
+ `responseCode` non-2xx **or NULL**. Returns `(object){isOk, message}`; read-only, so no
182
+ commit.
183
+ 3. The detection signal is the `_underscore` **outbound API log**, not the entitlement
184
+ itself — see [why a monitor is needed](#why-an-external-monitor) below.
185
+
186
+ The change-set `dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql` does
187
+ the registration: one `Core.Monitors` row (`phpClass = _Worker_Monitors_RateEntitlement`,
188
+ `peopleToNotify = ['devteam@goagilant.com']`, `requiredConsecutiveOks = 2`,
189
+ `reminderFrequencyMinutes = 60`) + one `Core.CronJobs` row (`action = 'Monitor/Run'`,
190
+ `parameters = JSON_OBJECT('monitorId', LAST_INSERT_ID())`, schedule `*/15`,
191
+ `maxExecutionTime = 60`). LAST_INSERT_ID() chains the cron row to the just-inserted monitor.
192
+
193
+ ### Detection via the _underscore outbound API log
194
+
195
+ `_underscore/ApiRequest.php` logs **every** outbound call to `_Model_Client_Logs_Api`
196
+ (table `Logs_<Client>.Api`) and **commits it immediately**, independent of whether the
197
+ caller later swallows the exception — which is exactly what makes log-scan monitoring
198
+ possible for silent-failure interceptors. Columns to scan: `dtStamp`
199
+ (`FIELD_DATETIME_CREATED`), `direction` (`OUT`), `method`, `hostname` (scheme://host),
200
+ `route` (path after host), `responseCode`. `setUrl()` splits a full URL into
201
+ `hostname` + `route`, so a call to `.../contract` is logged with `route` ending `/contract`
202
+ (and cancellation as `/contract/cancel`). **Pattern for new monitors:** scan the client's
203
+ `Logs_<Client>.Api` rather than trying to detect failure on the business object.
204
+
205
+ ### Why an external monitor
206
+
207
+ Some interceptors **silently swallow** their own failures (no persistent flag on the
208
+ business record), so the only durable failure signal is the committed outbound API log.
209
+ The Rate case is documented in
210
+ [Rate AIG contract creation](../../../clients/rate/features/aig-contract-creation.md).
211
+
153
212
  ## Client variations
154
213
 
155
214
  None — the framework is shared Core infrastructure. Individual monitors target specific
156
- clients' data flows (Compass, Prudential, AIG, …) but live as separate child classes.
215
+ clients' data flows (Compass, Prudential, AIG, Rate, …) but live as separate child classes.
157
216
 
158
217
  ## Gotchas / known issues
159
218
 
160
- - **First child monitor not yet built** — `worker2/Worker/Monitors/` does not exist yet
161
- (as of 2026-06-10).
162
- - **Migration not applied to staging/production Core** only local Core_2. Coordinate
163
- before merge.
219
+ - **Migration not applied to staging/production Core** — `Core.Monitors`
220
+ (`2026-05-21 - Monitors.sql`) is on local Core_2 only (as of 2026-06-10, re-confirmed
221
+ 2026-06-29). The new `2026-06-29a` Rate change-set will **hard-fail (table not found)** on
222
+ any environment where `2026-05-21 - Monitors.sql` has not yet been promoted — confirm the
223
+ Monitors table exists before deploying.
164
224
  - `worker2/MONITORING_PLAN.md` is referenced by the design doc but **missing on disk** —
165
225
  stale reference to resolve.
166
226
  - Child return value must be an **object** with both `isOk` and `message`; a bare array
@@ -177,6 +237,7 @@ clients' data flows (Compass, Prudential, AIG, …) but live as separate child c
177
237
  HTML email + dashboard deep-links · anti-flap on the alarm side.
178
238
 
179
239
  ## Change history
240
+ - 2026-06-29 — Built the first child monitor, `_Worker_Monitors_RateEntitlement` (TRUE-79129) + change-set `2026-06-29a`; added the log-scan worked example, the `_underscore` outbound-API-log detection pattern, and confirmed child details (orchestrator never calls `initialize()` so children self-register non-Core connections; no commit on read-only `Run()`; heredoc cannot interpolate `self::CONST`). Re-flagged that `Core.Monitors` is still local-only — the new change-set hard-fails where the table is absent. (mhammontree)
180
241
  - 2026-06-10 — Documented the v1.0 monitoring framework (orchestrator, `Core.Monitors` table, recovery-side anti-flap state machine, child contract). First child monitor + staging/prod migration still pending. (mhammontree)
181
242
 
182
243
  ## Related docs
@@ -16,7 +16,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
16
16
 
17
17
  ## 2.0 framework
18
18
 
19
- - **_underscore** (_Underscore) _(framework core)_ — 17 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
19
+ - **_underscore** (_Underscore) _(framework core)_ — 18 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
20
  - **worker2** (Worker) — 22 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
21
21
  - **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
22
22
  - **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
@@ -2,6 +2,7 @@
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
+ | [Rate AIG Warranty Contract Creation (silent-failure interceptor)](features/aig-contract-creation.md) | 2.0 | When a Rate entitlement is created, `_Model_Rate_Entitlement::postPost` (`_underscore/Model/Rate/Entitlement.php`) creates an **AIG warranty contract** as a non | _underscore/Model/Rate/Entitlement.php, _underscore/ApiRequest.php, worker2/Worker/Monitors/RateEntitlement.php |
5
6
  | [Rate Monthly Reconciliation Report](features/monthly-reconciliation-report.md) | 1.0 | A monthly cron that emails an Excel reconciliation report covering all Rate subscription sales orders and their linked PayPal payments for the prior calendar mo | worker/crons/notifications/reports/rate/send_monthly_rate_purchases_report.php, worker/schedules/cron.worker.notification.json |
6
7
  | [Rate SalesOrder → NetSuite CashSale Export (postPost)](features/netsuite-cashsale-export.md) | 2.0 | Rate sells home-warranty / home-tech-support products. | _underscore/Model/Rate/SalesOrder.php, _underscore/Model/Rate/Item.php |
7
8
  | [Rate SAML SSO](features/saml-sso.md) | 2.0 | Rate uses Azure AD as its IdP (`login.rate.com`). | _underscore/Model/Rate/ClientAuthentication.php, saml/Controller/Index.php, toga2-view/src/hooks/useAuthenticationFlow.ts |
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: "Rate AIG Warranty Contract Creation (silent-failure interceptor)"
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: rate
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-06-29
10
+ owners: [mhammontree]
11
+ files:
12
+ - _underscore/Model/Rate/Entitlement.php
13
+ - _underscore/ApiRequest.php
14
+ - worker2/Worker/Monitors/RateEntitlement.php
15
+ related:
16
+ - clients/rate/profile.md
17
+ - ../../../2.0/apps/worker2/features/monitoring-framework.md
18
+ ---
19
+
20
+ ## Summary
21
+
22
+ When a Rate entitlement is created, `_Model_Rate_Entitlement::postPost`
23
+ (`_underscore/Model/Rate/Entitlement.php`) creates an **AIG warranty contract** as a
24
+ non-critical side effect: it authenticates against AIG, POSTs to `.../contract`, and on
25
+ success stamps `payload->c_aigContractNumber` and `payload->c_aigContractId` back onto the
26
+ entitlement. **It only attempts this when the entitlement payload has a primary contact
27
+ address.**
28
+
29
+ The catch: **every failure path is silent.** There is no persistent failure flag on the
30
+ entitlement, so a contract that never got created looks identical to one that did until a
31
+ customer or AIG reports it missing. That is why this flow is watched externally by the
32
+ `_Worker_Monitors_RateEntitlement` monitor rather than by any state on the entitlement.
33
+
34
+ This is a base-`_underscore` interceptor specific to Rate's product (it lives under
35
+ `Model/Rate/`); documented here as a Rate client-feature because the behavior and its
36
+ monitoring are Rate-scoped.
37
+
38
+ ## How it works
39
+
40
+ 1. `postPost` runs after an entitlement is saved. It proceeds **only if** the payload has a
41
+ primary contact address; otherwise it does nothing.
42
+ 2. Authenticate against AIG (POST `/authentication/login`) → obtain an access token.
43
+ 3. POST the contract payload to `.../contract`.
44
+ 4. On success: read `aigContractNumber` from the response and set
45
+ `payload->c_aigContractNumber` + `payload->c_aigContractId`.
46
+
47
+ ## Silent-failure paths (the gotcha)
48
+
49
+ All of the following fail **silently** — no exception surfaces to the caller and nothing is
50
+ persisted on the entitlement:
51
+
52
+ - A `try/catch` that only `error_log()`s with the note *"AIG contract creation is
53
+ non-critical"* and swallows the exception.
54
+ - **Auth response non-2xx** (no throw).
55
+ - **No access token** returned (no throw).
56
+ - **Contract POST non-2xx** (no throw).
57
+ - **Success response but missing `aigContractNumber`** (no throw).
58
+
59
+ Net effect: there is no durable signal on the entitlement that contract creation failed.
60
+
61
+ ## Detection / monitoring
62
+
63
+ Because the interceptor swallows failures, detection rides the **committed outbound API
64
+ log** instead. `_underscore/ApiRequest.php` logs every outbound call to
65
+ `_Model_Client_Logs_Api` (table `Logs_Rate.Api`) and commits it immediately, regardless of
66
+ whether the caller later swallows the exception. The AIG contract call lands with
67
+ `direction = OUT`, `method = POST`, and `route LIKE '%/contract'` (cancellation is
68
+ `/contract/cancel`).
69
+
70
+ `_Worker_Monitors_RateEntitlement` (worker2) scans `Logs_Rate.Api` for failed AIG contract
71
+ POSTs (`route LIKE '%/contract'`, `responseCode` non-2xx **or NULL**) in the last 30
72
+ minutes and alerts the dev team. See the
73
+ [Monitoring Framework worked example](../../../2.0/apps/worker2/features/monitoring-framework.md#worked-example--rateentitlement-the-first-child-monitor).
74
+
75
+ ### Known detection limitation (decided)
76
+
77
+ Detection matches **only** `POST .../contract` (`route LIKE '%/contract'`), which
78
+ deliberately **excludes** `/contract/cancel` and the shared `/authentication/login`.
79
+ Consequence: a creation failure caused by an **auth failure** is **not** caught, because
80
+ `/authentication/login` is shared with the cancellation flow and so cannot be attributed to
81
+ creation vs. cancellation. This is an accepted, documented limitation — not an oversight.
82
+
83
+ ## Gotchas / known issues
84
+
85
+ - **No persistent failure flag** — the only durable evidence of a failed contract creation
86
+ is the outbound API log row, not the entitlement.
87
+ - **Address-gated** — `postPost` does nothing when the payload lacks a primary contact
88
+ address, so an entitlement with no address never even attempts contract creation (and
89
+ never logs a `/contract` call).
90
+ - **Auth-induced failures are blind to the monitor** — see the detection limitation above.
91
+
92
+ ## Change history
93
+
94
+ - 2026-06-29 — Documented the silent-failure AIG warranty-contract interceptor and the
95
+ external log-scan monitor (`_Worker_Monitors_RateEntitlement`, TRUE-79129), including the
96
+ accepted detection limitation that auth-induced failures are not attributable (shared
97
+ `/authentication/login`). (mhammontree)
@@ -6,17 +6,21 @@ apps:
6
6
  - saml
7
7
  - toga2-view
8
8
  - worker
9
+ - worker2
10
+ - dbchanges2
9
11
  project: SAML SSO Gateway
10
12
  client: rate
11
13
  type: profile
12
14
  status: active
13
- updated: 2026-06-25
14
- owners: ["rgirish", "bala"]
15
+ updated: 2026-06-29
16
+ owners: ["rgirish", "bala", "mhammontree"]
15
17
  files: []
16
18
  related:
17
19
  - clients/rate/features/saml-sso.md
18
20
  - clients/rate/features/monthly-reconciliation-report.md
19
21
  - clients/rate/features/netsuite-cashsale-export.md
22
+ - clients/rate/features/service-card-entitlements.md
23
+ - clients/rate/features/aig-contract-creation.md
20
24
  ---
21
25
 
22
26
  ## Summary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.232",
3
+ "version": "1.0.233",
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",