toga-ai 1.0.291 → 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 |
@@ -92,6 +92,15 @@ project-local `_Component_*`/`_Model_*` must still declare the project namespace
92
92
  unqualified `catch (Exception $e)` fails **silently** — the more insidious of the two.
93
93
 
94
94
  ## Change history
95
+ - 2026-07-08 — **Production recurrence** of this exact fatal (concrete fingerprint, no doc change
96
+ to the mechanism): a worker2 deploy declared `_Component_Forecast_SaleImport`
97
+ (`Worker/Netsuite/Invoice.php` autoload) and its sibling `_Component_Forecast_Db`
98
+ (`Component/Forecast/Db/Db.php`, opportunity jobs) in the global namespace → every
99
+ Forecast-writing NetSuite webhook type threw at autoload the moment the deploy landed, flipping
100
+ a Forecast-wide ~98%-success window to ~72%-failure at a single timestamp. Confirms the
101
+ invisible-to-`php -l`, blocks-all-writes-before-DB behavior in prod. Diagnostic path (how the
102
+ before/after `isSuccess` split is read out of `Core.WorkerJobs`) is captured in the
103
+ [Forecast.Sales import doc's diagnostic playbook](./forecast-sale-import.md). (dfranks)
95
104
  - 2026-07-08 — Documented the `_Component_*`/`_Model_*` project-namespace registration
96
105
  requirement (Loader.php eval-bridge mechanism, ~71-83), the runtime-only failure mode (not
97
106
  caught by `php -l`), and the three constructs that must be backslash-qualified inside a
@@ -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 silentlyno 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-06Moved 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-08Corrected 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)
@@ -447,6 +447,46 @@ Keep **create→delete windows short** so most records slip between cron runs; o
447
447
  `import_sales` during a batch. (The daily discrepancy-fix also cleans stragglers since the
448
448
  record is deleted in NetSuite.)
449
449
 
450
+ ## Diagnostic playbook — proving whether inbound webhooks actually wrote Forecast.Sales (run cold)
451
+ Durable procedure for answering "did the real-time webhook path work, or is a `fixer.php`
452
+ sweep masking a broken pipeline?" It spans **two DB clusters** — correlate them; never infer
453
+ success from a `Forecast.Sales` row alone.
454
+ 1. **Receipt (prod-logs `Logs.Webhook`)** — proves the POST *arrived*, nothing more. Columns:
455
+ `id, uuid, dtStamp, sourceIp, method, route, requestPayload` (JSON with
456
+ `recordType`/`internalId`/`eventType`/`action`). NetSuite hits land on route `/netsuite`.
457
+ **There is NO status/error column** — a row here means "received", not "succeeded".
458
+ 2. **Success/failure (prod-core `Core.WorkerJobs`)** — the authoritative outcome:
459
+ `isSuccess` (NULL=pending/running, 0=fail, 1=success), `failureReason` (mediumtext, full
460
+ stack trace), `action`, `dtCreated`.
461
+ 3. **Two-stage fan-out.** The router job `action='Netsuite/Webhook'` fans out to per-record-type
462
+ **child** jobs (`Netsuite/Invoice/post`, `Netsuite/SalesOrder/put`, `Netsuite/Opportunity/*`,
463
+ etc.). The **router almost always succeeds**; the Forecast writes — and their failures — live
464
+ in the **child** jobs. Filter WorkerJobs on the child `action`, not the router, to judge the
465
+ pipeline.
466
+ 4. **CRITICAL — a `Forecast.Sales` row does NOT prove the webhook worked.** `fixer.php` (the
467
+ reconciliation sweep) independently writes/repairs rows, so a present row can mask a fully
468
+ broken webhook path. **Always correlate `Logs.Webhook.dtStamp` against
469
+ `WorkerJobs.isSuccess`/`dtCreated` by uuid/timestamp** — never conclude success from row
470
+ presence.
471
+ 5. **Bucket errors by class.** `failureReason` embeds a per-row `[uuid] ` prefix; strip it with
472
+ `CASE`/`REGEXP_REPLACE` to collapse per-row UUIDs into error classes and get clean counts —
473
+ the fast way to distinguish a deployment-wide fatal from long-tail noise.
474
+
475
+ **Known WorkerJobs failure classes on this pipeline (durable):**
476
+ - **Autoload fatal blocks ALL Forecast writes before any DB work** — a global-namespace
477
+ `_Component_Forecast_SaleImport`/`_Component_Forecast_Db` throws at autoload (see the
478
+ [namespace-registration doc](./component-model-namespace-registration.md)); every
479
+ Forecast-writing child type (Invoice/SalesOrder/Opportunity/JournalEntry/CashSale/CreditMemo)
480
+ fails identically and the fatal is **invisible to `php -l`**. A clean before/after `isSuccess`
481
+ split at a single deploy timestamp is the fingerprint (a bad push flips a Forecast-wide
482
+ ~98%-success window to ~72%-failure). Recurred in production 2026-07-08.
483
+ - **`Netsuite/InventoryItem/*` / `NonInventoryItem/post` — `watchdog: exceeded maxExecutionTime
484
+ without completing`**: item syncs timing out at the 300s watchdog.
485
+ - **`Netsuite/Invoice/post` — MySQL error 1213 `Deadlock found when trying to get lock`** on the
486
+ Forecast DB: pre-existing, low-volume, ~1.6–3.9s each.
487
+ - **`Netsuite/Webhook` router — `Unsupported NetSuite eventType: approve`** (`Worker/Netsuite.php`
488
+ ~line 53): salesOrder `approve` events aren't handled by the router and fail at fan-out.
489
+
450
490
  ## Gotchas / known issues
