toga-ai 1.0.292 → 1.0.293

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.
@@ -10,7 +10,7 @@
10
10
  | [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 |
11
11
  | [_Component_*/_Model_* project-namespace registration (autoloader) & backslash-qualify traps](features/component-model-namespace-registration.md) | Every **project-local** `_Component_*` and `_Model_*` class in a 2.0 app **must declare the project namespace** at the top of the file: ```php namespace <NAMESP | _underscore/Loader.php, worker2/_.php, api2/_.php, worker2/Component/Forecast/Db/Db.php, worker2/Component/Forecast/SaleImport/SaleImport.php, api2/Component/Api/Netsuite/Netsuite.php |
12
12
  | [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 |
13
- | [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 |
13
+ | [Error Reporting — Issue/Event Aggregation (agreed POST-to-receiver design)](features/error-reporting-issue-event.md) | Platform-wide error-reporting infrastructure for TOGA 2.0, built around a two-table **Issue / Event** aggregation model in the shared **Core Logs DB**. | _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 |
14
14
  | [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 |
15
15
  | [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/fixer.php, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/Junk Drawer/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
16
16
  | [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 |
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: Error Reporting — Issue/Event Aggregation (exceptionHandler)
2
+ title: Error Reporting — Issue/Event Aggregation (agreed POST-to-receiver design)
3
3
  framework: "2.0"
4
4
  repo: _underscore
5
5
  project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-06
9
+ updated: 2026-07-08
10
10
  owners: ["dfranks"]
11
11
  files:
12
12
  - _underscore/Error.php
@@ -21,39 +21,51 @@ related:
21
21
 
22
22
  ## Summary
23
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.
24
+ Platform-wide error-reporting infrastructure for TOGA 2.0, built around a two-table
25
+ **Issue / Event** aggregation model in the shared **Core Logs DB**. An **Issue** is the
26
+ deduplicated record for a distinct error (keyed by a hash of the stack trace) carrying an
27
+ aggregate `occurrences` counter and an escalatable `urgency`; an **Event** is one row per
28
+ individual occurrence with its own trace and context.
29
+
30
+ **The agreed architecture (see below) decouples *reporting* from *persistence*:**
31
+ application error handlers **POST** error payloads to a centralized `/errors` receiver
32
+ endpoint; a **worker2 cron** consumes them and does all the hashing, upserting, aggregation,
33
+ escalation, and ClickUp sync into the Core Logs DB. Handlers do **not** write Issue/Event
34
+ rows to the database directly. This is the canonical target design for the whole
35
+ error-monitoring epic (TRUE-781xx) — the handler ticket, the dashboard ticket, and the
36
+ escalation-cron ticket must all conform to it.
37
+
38
+ ## Agreed error-monitoring architecture
39
+
40
+ Source: the **"2026-05-21 - Errors, Monitoring and Alerts"** meeting design (approved;
41
+ cited via Talos DevCore meeting notes). Pipeline:
42
+
43
+ 1. **Report (application side).** An application's error/exception handler serializes the
44
+ error (message, stack trace, context) and **POSTs it to a centralized `/errors` receiver
45
+ endpoint** (the meeting referenced `webhook.hub.com/error`). The handler's only job is to
46
+ transmit — it performs **no direct database persistence**.
47
+ 2. **Ingest (worker2 cron).** A worker2 cron consumes the received error payloads and:
48
+ - builds `issueHash` from the stack trace to identify the distinct error,
49
+ - **upserts an Issue** and **inserts an Event** into the **Core** Logs DB,
50
+ - aggregates occurrence counts within a time window,
51
+ - auto-escalates / de-escalates the Issue `urgency` based on occurrence frequency,
52
+ - syncs the Issue to ClickUp.
53
+ 3. **Scope.** API 2.0 handlers first; 1.0 is deferred.
54
+
55
+ The critical rule: **handlers POST; the cron persists (to Core).** Any approach where the
56
+ handler writes Issue/Event rows inline to a database contradicts this design.
57
+
58
+ ## Data model (Core Logs DB)
59
+
39
60
  - **`_underscore/Model/Core/Logs/Issue.php`** — `const DATABASE = _underscore::DB_LOGS;
40
61
  const TABLE = 'Issues';`. Fields: `id, uuid, dtCreated, dtLastOccurred, urgency, subject,
41
- description, clickupIdentifier, hash, errorMessage, occurrences, trace`.
62
+ description, clickupIdentifier, hash, errorMessage, occurrences, trace`. One row per
63
+ distinct error (dedup key = `hash`); `occurrences` is the rolling count, `dtLastOccurred`
64
+ the most-recent hit, `urgency` a list field, `clickupIdentifier` links to an escalation
65
+ ticket. There is **no `type` column**.
42
66
  - **`_underscore/Model/Core/Logs/Event.php`** — `const DATABASE = _underscore::DB_LOGS;
43
67
  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.
68
+ context, dtCreated`. One row per occurrence.
57
69
 
58
70
  The `Issues`/`Events` tables live in the shared **Core Logs** DB, provisioned from
59
71
  `dbchanges2/Logs/` (the `Logs/` folder targets the framework-level, non-tenant `Logs` DB
@@ -61,35 +73,55 @@ with unqualified table names — see the dbchanges2 architecture folder→DB map
61
73
  with per-client API/error logging in `Logs_<Tenant>` via `Model/Client/Logs/*`
62
74
  (`DB_CLIENT_LOGS`, `dbchanges2/Logs_Client/`).
63
75
 
64
- ## Data model
76
+ ## Current state of `_underscore/Error.php` (important — reads differently than you'd expect)
77
+
78
+ `_underscore/Error.php` registers `exceptionHandler()` via `set_exception_handler`, but in
79
+ **`_production` it does NOT persist any Issue/Event rows.** It builds a debug HTML body,
80
+ writes it to an S3 error-details file, and (when a Logs register is present) records a
81
+ single `_Model_Client_Logs_Error` / `_Model_Core_Logs_Error` row — the older flat error log.
82
+ There is no Issue/Event aggregation, no `issueHash` upsert, and no ClickUp escalation in the
83
+ handler on `_production`.
65
84
 
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`.
85
+ **An inline-DB-persist approach exists on an in-flight branch, but it conflicts with the
86
+ agreed design and must not be treated as the model to follow:** that approach has the
87
+ handler compute `$issueHash = md5(serialize($backtrace))` and directly upsert
88
+ `_Model_Client_Logs_Issue` + insert `_Model_Client_Logs_Event`, committing a transaction —
89
+ i.e. handler-side direct persistence, and to the **Client** Logs DB rather than **Core**.
90
+ That contradicts the approved architecture on two counts: (a) handlers should POST to the
91
+ `/errors` receiver, not persist; and (b) aggregation belongs in the Core Logs DB, driven by
92
+ the worker2 cron. New work (TRUE-78178 handlers) should implement the POST-to-receiver path,
93
+ not extend the inline-persist branch.
71
94
 
72
95
  ## Gotchas / known issues
73
96
 
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
97
+ - **Do not assume error persistence "already works."** On `_production` the handler writes
98
+ only an S3 debug body and a flat `*_Logs_Error` row — no Issue/Event aggregation exists in
99
+ production yet. The Issue/Event model is the *target*, delivered by the POST→cron pipeline.
100
+ - **`occurrences` must be incremented, not just seeded** — a naive upsert that sets
101
+ `occurrences = 0` only at creation never advances the aggregate counter. The cron must
76
102
  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.
103
+ - **`Event.trace` must be populated per occurrence** — the per-Event `trace` column is easy
104
+ to leave empty; record it on every Event.
105
+ - **Persistence must be conditional on the Core-Logs register.** If the Core Logs register
106
+ (`\_Database::$_registers[_underscore::DB_LOGS]`) isn't set, an Issue/Event write must be
107
+ skipped rather than fatal — but this belongs in the cron, not the handler.
108
+ - **Client vs Core Logs DB mix-up.** The Issue/Event tables belong in the **Core** Logs DB
109
+ (`DB_LOGS`, `Model/Core/Logs/*`). Writing them to a per-client Logs DB
110
+ (`DB_CLIENT_LOGS`, `_Model_Client_Logs_*`) is the wrong target and does not match the
111
+ agreed design.
82
112
  - **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`.
113
+ Logs DB, two dbchanges2 `Logs/` migrations that both `CREATE TABLE Issues`/`Events` will
114
+ collide ("table already exists"). Only **one** migration may create the shared tables;
115
+ later migrations only `ALTER`.
89
116
 
90
117
  ## Change history
91
118
 
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)
119
+ - 2026-07-08 — Corrected KB drift: documented the approved 2026-05-21 error-monitoring
120
+ architecture (handlers POST to a centralized `/errors` receiver; a worker2 cron builds
121
+ issueHash, upserts Issues + inserts Events into the Core Logs DB, aggregates/escalates,
122
+ syncs ClickUp — handlers do not persist). Clarified that `_production` Error.php performs
123
+ no Issue/Event persistence (only an S3 debug body + flat `*_Logs_Error` row), and that the
124
+ inline-DB-persist approach (handler-side upsert to the *Client* Logs DB) conflicts with the
125
+ agreed design and is not the pattern to follow. (dfranks)
126
+ - 2026-07-06 — Prior doc described the exceptionHandler as directly persisting Issue/Event
127
+ rows to the Core Logs DB with dedup; superseded by the 2026-07-08 correction above. (dfranks)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.292",
3
+ "version": "1.0.293",
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",