toga-ai 1.0.311 → 1.0.313
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/1.0/apps/library/INDEX.md +1 -0
- package/knowledge/1.0/apps/library/features/netsuite-sync-alert-monitor.md +116 -0
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +30 -4
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -1
- package/knowledge/2.0/apps/worker2/features/talos-transcript-ingestion.md +89 -16
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -11,5 +11,6 @@
|
|
|
11
11
|
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
|
|
12
12
|
| [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w | library/app/api/netsuite/rest.php, library/ssl/netsuite_ec_key.pem, test/@dave/Junk Drawer/nsq.php |
|
|
13
13
|
| [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php |
|
|
14
|
+
| [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`, title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watc | library/app/systemmonitor/netsuiteintegration.php, worker/crons/infrastructure/system_monitors.php |
|
|
14
15
|
| [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. | library/app/api/toga2.php, library/app/api/startechticket.php, worker/crons/toga2/startech/common_import_supporting_records.php |
|
|
15
16
|
| [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross | library/app/api/toga2.php, worker/crons/toga2/aig/sync_togasupply_aig.php, worker/crons/toga2/wje/sync_togasupply_wje.php |
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: library
|
|
5
|
+
project: Library
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-10
|
|
10
|
+
owners: ["bala"]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/systemmonitor/netsuiteintegration.php
|
|
13
|
+
- worker/crons/infrastructure/system_monitors.php
|
|
14
|
+
related:
|
|
15
|
+
- ../../worker/features/netsuite-togasupply-per-client-sync.md
|
|
16
|
+
- cron-execution-monitoring.md
|
|
17
|
+
- toga2-api-client-and-bridge.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
`App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`,
|
|
23
|
+
title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watches every TOGa Supply
|
|
24
|
+
client's NetSuite sync checkpoints and opens/updates a ClickUp task when any checkpoint goes
|
|
25
|
+
stale. It reads each client's six `NETSUITE_LAST_SYNC_DATETIME_*` parameters over the TOGa2
|
|
26
|
+
REST API and flags any older than **48h** (`STALE_THRESHOLD_SECONDS`). It is the alerting
|
|
27
|
+
surface sitting on top of the [NetSuite → TOGa Supply per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md);
|
|
28
|
+
that worker job does the syncing, this library class notices when it stops.
|
|
29
|
+
|
|
30
|
+
## Key files / entry points
|
|
31
|
+
|
|
32
|
+
- **`library/app/systemmonitor/netsuiteintegration.php`** — the monitor class. Extends
|
|
33
|
+
`App_SystemMonitor`. Holds the hardcoded per-client allowlist (`CLIENT_CONFIGURATIONS`),
|
|
34
|
+
the checkpoint key list (`NETSUITE_SYNC_PARAMETER_KEYS`), the 48h threshold, and all
|
|
35
|
+
ClickUp task create/update logic.
|
|
36
|
+
- **`worker/crons/infrastructure/system_monitors.php`** — the shared dispatcher (runs once a
|
|
37
|
+
minute). It instantiates every active monitor and calls `getStatus()`. This monitor is
|
|
38
|
+
registered there as `new App_SystemMonitor_NetsuiteIntegration`.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
- **Schedule.** `minuteFrequency = 60`, `minHourToRun = 10`, `maxHourToRun = 17` — so its
|
|
43
|
+
only tick is the **top of each hour**, 10:00–17:00. There is exactly one run per hour; if
|
|
44
|
+
that run dies, the monitor produces nothing until the next hour.
|
|
45
|
+
- **`getStatus()`** iterates `CLIENT_CONFIGURATIONS`, calls `getClientStatus()` per client,
|
|
46
|
+
and returns OK only if **every** client is OK (messages joined with `<br>`).
|
|
47
|
+
- **`getClientStatus()`** reads all six `NETSUITE_SYNC_PARAMETER_KEYS` for the client in a
|
|
48
|
+
**single** TOGa2 `GET /parameters` call using the options `where..in` DSL
|
|
49
|
+
(`['where' => ['AND' => [['key' => ['in' => KEYS]]]]]`), then compares each `value`
|
|
50
|
+
(`strtotime`) against `time() - STALE_THRESHOLD_SECONDS`. Any checkpoint older than 48h is
|
|
51
|
+
collected as stale.
|
|
52
|
+
- **`extractParameterRows()`** normalizes the api2 response: a single match comes back as an
|
|
53
|
+
**object**, many matches as an **array** — this helper always returns an array to iterate
|
|
54
|
+
(guarding on `isSuccess` + `data->parameters`).
|
|
55
|
+
- **`reportStaleParametersToClickUp()`** resolves the current True Dev sprint list, then for
|
|
56
|
+
each stale checkpoint creates or updates one ClickUp task titled
|
|
57
|
+
`"<client name> - <PARAMETER_KEY> Error"`. Existing tasks are matched by that exact title
|
|
58
|
+
and updated in place (description + Last Occurrence + incremented Occurrences), so repeated
|
|
59
|
+
stalls don't duplicate tasks. One task per client + sync type.
|
|
60
|
+
|
|
61
|
+
## Data model
|
|
62
|
+
|
|
63
|
+
- **Monitored clients** — hardcoded in `CLIENT_CONFIGURATIONS` (each entry = plain-English
|
|
64
|
+
`name` + the client/api/secret consts from `App_Api_Toga2`). Only clients in this array are
|
|
65
|
+
ever checked; anything omitted is invisible to the monitor. As of 2026-07-10 the list holds
|
|
66
|
+
15 clients: Canon, Endeavor Health, NYCHH, Trividia, Prudential-EDI, Broward Sheriff's
|
|
67
|
+
Office, Miami-Dade, Compass Group, S&P Global, ERAU, Masonite, NCCI, SHRSS, SBA, Quad,
|
|
68
|
+
Growrk. **AIG is deliberately excluded** (see the sync doc's AIG gotcha — a held account
|
|
69
|
+
whose checkpoints never advance, so it would alert forever).
|
|
70
|
+
- **Checkpoints** — the six `NETSUITE_LAST_SYNC_DATETIME_{SALES_ORDERS,PURCHASE_ORDERS,INVOICES,ITEM_RECEIPTS,ITEM_FULFILLMENTS,INVENTORY_ADJUSTMENTS}`
|
|
71
|
+
parameters, written per client by `common_sync_togasupply.php` and read here over the API.
|
|
72
|
+
- **Reporting state** — the dispatcher records `lastReported` / `lastStatus` /
|
|
73
|
+
`lastMessage` per monitor in the **`SystemMonitors`** table in **`db_log`** (title
|
|
74
|
+
"NetSuite Sync Alert"). ClickUp tasks land in the current True Dev sprint list.
|
|
75
|
+
|
|
76
|
+
## Gotchas / known issues
|
|
77
|
+
|
|
78
|
+
- **A monitor that can't finish inside the cron budget silently never runs.** The dispatcher
|
|
79
|
+
calls `App_Framework::cronInitialization(300, true)` — a hard **300s** budget shared across
|
|
80
|
+
**all** monitors in one process. When each of the six checkpoints was read in its own
|
|
81
|
+
`/parameters` call (14 clients × 6 = 84 serialized ~2.7s calls ≈ 230s), the whole
|
|
82
|
+
`system_monitors.php` run blew the budget and was killed mid-execution. Because this
|
|
83
|
+
monitor's only tick is the top of the hour — the exact run that died — it never completed
|
|
84
|
+
and **never reported**. Fixed 2026-07-10 by reading all six keys per client in one
|
|
85
|
+
`where..in` call (~230s → ~40s). If you add more clients/keys or more per-client calls,
|
|
86
|
+
watch the total against 300s.
|
|
87
|
+
- **Two silent-failure traps hide a dead monitor.** (1) The dispatcher wraps `getStatus()` in
|
|
88
|
+
`catch (Exception)` and on a throw sets `$status = array(true, 'Unexpected error occurred')`
|
|
89
|
+
— a **thrown monitor is recorded OK**, not ALERT. (2) A run killed by the time limit records
|
|
90
|
+
nothing at all (no `cronFinished()`). Either way the monitor looks fine while doing nothing.
|
|
91
|
+
- **"Seeded/listed but not reporting = dead monitor."** To catch this: check
|
|
92
|
+
`SystemMonitors.lastReported` in `db_log` — if this monitor's row is stale while every other
|
|
93
|
+
monitor is current, its hourly run is dying. Cross-check the cron log
|
|
94
|
+
(`Vision_Log.Log`, `sourceJob LIKE '%system_monitors%'`) for a **Started without a Finished**
|
|
95
|
+
on the heavy runs. This is exactly how the 2026-06-29 → 07-10 blind period was found:
|
|
96
|
+
`lastReported` was frozen at 2026-06-29 14:19 while everything else advanced.
|
|
97
|
+
- **Hardcoded ClickUp API token in source.** `CLICK_UP_API_ACCESS_TOKEN` is a literal constant
|
|
98
|
+
in this file (pre-existing). It should be moved to config/secrets rather than living in the
|
|
99
|
+
class — treat the in-repo token as compromised if the repo leaks. (Documenting that it
|
|
100
|
+
exists and where; the value itself must never be copied into the KB.)
|
|
101
|
+
- **Adding a client here is not the same as onboarding it to the sync.** This list only
|
|
102
|
+
controls *alerting*. A client must actually be provisioned on the sync engine (wrapper +
|
|
103
|
+
schedule + Parameters seed + customer link + custom fields — see the sync doc) before its
|
|
104
|
+
checkpoints mean anything; and a client that is held / never transacts supply (AIG) must be
|
|
105
|
+
left **off** this list or it produces permanent false-positive tickets.
|
|
106
|
+
|
|
107
|
+
## Change history
|
|
108
|
+
|
|
109
|
+
- 2026-07-10 — **Fixed the monitor timing out and silently not running since 2026-06-29.**
|
|
110
|
+
Rewrote `getClientStatus()` to read all six checkpoints per client in one TOGa2
|
|
111
|
+
`where..in` `/parameters` call (was 84 serialized calls ≈ 230s, over the 300s
|
|
112
|
+
`system_monitors.php` budget → killed every hourly run); added `extractParameterRows()` to
|
|
113
|
+
normalize api2's single-object-vs-array response. Run dropped ~230s → ~40s (verified
|
|
114
|
+
read-only against prod, 41.2s). Added **Quad** and **Growrk** to `CLIENT_CONFIGURATIONS`
|
|
115
|
+
(previously unmonitored); **AIG deliberately left off** (held account, would alert forever).
|
|
116
|
+
Documented the dead-monitor detection path and the two silent-failure traps. (bala)
|
|
@@ -153,11 +153,27 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
153
153
|
(e.g. this parse error), not per-client data. When the fix deploys, MODE flips to RUNNING and the
|
|
154
154
|
watermark jumps to ~now on completion (a positive live signal); the sync self-recovers and
|
|
155
155
|
backfills the gap.
|
|
156
|
-
- **
|
|
156
|
+
- **The NetSuite sync monitor's coverage is a per-client allowlist.**
|
|
157
157
|
`App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`)
|
|
158
|
-
alerts ClickUp when any client's checkpoint is >48h stale, but
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
alerts ClickUp when any client's checkpoint is >48h stale, but it only checks the clients in
|
|
159
|
+
its hardcoded `CLIENT_CONFIGURATIONS` list — an unlisted client's stall raises no alert.
|
|
160
|
+
Quad and Growrk were added 2026-07-10 (previously omitted); **AIG is deliberately left off**
|
|
161
|
+
because it's a held account whose checkpoints never advance (see the AIG gotcha) and would
|
|
162
|
+
alert forever. Full monitor mechanics + its own silent-failure traps live in the
|
|
163
|
+
[NetSuite Sync Alert Monitor doc](../../library/features/netsuite-sync-alert-monitor.md).
|
|
164
|
+
- **Item Fulfillments + Inventory Adjustments syncs are stalled across most togasupply clients
|
|
165
|
+
(real stalls, not disabled/false positives) as of 2026-07-10.**
|
|
166
|
+
`NETSUITE_LAST_SYNC_DATETIME_ITEM_FULFILLMENTS` has been frozen since ~2026-04-28/05-05 and
|
|
167
|
+
`NETSUITE_LAST_SYNC_DATETIME_INVENTORY_ADJUSTMENTS` since ~2026-06-12/20 on **Canon,
|
|
168
|
+
Endeavor, Trividia, Miami-Dade, S&P, ERAU, NCCI, SHRSS** and **Growrk (IF/InvAdj)**, while
|
|
169
|
+
**Prudential-EDI** is fully frozen (all six checkpoints) since ~2026-06-20. The other four
|
|
170
|
+
sync types (sales orders, POs, invoices, item receipts) advance normally for these clients.
|
|
171
|
+
All wrappers have `IS_ENABLED_INTEGRATION_ITEM_FULFILLMENTS = true` and
|
|
172
|
+
`IS_ENABLED_INTEGRATION_INVENTORY_ADJUSTMENTS = true`, so the IF/InvAdj sections are
|
|
173
|
+
**erroring while the others succeed** — a real data issue, not a disabled integration type.
|
|
174
|
+
Healthy across all six: **NYCHH, Broward, Compass, Masonite, SBA.** When the fixed monitor
|
|
175
|
+
runs, expect it to surface these as ~24 legitimate ClickUp tickets on its first successful
|
|
176
|
+
pass.
|
|
161
177
|
- **Seeded-but-stale = stalled sync.** Verify health by reading `LAST_SYNC_DATETIME_*` in each
|
|
162
178
|
`Client_<Id>.Parameters`: a healthy client advances to ~now every 5 min. As of 2026-06-17,
|
|
163
179
|
Quad was seeded but frozen at 2025-04/05 (not advancing) while the other 12 deployed clients
|
|
@@ -175,9 +191,19 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
175
191
|
opportunity/entitlement account, not a procurement/supply customer. Lesson: before onboarding a
|
|
176
192
|
client, confirm it actually transacts supply orders in NetSuite (SuiteQL the `customer`'s
|
|
177
193
|
transaction history) — being in the array doesn't mean it belongs on this engine.
|
|
194
|
+
Confirmed again 2026-07-10: `Client_Aig`'s six checkpoints are all still frozen at exactly
|
|
195
|
+
the seed date `2026-04-28 00:00:00` (never advanced), which is why AIG is **deliberately
|
|
196
|
+
excluded** from the NetSuite Sync Alert monitor — including it would generate permanent
|
|
197
|
+
false-positive stale tickets.
|
|
178
198
|
|
|
179
199
|
## Change history
|
|
180
200
|
|
|
201
|
+
- 2026-07-10 — Added **Quad + Growrk** to the NetSuite Sync Alert monitor's
|
|
202
|
+
`CLIENT_CONFIGURATIONS` (previously unmonitored); **AIG deliberately left off** (held
|
|
203
|
+
account, checkpoints frozen at the 2026-04-28 seed). Recorded a real, ongoing IF/InvAdj
|
|
204
|
+
stall across most togasupply clients (Prudential-EDI fully frozen) surfaced during the
|
|
205
|
+
monitor fix — see the new gotchas. Monitor mechanics moved to their own
|
|
206
|
+
[library feature doc](../../library/features/netsuite-sync-alert-monitor.md). (bala)
|
|
181
207
|
- 2026-07-09 — **Tier-wide outage (2026-07-06 → 07-09): a dropped brace in shared `rest.php`
|
|
182
208
|
killed every client's sync for 3 days.** A bad merge (`c3caa11a`, "Merge branch '_production'
|
|
183
209
|
into TRUE-79078", 2026-06-24) silently dropped two tokens at the `listLocations()` /
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
|
|
24
24
|
| [Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus programmatically. | .claude/skills/plan-ticket/scripts/talos.js |
|
|
25
25
|
| [Talos Pricing Automation (worker2 Cron — AWS Actuals, Calibration, Monthly Report)](features/talos-pricing-automation.md) | The worker2 half of the **Talos Pricing Platform** (see the talos `pricing-cogs-model` and tools `talos-pricing-ui` docs for the other halves). | worker2/Worker/Talos/Pricing.php, worker2/Database/TalosPricingCrons.sql |
|
|
26
|
-
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` runs a fully automated, cron-driven pipeline that ingests raw Teams transcripts into the **Talos / TOGa IQ** AWS Bedrock knowledge ba | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, dbchanges2/Team/2026-06-30a, dbchanges2/Team/2026-06-30b, dbchanges2/Team/2026-06-30c, dbchanges2/Team/2026-06-30d, dbchanges2/Team/2026-06-30e, dbchanges2/Core/2026-06-30a, dbchanges2/Core/2026-07-02a, dbchanges2/Team/2026-07-02a, dbchanges2/Team/2026-07-08a, dbchanges2/Team/2026-07-09a |
|
|
26
|
+
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` runs a fully automated, cron-driven pipeline that ingests raw Teams transcripts into the **Talos / TOGa IQ** AWS Bedrock knowledge ba | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Team/2026-06-30a, dbchanges2/Team/2026-06-30b, dbchanges2/Team/2026-06-30c, dbchanges2/Team/2026-06-30d, dbchanges2/Team/2026-06-30e, dbchanges2/Core/2026-06-30a, dbchanges2/Core/2026-07-02a, dbchanges2/Team/2026-07-02a, dbchanges2/Team/2026-07-08a, dbchanges2/Team/2026-07-09a, dbchanges2/Team/2026-07-10a |
|
|
27
27
|
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php |
|
|
28
28
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | > **SUPERSEDED (2026-07-09) — the S3-staging model below is history.** `Export` is now a thin > **GRAPH-DIRECT** cron poller: it no longer archives raw VTT to ` | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
|
|
29
29
|
| [VAPI Webhook Handler (worker2 — AI-BDR end-of-call processing)](features/vapi-webhook-handler.md) | `_Worker_Vapi` ([worker2/Worker/Vapi.php](worker2/Worker/Vapi.php)) is the **PHP side of the AI-BDR call loop** — the webhook that receives VAPI's end-of-call r | worker2/Worker/Vapi.php, worker2/Worker/Ai/Bdr/Vapi.php |
|
|
@@ -6,12 +6,13 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-10
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Team/Transcripts.php
|
|
13
13
|
- worker2/bin/sync-knowledge-bases.php
|
|
14
14
|
- worker2/Config/production.ini
|
|
15
|
+
- worker2/Database/TeamsTranscriptExports.sql
|
|
15
16
|
- dbchanges2/Team/2026-06-30a
|
|
16
17
|
- dbchanges2/Team/2026-06-30b
|
|
17
18
|
- dbchanges2/Team/2026-06-30c
|
|
@@ -22,6 +23,7 @@ files:
|
|
|
22
23
|
- dbchanges2/Team/2026-07-02a
|
|
23
24
|
- dbchanges2/Team/2026-07-08a
|
|
24
25
|
- dbchanges2/Team/2026-07-09a
|
|
26
|
+
- dbchanges2/Team/2026-07-10a
|
|
25
27
|
related:
|
|
26
28
|
- ./teams-transcript-export.md
|
|
27
29
|
- ./notification-email-template.md
|
|
@@ -65,20 +67,48 @@ JSON that PHP renders to an inline-styled HTML recap email, enqueued to the orga
|
|
|
65
67
|
## How it works
|
|
66
68
|
|
|
67
69
|
### `Export(int $lookbackDays, ?int $limit = null, array $cc = [], array $bcc = [])` (cron)
|
|
68
|
-
Thin poller
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
Thin poller with a **quiet-period stability gate** — it does **not** enqueue a transcript on
|
|
71
|
+
first sight (that processed still-in-progress meetings and emailed half-meeting recaps). The old
|
|
72
|
+
`organizers` parameter was **REMOVED** — organizers are always resolved from config now.
|
|
73
|
+
|
|
74
|
+
**Why a gate is needed:** the Graph `callTranscript` object carries **no "meeting ended"
|
|
75
|
+
signal** — only `id, meetingId, createdDateTime, transcriptContentUrl, meetingOrganizer` (no
|
|
76
|
+
`endDateTime`, no `duration`, no `status`, verified against Graph v1.0 `onlineMeeting:
|
|
77
|
+
getAllTranscripts`). Teams keeps **appending to the VTT while the meeting is live**.
|
|
78
|
+
`onlineMeeting.endDateTime` is only the **scheduled** end and for recurring meetings is the
|
|
79
|
+
series anchor (same unreliability that already rules out `startDateTime` for the meeting date).
|
|
80
|
+
So the only reliable end proxy is **that the transcript content stopped growing**.
|
|
81
|
+
|
|
82
|
+
**Observe-until-stable**, per organizer, per transcript:
|
|
83
|
+
1. Skip immediately if already handed off (`dtEnqueued` set) — **no re-download**.
|
|
84
|
+
2. Otherwise download the VTT **only to measure `strlen` (byte size)**.
|
|
85
|
+
3. First sighting → `recordObservation()` (INSERT, `dtEnqueued` NULL) and **defer**.
|
|
86
|
+
4. Size grew since last poll → still live → `updateObservationSize()` (resets the
|
|
87
|
+
`dtLastChanged` anchor) and **defer**.
|
|
88
|
+
5. Size unchanged for `>= TRANSCRIPT_STABLE_QUIET_SECONDS` (const = **1200s / 20 min**) →
|
|
89
|
+
meeting ended → `markEnqueued()` (stamps `dtEnqueued`, commit) then enqueue one
|
|
90
|
+
`Team/Transcripts/Process` job. At the hourly Export cron cadence this is effectively
|
|
91
|
+
"second poll with unchanged size ⇒ process," with the 20-min floor guarding against
|
|
92
|
+
unusually frequent polls.
|
|
75
93
|
|
|
76
94
|
- `$limit` caps enqueues per run (mainly for testing; e.g. `1` = a single meeting).
|
|
77
95
|
- `cc` / `bcc` thread through each `Process` job into the recap email; validated in
|
|
78
96
|
`enqueueRecapEmail` (deduped, organizer excluded). **Fallback:** if the organizer email
|
|
79
97
|
can't be resolved but `cc` is present, the recap sends **TO** the cc addresses.
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
|
|
99
|
+
**Watermark (`resolveWatermark`) — must not skip past an open observation.** The per-organizer
|
|
100
|
+
Graph window start takes `MIN(dtCreated)` over **open** observations (`dtEnqueued IS NULL`),
|
|
101
|
+
falling back to `MAX(dtCreated)` over handed-off rows, minus the 1-min overlap. Without this a
|
|
102
|
+
long meeting whose `createdDateTime` predates a newer already-handed-off meeting falls outside
|
|
103
|
+
the Graph date filter and gets stuck "observing" forever.
|
|
104
|
+
|
|
105
|
+
**Private helpers (Transcripts.php):** `loadObservation` (`useQueryCache(false)`; returns
|
|
106
|
+
`id,sizeBytes,dtLastChanged,dtEnqueued,meetingSubject,organizerEmail`), `recordObservation`
|
|
107
|
+
(replaces `recordExport`; synchronous + `_Database::transactionCommit(DB_TEAM)`),
|
|
108
|
+
`updateObservationSize`, `markEnqueued`, `meetingFor` (memoized `getMeeting` wrapper),
|
|
109
|
+
`secondsSince` (parses a MySQL UTC datetime with `' UTC'` suffix → elapsed seconds).
|
|
110
|
+
`alreadyExported()` and `recordExport()` were **removed** (superseded by the observation
|
|
111
|
+
helpers).
|
|
82
112
|
|
|
83
113
|
### `Process(int $transcriptId, string $graphUserId, string $meetingId, string $organizerEmail, string $subject, string $meetingStartIso, array $cc = [], array $bcc = [])`
|
|
84
114
|
1. Download the VTT **straight from Graph** (no S3 round trip).
|
|
@@ -128,12 +158,27 @@ spacing (Bedrock allows only one in-flight ingestion per KB, so parallel syncs r
|
|
|
128
158
|
- **`Team.TranscriptPromptTemplate`** — `name, bodyText, instruction, model, temperature, …`.
|
|
129
159
|
- **`Team.KnowledgeBases`** — `slug, name, isGeneral, bedrockKbId, isActive`; auto-kept-current
|
|
130
160
|
from Bedrock by the daily `SyncKnowledgeBases`.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
`transcriptIdentifier`
|
|
136
|
-
|
|
161
|
+
**The two tables are BOTH live and NOT redundant** — different owners, different jobs:
|
|
162
|
+
|
|
163
|
+
- **`Team.TranscriptExports`** — **discovery/observation ledger** owned by `Export()`: drives
|
|
164
|
+
the per-organizer watermark, transcript dedupe, and the quiet-period observation. Keyed on
|
|
165
|
+
`transcriptIdentifier` (widened to `VARCHAR(768)`, dbchanges2 `Team/2026-07-09a`).
|
|
166
|
+
Quiet-period columns (dbchanges2 `Team/2026-07-10a`): `sizeBytes` **repurposed** as the
|
|
167
|
+
last-observed raw VTT byte count; `dtLastChanged` (the quiet-period anchor); `dtEnqueued`
|
|
168
|
+
(**NULL = still observing; non-NULL = handed off**, the new done-marker). Dead column
|
|
169
|
+
`s3Key` **dropped** (leftover from the removed S3-staging scheme, no code refs).
|
|
170
|
+
- **`Team.TranscriptProcessing`** — **pipeline state machine** owned by `Process()`: `status`
|
|
171
|
+
ENUM (`pending/cleaned/uploaded/archived/synced/failed`), KB result keys
|
|
172
|
+
(`kbSlug/approvedKey/archiveKey/aiModel/failureReason`), and its own
|
|
173
|
+
`transcriptIdentifier`-keyed idempotency (also `VARCHAR(768)`). Dead column `sourceKey`
|
|
174
|
+
(and its unique index) **dropped** (removed S3-staging leftover).
|
|
175
|
+
|
|
176
|
+
**Dedupe key rule:** recurring meetings **reuse the same `meetingIdentifier`** but get a
|
|
177
|
+
**new `transcriptIdentifier` per occurrence**, so dedupe MUST stay keyed on
|
|
178
|
+
`transcriptIdentifier`, never `meetingIdentifier`.
|
|
179
|
+
|
|
180
|
+
**Migration ordering (`Team/2026-07-10a`):** ADD columns → backfill
|
|
181
|
+
`dtEnqueued=dtExported` on existing rows → DROP `s3Key` → DROP INDEX + `sourceKey`.
|
|
137
182
|
|
|
138
183
|
## Configuration
|
|
139
184
|
|
|
@@ -146,6 +191,20 @@ part of the ingestion loop** (raw reads removed). Credential values live only in
|
|
|
146
191
|
|
|
147
192
|
## Gotchas / known issues
|
|
148
193
|
|
|
194
|
+
- **DEPLOY ORDER: apply migration `2026-07-10a` BEFORE deploying the new worker2 code.** The
|
|
195
|
+
code reads/writes `dtEnqueued`/`dtLastChanged`; deploying code first errors on every poll.
|
|
196
|
+
The migration also backfills every existing row as handed-off (`dtEnqueued=dtExported`) so
|
|
197
|
+
no historical or legacy-duplicate row is re-observed or re-processed.
|
|
198
|
+
- **Root cause of the earlier "same meeting processed multiple times" duplicate rows.**
|
|
199
|
+
Diagnosed from prod `Team.TranscriptExports` rows 573/577 (same "Craftex Open Items"
|
|
200
|
+
meeting): row 573's `transcriptIdentifier` was truncated to exactly 255 chars and is the
|
|
201
|
+
byte-for-byte prefix of row 577's full 304-char id. These were pre-`2026-07-09a` truncated
|
|
202
|
+
rows; the removed `alreadyExported()` compared the full 304-char id and never matched the
|
|
203
|
+
truncated stored value, so the meeting re-enqueued. The `2026-07-09a` widen fixed it going
|
|
204
|
+
forward but left stale truncated rows that could never match. Across the table 42
|
|
205
|
+
`meetingIdentifier`s had duplicates (lengths 56/255/304) — a mix of truncation artifacts and
|
|
206
|
+
**legitimate recurring occurrences** (recurring reuses `meetingIdentifier`, new
|
|
207
|
+
`transcriptIdentifier` each time). `2026-07-10a`'s backfill marks all historical rows inert.
|
|
149
208
|
- **Idempotency: 304-char Graph ids truncated at `VARCHAR(255)` (root cause of endless
|
|
150
209
|
reprocessing).** Graph `callTranscript` ids are ~304 chars; `transcriptIdentifier` columns
|
|
151
210
|
were `VARCHAR(255)`, so ids were silently truncated. `alreadyExported()` compared the full
|
|
@@ -183,6 +242,20 @@ part of the ingestion loop** (raw reads removed). Credential values live only in
|
|
|
183
242
|
|
|
184
243
|
## Change history
|
|
185
244
|
|
|
245
|
+
- 2026-07-10 — **Added the quiet-period stability gate to `Export`** so in-progress meetings
|
|
246
|
+
are no longer processed (was emailing half-meeting recaps). `callTranscript` has no
|
|
247
|
+
end-of-meeting signal, so `Export` now observes each transcript's VTT byte size and only
|
|
248
|
+
hands off (enqueues `Process`) once size is unchanged for `TRANSCRIPT_STABLE_QUIET_SECONDS`
|
|
249
|
+
(1200s). New `TranscriptExports` columns `dtLastChanged`/`dtEnqueued` + repurposed
|
|
250
|
+
`sizeBytes`; new helpers (`recordObservation`, `updateObservationSize`, `markEnqueued`,
|
|
251
|
+
`loadObservation`, `meetingFor`, `secondsSince`); removed `alreadyExported`/`recordExport`.
|
|
252
|
+
`resolveWatermark` now floors at `MIN(dtCreated)` of open observations so a long meeting
|
|
253
|
+
can't fall out of the Graph window. Confirmed both `Team` tables are non-redundant
|
|
254
|
+
(Exports = discovery/observation, Processing = pipeline state machine); dropped dead columns
|
|
255
|
+
`TranscriptExports.s3Key` and `TranscriptProcessing.sourceKey`. Diagnosed the prior
|
|
256
|
+
duplicate-row root cause (pre-`2026-07-09a` truncated ids never matched; dedupe must stay
|
|
257
|
+
keyed on `transcriptIdentifier`, never `meetingIdentifier`). **Apply `2026-07-10a` before
|
|
258
|
+
deploying the code.** dbchanges2: `Team/2026-07-10a`. (jcardinal)
|
|
186
259
|
- 2026-07-09 — **Re-architected to GRAPH-DIRECT.** `Export` is now a thin cron poller
|
|
187
260
|
(removed the `organizers` param; added `$limit`, `cc`/`bcc`); `Scan` and
|
|
188
261
|
`bin/reprocess-transcripts.php` deleted; the `toga-private` staging bucket dropped entirely.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -4,7 +4,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
4
4
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
|
-
- **library** (Library) _(framework core)_ —
|
|
7
|
+
- **library** (Library) _(framework core)_ — 12 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 14 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
10
10
|
- **togadesk** (TOGa Desk) — 9 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
package/package.json
CHANGED