451
491
  - **Testing locally pollutes PROD unless you UNDEPLOY the prod AMQ enqueuer first.** Each sale
452
492
  transaction type has **TWO** NetSuite UE enqueuer deployments: **"AMQ — Enqueuer"**
@@ -499,6 +539,19 @@ record is deleted in NetSuite.)
499
539
  - The cron's sign handling is not portable here — see Sign convention.
500
540
 
501
541
  ## Change history
542
+ - 2026-07-08 — **Added the webhook-vs-fixer diagnostic playbook** for proving whether inbound
543
+ NetSuite webhooks actually wrote `Forecast.Sales`: correlate `Logs.Webhook` (prod-logs;
544
+ receipt-only, NO status column) against `Core.WorkerJobs` (prod-core; `isSuccess`/
545
+ `failureReason`), judge the **child** `Netsuite/<Type>/<verb>` jobs (not the almost-always-OK
546
+ `Netsuite/Webhook` router), and **never infer success from a `Forecast.Sales` row** because
547
+ `fixer.php` independently writes/masks rows — correlate timestamps. Bucket `failureReason` with
548
+ `CASE`/`REGEXP_REPLACE` (strip the `[uuid] ` prefix) into error classes. Recorded a concrete
549
+ **production recurrence (2026-07-08)** of the global-namespace autoload fatal on
550
+ `_Component_Forecast_SaleImport`/`_Component_Forecast_Db` (Forecast-wide, invisible to `php -l`,
551
+ clean before/after `isSuccess` split at a single deploy timestamp), plus three lower-volume
552
+ WorkerJobs failure classes on this pipeline (item-sync watchdog timeout; Invoice deadlock 1213;
553
+ router `Unsupported NetSuite eventType: approve`). Read-only diagnostic session, no code change.
554
+ (dfranks)
502
555
  - 2026-07-08 — **Clarified the revenue sign convention is NOT the `amountDue` "store raw
503
556
  positive" rule.** The TRUE-78923 "don't sign-flip credit memos/refunds" guidance applies
504
557
  only to `amountDue` (which this importer does not write); the line `amount`/revenue sign
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.291",
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",