toga-ai 1.0.267 → 1.0.269

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.
@@ -4,6 +4,7 @@
4
4
  |-----|---------|-------|
5
5
  | [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. | library/_.php, library/app/, library/browser/ |
6
6
  | [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability | library/app/sso.php, library/sso/togahub_private_key.key |
7
+ | [Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)](features/cron-execution-monitoring.md) | `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out l | library/app/framework.php |
7
8
  | [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | `App_Model_Toga_Diagnostic::initializeDiagnosticDialog()` renders the device modal used across all TOGa service request views. | library/app/model/toga/diagnostic.php |
8
9
  | [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. | library/app/api/toga2.php |
9
10
  | [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. | library/app/email/template.php, library/app/email/agilant.php |
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)
3
+ framework: "1.0"
4
+ repo: library
5
+ project: Library
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-06
10
+ owners: [dfranks]
11
+ files:
12
+ - library/app/framework.php
13
+ related:
14
+ - ../../worker/architecture.md
15
+ ---
16
+
17
+ ## Summary
18
+
19
+ `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in
20
+ `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out
21
+ lifecycle recorded in the **`CronJobExecutions`** table in **`db_common`**. On start the job
22
+ INSERTs an execution row; on finish it UPDATEs that row with completion state and an
23
+ `executionTimeSeconds` duration. This is the shared-DB monitoring surface for 1.0 crons
24
+ across all 1.0 apps (worker, togadesk, toga, …), independent of the older `Log`/`CRON_START`
25
+ row in `db_log` and of the per-job Sentry check-in monitors.
26
+
27
+ ## How it works
28
+
29
+ - **`cronInitialization()`** — INSERTs one row into `CronJobExecutions` (`db_common`) via the
30
+ execution-id registry variable, records start time, and returns/stores the execution id used
31
+ to close the row later.
32
+ - **`cronFinished()`** — UPDATEs the same `CronJobExecutions` row (matched by execution id)
33
+ with completion status and `executionTimeSeconds`.
34
+ - **Connection:** every `CronJobExecutions` read/write goes against **`db_common`**. The table
35
+ exists **only** in `db_common` — it does **not** exist on the `db_log` connection.
36
+ - **Duration precision:** `executionTimeSeconds` is `round(microtime(true) - $start, 3)` —
37
+ millisecond precision landing in a `decimal(10,3)` column. Do **not** `(int)`-cast it; the
38
+ cast silently discards the sub-second precision even when the schema column is `decimal(10,3)`.
39
+ - **Schema:** the table is provisioned by the dbchanges migration
40
+ `dbchanges/Common/DF/2026-5-7 Cron Checkin.sql` (table `CronJobExecutions`,
41
+ `executionTimeSeconds decimal(10,3)`, no `note` column).
42
+ - **Design decision:** 1.0 records executions by writing **directly to the shared DB**
43
+ (`db_common`), *not* by POSTing from 1.0 to a 2.0 API endpoint. Per Jeff Cardinal, 1.0 code
44
+ must not POST to 2.0 code; the direct shared-DB write is the sanctioned path (TRUE-78182).
45
+
46
+ ## Gotchas
47
+
48
+ - **`db_log` has no `CronJobExecutions` table.** A `cronFinished()` UPDATE that runs against the
49
+ `db_log` connection throws on **every** 1.0 cron job. Both the INSERT and the UPDATE must
50
+ target `db_common`.
51
+ - **A `git merge` can silently resurrect pre-refactor logic.** These functions were cleanly
52
+ consolidated to a single `db_common`-only INSERT/UPDATE pair (commit `0a3a6f75`), but a later
53
+ `git merge origin/_production` (`509614036605…`) reintroduced the old dual-path structure from
54
+ `_production`'s still-unmigrated copy of the file — leaving duplicate INSERT/UPDATE blocks, and
55
+ reverting one path's connection argument back to `db_log`. Automatic merge resolution updated
56
+ the SQL text (renamed the table) but restored the wrong connection name, producing a
57
+ platform-wide breaking bug disguised as a simple PR-feedback rework. **When a merge touches a
58
+ function you already refactored on your branch and the base branch never got that refactor,
59
+ diff against the merge-base — do not just eyeball the current file** — to catch resurrected
60
+ logic. (Fixed 2026-07-06: consolidated back to one `db_common` INSERT/UPDATE pair.)
61
+
62
+ ## Change history
63
+ - 2026-07-06 — Fixed a merge (`origin/_production`) that reintroduced duplicate
64
+ `CronJobExecutions` writes and pointed one `cronFinished()` UPDATE at `db_log` (where the
65
+ table doesn't exist), which would have thrown on every 1.0 cron. Consolidated to one
66
+ `db_common` INSERT/UPDATE pair and switched `executionTimeSeconds` to `round(…, 3)` for the
67
+ `decimal(10,3)` precision the migration provides. (dfranks, TRUE-78182)
68
+ - 2026-05-07 — Introduced `CronJobExecutions` check-in/out in `App_Framework` cron lifecycle;
69
+ direct `db_common` write chosen over a 1.0→2.0 API POST. (dfranks)
70
+ </content>
71
+ </invoke>
@@ -7,6 +7,7 @@
7
7
  | [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
8
8
  | [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
9
9
  | [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
10
+ | [Error Reporting — Issue/Event Aggregation (exceptionHandler)](features/error-reporting-issue-event.md) | `_underscore`'s global exception handler persists every uncaught exception into a two-table **Issue / Event** model in the **shared Core Logs DB** (`_underscore | _underscore/Error.php, _underscore/Model/Core/Logs/Issue.php, _underscore/Model/Core/Logs/Event.php, dbchanges2/Logs/2026-07-06 - Issue and Event tables.sql |
10
11
  | [Record-Changed Event Publishing (_Event::publish to SQS)](features/event-publish-sqs.md) | `_Event::publish()` (in `_underscore/Event.php`) is the PHP side of the real-time event pipeline. | _underscore/Event.php |
11
12
  | [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/verify_je_classification.php, test/@dave/probe_je_accounts.php, test/@dave/probe_je_shape.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
12
13
  | [Item-Fulfillment Stage Lifecycle (picked/packed/shipped) & Order Status](features/item-fulfillment-stage-lifecycle-and-order-status.md) | Every ItemFulfillment (IF) now carries an explicit **stage** — picked → packed → shipped — resolved through `ItemFulfillmentStages → ItemFulfillmentStatuses` (m | _underscore/Model/Client/SalesOrder.php, _underscore/Model/Quad/SalesOrder.php, _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/SalesOrderStatus.php, _underscore/Model/Client/SalesOrderItem.php, _underscore/Model/Client/Item.php, _underscore/Model/Client/PurchaseOrderItem.php, library/app/api/toga2.php, dbchanges2/Client/2026-06-30a - BackfillNullStageItemFulfillmentsToShipped.sql, dbchanges2/Client/2026-06-30b - SalesOrderStatusesPickedPacked.sql, dbchanges2/Client/2026-06-30c - ItemFulfillmentStageIdNotNull.sql, dbchanges2/Client_CompassCanada/2026-06-30a - ItemFulfillmentLifecycleAndShippedBackfill.sql |
@@ -0,0 +1,95 @@
1
+ ---
2
+ title: Error Reporting — Issue/Event Aggregation (exceptionHandler)
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-06
10
+ owners: ["dfranks"]
11
+ files:
12
+ - _underscore/Error.php
13
+ - _underscore/Model/Core/Logs/Issue.php
14
+ - _underscore/Model/Core/Logs/Event.php
15
+ - dbchanges2/Logs/2026-07-06 - Issue and Event tables.sql
16
+ related:
17
+ - ../architecture.md
18
+ - ./per-client-database-connections.md
19
+ - ../../dbchanges2/architecture.md
20
+ ---
21
+
22
+ ## Summary
23
+
24
+ `_underscore`'s global exception handler persists every uncaught exception into a
25
+ two-table **Issue / Event** model in the **shared Core Logs DB** (`_underscore::DB_LOGS`,
26
+ models under `Model/Core/Logs/`) — **not** the per-client Logs DB. An **Issue** is the
27
+ deduplicated record for a distinct error (keyed by a hash of the backtrace) carrying an
28
+ aggregate `occurrences` counter; an **Event** is one row per individual occurrence with
29
+ its own stack trace and context. This is platform-wide error-reporting infrastructure,
30
+ not client-specific.
31
+
32
+ ## Key files / entry points
33
+
34
+ - **`_underscore/Error.php`** — `exceptionHandler()` (registered via
35
+ `set_exception_handler`). Computes `$issueHash = md5(serialize($backtrace))`, then, only
36
+ when the Core-Logs register is present (`isset(\_Database::$_registers[_underscore::DB_LOGS])`),
37
+ upserts an `_Model_Core_Logs_Issue` and inserts an `_Model_Core_Logs_Event`, committing
38
+ the `DB_LOGS` transaction.
39
+ - **`_underscore/Model/Core/Logs/Issue.php`** — `const DATABASE = _underscore::DB_LOGS;
40
+ const TABLE = 'Issues';`. Fields: `id, uuid, dtCreated, dtLastOccurred, urgency, subject,
41
+ description, clickupIdentifier, hash, errorMessage, occurrences, trace`.
42
+ - **`_underscore/Model/Core/Logs/Event.php`** — `const DATABASE = _underscore::DB_LOGS;
43
+ const TABLE = 'Events';`. Fields: `id, uuid, issueId (FK → Issue), errorMessage, trace,
44
+ context, dtCreated`.
45
+
46
+ ## How it works
47
+
48
+ 1. On an uncaught exception, hash the backtrace to identify the distinct error.
49
+ 2. Load the existing `Issue` by `hash`. On a miss, create it with `errorMessage`, `trace`,
50
+ `urgency = 'LOW'`, and `occurrences = 0`.
51
+ 3. **Increment `occurrences`** (`($issue->occurrences ?? 0) + 1`) and set
52
+ `dtLastOccurred`, then save — so the counter advances on every occurrence, hit or new.
53
+ 4. Insert one `Event` for this occurrence: `issueId`, `errorMessage`, per-occurrence
54
+ `trace`, and `context` (`print_r($GLOBALS, true)`).
55
+ 5. Commit the `DB_LOGS` transaction; surface `Error {issueId}, Event {eventId}` in the
56
+ debug body.
57
+
58
+ The `Issues`/`Events` tables live in the shared **Core Logs** DB, provisioned from
59
+ `dbchanges2/Logs/` (the `Logs/` folder targets the framework-level, non-tenant `Logs` DB
60
+ with unqualified table names — see the dbchanges2 architecture folder→DB mapping). Contrast
61
+ with per-client API/error logging in `Logs_<Tenant>` via `Model/Client/Logs/*`
62
+ (`DB_CLIENT_LOGS`, `dbchanges2/Logs_Client/`).
63
+
64
+ ## Data model
65
+
66
+ - **Issue** = one row per distinct error (dedup key = `hash`); `occurrences` is the rolling
67
+ count, `dtLastOccurred` the most-recent hit, `urgency` a list field, `clickupIdentifier`
68
+ links to an escalation ticket. There is **no `type` column** — it was dropped.
69
+ - **Event** = one row per occurrence, FK `issueId` → Issue, carrying the occurrence's own
70
+ `trace` and `context`.
71
+
72
+ ## Gotchas / known issues
73
+
74
+ - **`occurrences` must be incremented, not just seeded.** It was previously set to `0` only
75
+ at creation and never advanced, so the aggregate counter never moved. Fixed 2026-07-06 to
76
+ increment on every occurrence.
77
+ - **`Event.trace` must be set per occurrence.** The schema has a per-Event `trace` column
78
+ that was never populated; now recorded on every Event.
79
+ - **Persistence is conditional on the Core-Logs register.** If
80
+ `\_Database::$_registers[_underscore::DB_LOGS]` isn't set, the whole Issue/Event block is
81
+ skipped silently — no error row is written.
82
+ - **Shared-schema migration collision.** Because `Issues`/`Events` live in the *shared* Core
83
+ Logs DB, any two dbchanges2 `Logs/` migrations that both `CREATE TABLE Issues`/`Events`
84
+ will collide — the second fails with "table already exists". Two separate error-reporting
85
+ workstreams introduced these same tables (one adding the escalation cron + an
86
+ `IssueEmailAddresses` table, one adding this error-class persistence). Only **one**
87
+ migration may create the shared tables; reconcile overlapping `Logs/` create-scripts at
88
+ merge so the tables are created exactly once and later migrations only `ALTER`.
89
+
90
+ ## Change history
91
+
92
+ - 2026-07-06 — Moved Issue/Event tables and exceptionHandler persistence from the per-client
93
+ Logs DB to the shared Core Logs DB and dropped the `type` column; fixed `occurrences` to
94
+ increment each occurrence and set `Event.trace` per occurrence; removed dead commented-out
95
+ per-client-logs persistence block. (dfranks)
@@ -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)_ — 10 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
7
+ - **library** (Library) _(framework core)_ — 11 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
8
  - **worker** (Worker) — 12 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
9
  - **togadesk** (TOGa Desk) — 8 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
10
10
  - **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
@@ -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)_ — 21 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
19
+ - **_underscore** (_Underscore) _(framework core)_ — 22 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
20
  - **worker2** (Worker) — 25 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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.267",
3
+ "version": "1.0.269",
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",