toga-ai 1.0.334 → 1.0.336

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.
@@ -6,6 +6,7 @@
6
6
  | [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
7
7
  | [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
8
8
  | [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php |
9
+ | [_ApiRequest ENCODE__JSON now sends Content-Type: application/json](features/apirequest-json-content-type.md) | `_ApiRequest::execute()`'s `ENCODE__JSON` branch json-encoded the request body but never set a `Content-Type` header. | _underscore/ApiRequest.php |
9
10
  | [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 |
10
11
  | [Asynchronous Query Execution (writes-only, via Worker)](features/async-query-execution.md) | `_Query` can run a **write** query asynchronously so a long/slow write does not hold a request-scoped DB connection open long enough to hit **"MySQL server has | _underscore/Query.php, worker2/Worker/Infrastructure/Database.php, worker2/Worker/Team/Transcripts.php |
11
12
  | [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 |
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: _ApiRequest ENCODE__JSON now sends Content-Type: application/json
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-14
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - _underscore/ApiRequest.php
13
+ related:
14
+ - ../../worker2/features/oneuptime-worker2-monitoring.md
15
+ ---
16
+
17
+ ## Summary
18
+ `_ApiRequest::execute()`'s `ENCODE__JSON` branch json-encoded the request body but never set
19
+ a `Content-Type` header. cURL therefore defaulted to `application/x-www-form-urlencoded`, and
20
+ receivers parsed the entire JSON string as a single form-field NAME — observed at OneUptime as
21
+ `{"<json>":""}`. Every 2.0 `ENCODE__JSON` POST/PUT/PATCH caller was silently shipping
22
+ form-urlencoded bodies.
23
+
24
+ ## How it works
25
+ The `ENCODE__JSON` branch now adds `Content-Type: application/json`. It is guarded by a
26
+ case-insensitive scan of already-set headers, so a caller-supplied `Content-Type` still wins
27
+ (no override). All other encodings are unaffected.
28
+
29
+ ## Gotchas / known issues
30
+ - This changes the wire format of ALL existing `ENCODE__JSON` callers (they were previously
31
+ sending form-urlencoded). This is a correction, but smoke-test heavy JSON callers post-deploy:
32
+ ClickUp, NetSuite, Vapi.
33
+
34
+ ## Change history
35
+ - 2026-07-14 — Fixed `ENCODE__JSON` to send `Content-Type: application/json` (guarded so a
36
+ caller-set Content-Type wins). Root-caused via OneUptime receiving `{"<json>":""}`. Affects
37
+ all 2.0 ENCODE__JSON callers. (jcardinal)
38
+ </content>
39
+ </invoke>
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-13
9
+ updated: 2026-07-14
10
10
  owners: ["jcardinal"]
11
11
  files:
12
12
  - _underscore/Cloud.php
@@ -22,7 +22,7 @@ workers or app code. Alongside the existing copy / get / delete methods it now h
22
22
 
23
23
  ## Key files / entry points
24
24
 
25
- - `_underscore/Cloud.php` — `_Cloud::getS3Objects($s3BucketName, $s3BucketPath = '')`.
25
+ - `_underscore/Cloud.php` — `_Cloud::getS3Objects($s3BucketName, $s3BucketPath = '', ?string $awsRegion = null)`.
26
26
 
27
27
  ## How it works
28
28
 
@@ -33,10 +33,18 @@ workers or app code. Alongside the existing copy / get / delete methods it now h
33
33
  - `LastModified` — ISO-8601 string
34
34
  - `Size`
35
35
 
36
- The method is **purely additive** — no existing `_Cloud` method signature changed. Keeping
37
- S3 listing here (rather than instantiating `S3Client` in a worker) is the reason to prefer
36
+ The method is **additive** — the optional 3rd param `?string $awsRegion = null` falls back
37
+ to `_Config::cloud('aws_region')`, so existing 2-arg callers are unchanged. Keeping S3
38
+ listing here (rather than instantiating `S3Client` in a worker) is the reason to prefer
38
39
  this helper over ad-hoc SDK calls.
39
40
 
41
+ ### Per-bucket region override
42
+
43
+ The config-default region is **us-east-1**. Buckets in another region must pass
44
+ `$awsRegion` explicitly or the SDK throws `AuthorizationHeaderMalformed`. Notably the
45
+ **`agilant-as2`** bucket lives in **us-west-2** — the worker2 Office Depot S3 monitors pass
46
+ `'us-west-2'`.
47
+
40
48
  First consumer: the worker2 Office Depot EDI backlog monitor — see
41
49
  [OneUptime push-metric monitors](../../worker2/features/oneuptime-worker2-monitoring.md).
42
50
 
@@ -48,7 +56,12 @@ None — shared core helper.
48
56
 
49
57
  - Callers get every matching key regardless of count; pagination is handled internally, so
50
58
  do not add your own `ContinuationToken` loop on top.
59
+ - A bucket outside the config-default us-east-1 (e.g. `agilant-as2` in us-west-2) throws
60
+ `AuthorizationHeaderMalformed` unless you pass `$awsRegion`.
51
61
 
52
62
  ## Change history
63
+ - 2026-07-14 — `getS3Objects()` gained an optional `?string $awsRegion = null` 3rd param
64
+ (falls back to `_Config::cloud('aws_region')`); needed because `agilant-as2` is in
65
+ us-west-2, not the config-default us-east-1. Additive — 2-arg callers unchanged. (jcardinal)
53
66
  - 2026-07-13 — Added `_Cloud::getS3Objects()` S3 LIST helper (paginated `listObjectsV2`,
54
67
  returns `{Key, LastModified, Size}`); additive, no existing signatures changed. (jcardinal)
@@ -3,5 +3,5 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [TOGa Supply (toga2-supply) Architecture](architecture.md) | `toga2-supply` is the **React + Vite frontend** for TOGa Supply — warehouse fulfillment tooling (shipment selection, fulfill & ship against carrier APIs, NetSui | toga2-supply/src/api/toga.ts, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx |
6
- | [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
6
+ | [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
7
7
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-supply` (React + Vite) builds and deploys on **AWS Amplify**. | toga2-supply/amplify.yml, toga2-supply/.gitattributes, toga2-supply/.github/workflows/sync-stage-environments.yml, toga2-supply/.env.qc-security |
@@ -13,7 +13,10 @@ files:
13
13
  - toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx
14
14
  - toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx
15
15
  - toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx
16
+ - toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx
17
+ - toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json
16
18
  - toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx
19
+ - toga2-supply/src/components/ui/BaseInput/BaseInput.tsx
17
20
  - toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts
18
21
  - toga2-supply/src/pages/EditShipment/types.ts
19
22
  - toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx
@@ -87,6 +90,75 @@ from the saved fulfillment; **Save UPDATES the existing records** instead of POS
87
90
  - `CardTable` was made reusable for this: optional `editIcon` + `isEditButtonDisabled` props
88
91
  (defaults preserve prior behavior; the shipments card is the only consumer today).
89
92
 
93
+ ## Update Info / Edit Shipment form (config-driven; Figma-alignment pass 2026-07)
94
+
95
+ The New Shipment / Edit Shipment form is **config-driven**: fields come from
96
+ `viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json`, are rendered by
97
+ `view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx` through the shared
98
+ `EditShipmentForm.tsx`, and the individual inputs are `components/ui/BaseInput/BaseInput.tsx`
99
+ (text/currency) plus react-select (State/Country). To change field layout, labels,
100
+ required-ness, or ordering, edit the **config JSON**, not the component.
101
+
102
+ - **Create vs Edit are distinct entry points (decision — kept, not merged).** Select Items →
103
+ Next = create a **New Shipment**; the pending-shipments pencil = **edit** an existing draft.
104
+ They are deliberately NOT merged and there is no auto-load from Select Items, because a Sales
105
+ Order can have **multiple** shipments (partial fulfillment — Select Items shows
106
+ Ordered/Fulfilled/Committed per line), so "the saved shipment" is not singular and auto-loading
107
+ would be ambiguous. Page title now signals the mode: create = **"New Shipment"**, edit =
108
+ **"Edit Shipment"** (create previously mis-titled "Update Info").
109
+ - **Fulfillment Location is READ-ONLY (decision — Eric, Exec Dir Services Ops, authoritative).**
110
+ Sourced from the Sales Order's location and shown display-only; the value must **not** change
111
+ in the tool — any change happens back at the SO. Implemented as `getAddress` fetching
112
+ `location.name`, `EditShipmentPage` adding `fulfillmentLocation` to the transformed address, and
113
+ `renderEditShipmentFormInput` rendering it via `BaseDisabledInput` on an `isReadOnly` flag. **Not
114
+ persisted to `IF.locationId`** for now — persisting would be a separate backend "set from SO"
115
+ change plus a NetSuite-mapping check.
116
+ - **Reference 1 / Reference 2 fields are placeholders (rendered, values NOT wired yet).**
117
+ Definitions are settled: **Reference 1 = NetSuite Sales Order #, Reference 2 = NetSuite Purchase
118
+ Order #** (Eric, authoritative — supersedes Skyler's earlier "Ref 1 = vendor account #, Ref 2 =
119
+ company name"). Value sourcing/persistence/carrier-wiring is intentionally deferred; when wiring,
120
+ verify: SO# = the document **ORDER #** (e.g. `281144`) vs the internal id (`7221433`, stored as
121
+ `c_netsuiteInternalSalesOrderId`); the PO# lives in NetSuite and may not be in the 2.0 DB (the
122
+ SO's `customerPurchaseOrder` field — already reused as the carrier `referenceString` — vs a
123
+ separate linked NetSuite PO document).
124
+ - **Phone # is REQUIRED by design (verified, kept per Eric):** config `isRequired:true` +
125
+ `showRequiredIndicator`, enforced by `validateFormOnSubmit`.
126
+ - **Address block layout (Figma):** inline **left** labels (Addressee / Attention / Address 1 /
127
+ Address 2 / City / State / ZIP / Country) — previously `sr-only`. Residential checkbox moved
128
+ **below** Country with the checkbox to the **right** of the label.
129
+ - **Carrier section = two vertical stacks (Figma, was 3 horizontal columns):** col6 = Carrier /
130
+ Shipment Method / Carrier Account # / Declared Cost; col7 = Reference 1 / Reference 2 / Phone # /
131
+ Fulfillment Location. Row container is `justify-between` so the columns fill to the right edge.
132
+ - **Verbiage (David):** "This shipment contains a battery" → "Contains a battery"; "This shipment
133
+ needs a return label" → "Needs a return label".
134
+ - **Return-label affordance:** the "Edit" link is inline to the right of the "Needs a return label"
135
+ label, and the return-location text ("TOGA Technology") below it is also clickable — both open the
136
+ Edit Return Label modal.
137
+ - **Input UX helpers (`BaseInput`):** per-field clear "X" on address text inputs (opt-in
138
+ `hasClearButton` flag, via `useFormContext` `setValue`/`watch`; State/Country already have
139
+ react-select's clear); a **"Clear All"** link in the address header (`ClearAllAddressButton`
140
+ resets every address field via `useFormContext`); Declared Cost formats to 2 decimals on blur
141
+ (`onBlur` when `field.isCurrency`). Return modal (`ReturnShippingModal.tsx`): compact selects
142
+ vertically centered; Carrier Account # widened to `w-[172px]` to match Carrier.
143
+
144
+ ## Planned direction — "Fulfill & Ship" from an existing Item Fulfillment (future, Eric)
145
+
146
+ A **third entry mode** is planned (distinct from Select-Items-create and the pencil-edit): a
147
+ "Fulfill & Ship" button on an **existing NetSuite Item Fulfillment** punches out to the tool,
148
+ sources item/serial info from that existing fulfillment, uses the tool for the **shipping-label
149
+ portion only**, then pushes label/tracking data back into the Item Fulfillment. Most plumbing
150
+ already exists from TRUE-79191: `getShipmentForEdit`/rehydrate + read-only items (source from the
151
+ existing IF), `fulfillShipment` + `generateReturnLabel` (label), `saveShipmentToNetsuite`
152
+ (push-back). New pieces needed: the punch-out entry carrying an IF id, and a **label-only mode**
153
+ (skip the create step). This is a separate feature/ticket — recorded as planned direction, not done.
154
+
155
+ ## Sources — meeting notes / decision origins
156
+
157
+ Fulfill & Ship meeting transcripts and notes live in the **Internal Knowledgebase MCP connector**
158
+ (AWS Bedrock KBs, slug `development-team`, client `general`/`toga-technology`), searchable via
159
+ `kb_search` — e.g. "2026-05-20 - Fulfill and Ship Dev Sync", "2026-02-27 - Fulfill & Ship Process
160
+ Review". Use it when a decision's origin needs tracing.
161
+
90
162
  ## Reprint (backend-combined, 2026-07)
91
163
 
92
164
  Fulfilled-shipments view (`FulfilledShipmentsPage` → `ShipmentsCardTableForm` with
@@ -180,6 +252,19 @@ the call 403s and returns no label.
180
252
  everywhere (and `overflow-y-auto` on the `max-h-[530px]` card list in `ShipmentsCardTableForm`),
181
253
  and the fulfilled-page outer → `h-full overflow-auto`.
182
254
 
255
+ - **"Attention" wrote to the wrong field (fixed 2026-07).** The Attention input was bound to
256
+ `valueKey "addressee"` — the same key as the Address name — so both wrote to `addressee` and the
257
+ form showed the same value twice (e.g. "Eric" in both). Rebound Attention to `"attention"`. When a
258
+ config-driven field mirrors another field's value, suspect a duplicated `valueKey`.
259
+ - **The `/shipments` list is PER-SALES-ORDER (open product decision, Eric to decide).**
260
+ `getShipments` filters by `SalesOrders.c_netsuiteInternalSalesOrderId = <internalId>`, so
261
+ `/shipments` with no `internalId` shows the empty state — the tool is a per-SO **punch-out**
262
+ (entered from a NetSuite SO). The hamburger "Shipments" nav link (no SO context) is therefore
263
+ misleading. Options pending Eric's model decision (nothing changed yet): (A) keep per-SO and
264
+ remove/relabel the nav link; (B) add a global "All Pending Shipments" view — drop the SO filter
265
+ (client scoping is automatic from the JWT), but each row must then carry its own SO `internalId`
266
+ for the edit/fulfill links.
267
+
183
268
  ## Remaining work
184
269
 
185
270
  - ~~Reprint rewire~~ — done (backend `reprintLabelsApi` combine; see Reprint section).
@@ -194,7 +279,19 @@ the call 403s and returns no label.
194
279
  unit add/remove diffing against the API (the edit route already carries the fulfillment uuid);
195
280
  (b) the return modal's weight/dims/carrier fields are an **edit-trap** — see the gotcha below.
196
281
  - UPS 35-char hardening, location-scoped inventory lookup, carrier/method on the NS IF,
197
- responsiveness per Figma, FedEx + UPS end-to-end verification before prod.
282
+ FedEx + UPS end-to-end verification before prod.
283
+ - **Responsiveness (phase 2/3 — explicitly NOT in original scope per the 2026-02-27 Process Review
284
+ and 2026-05-20 Dev Sync).** David Mancol's Figma annotation **is** the spec: reduce the in-box
285
+ column count by 1 on narrower screens; flexible gaps that scale with screen size (min 2px, max
286
+ 50px); may explore stacking. Owners: David Mancol (design) + Alexandra Peterson (phase 2/3). Not
287
+ yet implemented.
288
+ - **Reference 1/2 value wiring** — fields render but values aren't sourced/persisted; see the form
289
+ section (SO# vs internal id, PO# in NetSuite) before wiring.
290
+ - **Global-vs-per-SO Shipments list** — pending Eric's model decision; see the gotcha above.
291
+ - **"Fulfill & Ship from an existing Item Fulfillment"** third entry mode — see Planned direction.
292
+
293
+ This is the **first testable iteration** of the Figma-aligned form; a change-request list is
294
+ expected once QA/users start.
198
295
 
199
296
  ## Deploy / provisioning requirements (per environment)
200
297
 
@@ -228,6 +325,18 @@ not the base `_Model_Client_ItemFulfillment`. Tested with GroWrk; UPS support wa
228
325
  for Compass and is not yet in prod.
229
326
 
230
327
  ## Change history
328
+ - 2026-07-14 — TRUE-79191 (Figma-alignment pass on the config-driven New/Edit Shipment form):
329
+ fixed the **Attention** field writing to `addressee` (rebound to `attention`); Figma layout
330
+ (inline left address labels, residential checkbox below Country, carrier section as two vertical
331
+ stacks with `justify-between`, David's verbiage trims); create-vs-edit titles clarified
332
+ ("New Shipment" vs "Edit Shipment"); added **Fulfillment Location** (read-only, sourced from the
333
+ SO — Eric); added **Reference 1/2** placeholder inputs (Ref1 = NetSuite SO#, Ref2 = NetSuite PO#,
334
+ values not wired yet — Eric); `BaseInput` per-field clear + "Clear All", Declared Cost 2-decimal
335
+ blur formatting; return-label "Edit" link inline + return-location text clickable. Recorded
336
+ decisions: create/edit entry points kept distinct (SO can have multiple shipments), Phone #
337
+ required, the `/shipments` list is per-SO (nav-link/global-view open item — Eric), the planned
338
+ **third entry mode** (Fulfill & Ship from an existing IF, label-only), David's responsiveness spec
339
+ (phase 2/3), and where the meeting transcripts live (`kb_search`). (mhammontree)
231
340
  - 2026-07-14 — TRUE-79191: added **edit-a-saved-pending-shipment-in-place** (pencil on `dtSubmitted==null` rows → `/edit-shipment`, `getShipmentForEdit`/`updateShipment` PUT existing records; item lines/serials read-only in v1; `CardTable` made reusable with `editIcon`/`isEditButtonDisabled`). Completed the Increment-3 **return-label** frontend (combined outbound+return PDF, return page captioned "Return Label" via a position-aligned `captions` CSV to `reprintLabelsApi`). Fixed the Declared Cost "$ -" column (field was never requested/mapped). Documented the return-modal edit-trap (only the return ADDRESS persists — weight/carrier/dims reuse outbound) and the per-env deploy/provisioning checklist + the "testing in prod = full prod release (real labels/NS IF)" decision. (mhammontree)
232
341
  - 2026-07-02 — Reprint rewired to the backend: `ShipmentsApi.ts` now calls `GET /v2/tracking-numbers/reprint` (`reprintLabelsApi`) for a combined base64 PDF built from the stored PNGs; removed the stale client-side `pdf-lib` merge. Return-label backend generation landed (`generateReturnLabel`, `returnTrackingNumberId` on the bridge). Ship-to address source resolved to `COALESCE(IF, SO)`. (mhammontree)
233
342
  - 2026-06-18 — PNG label-storage rework reflected on the frontend: labels now store as PNG and the PDF is built on the backend (`LabelPdf`), so the client-side `pdf-lib` reprint is stale and must be rewired to a backend generate endpoint. Fixed the Fulfilled Shipments responsiveness/clip bug (Reprint button unreachable at 100% zoom) — `overflow-scroll` → `overflow-auto` and gave `FulfilledShipmentsPage`'s outer its own scroll; documented the `/shipments` vs `/fulfilled-shipments` two-component trap and the `AuthLayout overflow-hidden` clip. (mhammontree)
@@ -21,7 +21,7 @@
21
21
  | [NetSuite Supporting-Record Webhook Importer (the reusable recipe)](features/netsuite-supporting-record-webhook-importer.md) | A single **repeatable recipe** for porting a legacy daily-pull NetSuite *supporting-record* importer (the lookup/dimension tables behind Forecast2 — Employees, | worker2/Worker/Netsuite/Employee.php, worker2/Worker/Netsuite/Account.php, worker2/Worker/Netsuite/Classification.php, worker2/Worker/Netsuite/Customer.php, worker2/Worker/Netsuite/Item.php, worker2/Worker/Netsuite.php, _underscore/Model/Forecast/Employee.php, _underscore/Model/Forecast/Account.php, _underscore/Model/Forecast/Classification.php, _underscore/Component/Forecast/Db/Db.php, test/@dave/test_employee_lifecycle.php, test/@dave/test_account_lifecycle.php, test/@dave/test_classification_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, worker/crons/toga2/forecast2/import_supporting_records.php |
22
22
  | [Background Email-Template Worker (_Worker_Notification_EmailTemplate)](features/notification-email-template.md) | `_Worker_Notification_EmailTemplate::Send(...)` dispatches a **stored, client-defined `EmailTemplates` row off-thread** as a background WorkerJob. | worker2/Worker/Notification/EmailTemplate.php, worker2/Worker/Client/True.php, _underscore/Model/Client/EmailTemplate.php |
23
23
  | [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 |
24
- | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. | worker2/Worker/Monitor/Compass.php, _underscore/Cloud.php |
24
+ | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. | worker2/Worker/Monitor/Compass.php, worker2/Worker/Client/Compass.php, worker2/composer.json, _underscore/Cloud.php |
25
25
  | [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 |
26
26
  | [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 |
27
27
  | [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 |
@@ -6,10 +6,12 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-13
9
+ updated: 2026-07-14
10
10
  owners: ["jcardinal"]
11
11
  files:
12
12
  - worker2/Worker/Monitor/Compass.php
13
+ - worker2/Worker/Client/Compass.php
14
+ - worker2/composer.json
13
15
  - _underscore/Cloud.php
14
16
  related:
15
17
  - ./monitoring-framework.md
@@ -22,10 +24,10 @@ related:
22
24
  A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from
23
25
  the 1.0 `App_SystemMonitor_Compass` monitors. A worker2 cron action runs a self-contained
24
26
  check, decides pass/fail itself, and POSTs a JSON metric body to a OneUptime "Incoming
25
- Request" monitor — replacing the 1.0 tier's email alerts. First monitor:
26
- `_Worker_Monitor_Compass::OfficeDepotEdiImportQueue()`, which watches the Office Depot
27
- inbound EDI import backlog. Designed to be **client-agnostic** — a reusable template for
28
- future monitors and clients.
27
+ Request" monitor — replacing the 1.0 tier's email alerts. The class is
28
+ `abstract class _Worker_Monitor_Compass` and now carries a **suite of 9 monitors** covering
29
+ the Compass USA / Office Depot integration (S3, DB, and mailbox checks). Designed to be
30
+ **client-agnostic** — a reusable template for future monitors and clients.
29
31
 
30
32
  This is distinct from the DB-driven email-orchestrator
31
33
  [Monitoring Framework](./monitoring-framework.md) (`_Worker_Monitor` orchestrator +
@@ -37,92 +39,159 @@ that reports straight to OneUptime. Note the folder difference: these live under
37
39
  ## Key files / entry points
38
40
 
39
41
  - `worker2/Worker/Monitor/Compass.php` — `abstract class _Worker_Monitor_Compass`; each
40
- monitor is a `public static` action method returning a summary string recorded in
41
- `WorkerJobs` (standard worker2 action conventions). First method:
42
- `OfficeDepotEdiImportQueue()`.
43
- - `_underscore/Cloud.php` — `_Cloud::getS3Objects()` list helper this monitor relies on
42
+ monitor is a `public static` action method dispatched via a `Core.CronJobs` action
43
+ `Monitor/Compass/<Method>` and returning a summary string recorded in `WorkerJobs`
44
+ (standard worker2 action conventions). `initialize()` registers the Compass client DB by
45
+ delegating to `_Worker_Client_Compass::initialize()` (not per-method).
46
+ - `worker2/Worker/Client/Compass.php` — `_Worker_Client_Compass`; owns the Compass id
47
+ constants and the `DB_CLIENT_COMPASS` connection name the monitors query against.
48
+ - `_underscore/Cloud.php` — `_Cloud::getS3Objects()` list helper the S3 monitors rely on
44
49
  (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
45
50
 
46
- ## How it works
47
-
48
- Registered as a `Core.CronJobs` entry: schedule `*/5 6-20 * * *`, action
49
- `Monitor/Compass/OfficeDepotEdiImportQueue`. Each tick, `OfficeDepotEdiImportQueue()`:
51
+ ## The 9 monitors
52
+
53
+ Each measures one Compass/Office Depot integration metric and pushes a decided alarm token.
54
+ - **OfficeDepotEdiImportQueue** (S3) — counts files >10 min old in
55
+ `s3://agilant-as2/OfficeDepot/`, excluding `OUTBOX/`, `SENT/` and directory placeholders.
56
+ - **CompassSalesOrderTransmission** (DB) — approved SOs (both approval stages) with
57
+ `c_dtTransmittedToCompass` NULL (not yet transmitted to MITS).
58
+ - **CompassIncomingPurchaseOrders** (DB) — `SA%` SOs transmitted to MITS >4h ago (on/after
59
+ the 2026-04-30 cutoff) with no PO returned.
60
+ - **CompassPurchaseOrderTransmissionsToVendors** (DB) — unsubmitted POs for monitored
61
+ vendors (Office Depot, Strategic Systems, Compass, Presidio) due today or earlier.
62
+ - **EmailedAsnImport** (mailbox) — counts inbox messages in the emailed-ASN mailbox via
63
+ IMAP XOAUTH2 (see mailbox decision below).
64
+ - **OfficeDepotPoAcknowledgements** (DB) — Office Depot POs with `dtAcknowledged` NULL
65
+ through the OD→Compass PO/SO chain.
66
+ - **OfficeDepotNetsuiteIntegration** (DB) — OD SOs not in NetSuite
67
+ (`c_dtTransmittedToNetsuite` NULL) with a 30-min PO-grace subquery.
68
+ - **OfficeDepotAsnExport** (DB) — OD orders whose ASN not sent (ItemFulfillments with
69
+ `c_dtEdi856Sent` NULL + a tracking number, on/after the 2025-02-04 cutover). Covers all
70
+ order types (SA/MR/MA) via ItemFulfillments/tracking-number logic. (The 1.0 monitor
71
+ TRUE-75101 in `library/app/systemmonitor/compass.php` was intentionally NOT ported — its
72
+ logic is superseded by this corrected definition.)
73
+ - **OfficeDepotAs2Outbox** (S3) — counts ONLY top-level files in
74
+ `s3://agilant-as2/OfficeDepot/OUTBOX/` (skips subfolders + directory placeholders).
50
75
 
51
- 1. Lists `s3://agilant-as2/OfficeDepot/` via `_Cloud::getS3Objects()`, then counts files
52
- older than `AGED_FILE_MINUTES` (10), **excluding** the `OUTBOX/` and `SENT/` sub-prefixes
53
- and directory-placeholder keys.
54
- 2. Decides the alarm state itself and POSTs a JSON metric body to the OneUptime "Incoming
55
- Request" monitor via `_ApiRequest` — with **logging disabled** and
56
- **`throwExceptionsOnFailure` disabled**, so a failed ping never fails the worker job.
57
- 3. On S3 failure it still POSTs `{"status":"error"}` so a blind/dead checker is
58
- distinguishable from a real backlog.
76
+ ## How it works
59
77
 
60
- Body tokens (see the alarm contract below): `"alarm":"HIGH"` when the aged-file count is
61
- over threshold, else `"alarm":"OK"`; `"status":"error"` on S3 failure.
78
+ Each monitor is a self-contained method: per-monitor config as `ALL_CAPS` local variables
79
+ (PHP disallows `const` at function scope), plus a `$push` closure using `_ApiRequest` — all
80
+ inside the method, so the class scales cleanly as monitors accumulate. The metric POST runs
81
+ with **logging disabled** and **`throwExceptionsOnFailure` disabled**, so a failed ping
82
+ never fails the worker job.
62
83
 
63
- ### The token-based alarm contract (critical — OneUptime cannot compare numbers)
84
+ ### Payload contract & OneUptime criteria (critical — OneUptime cannot compare numbers)
64
85
 
65
86
  OneUptime **Incoming Request** monitors **cannot** do numeric threshold comparison on a
66
- pushed body. Verified in OneUptime source (`Common/Server/.../IncomingRequestCriteria.ts`):
67
- a `checkOn: "Request Body"` filter supports only `Contains` / `NotContains` **string**
68
- matching — it does **not** JSON-parse the body, does **not** target a nested key, and does
69
- **not** support Greater Than / Less Than (those filter types apply only to
70
- outgoing/synthetic checks like Response Time / Status Code / Metric Value).
71
-
72
- Consequence: the "dumb reporter, smart monitor" ideal (push a raw number, let OneUptime
73
- compare `> threshold`) is **not achievable for push monitors**. Instead the **worker makes
74
- the threshold decision** and emits a string token that OneUptime matches with `Contains`.
75
- OneUptime criteria for this monitor:
87
+ pushed body (`checkOn: "Request Body"` supports only `Contains` / `NotContains` **string**
88
+ matching — no JSON parse, no nested-key targeting, no Greater/Less Than). So the **worker
89
+ makes the threshold decision** and emits a string token OneUptime matches with `Contains`.
76
90
 
91
+ Payload body: `{status:"reporting"|"error", alarm:"HIGH"|"OK", <metric fields>, checkedAtUtc}`.
92
+ OneUptime criteria:
77
93
  - Request Body **Contains** `"alarm":"HIGH"` → Offline + incident.
78
- - Request Body **Contains** `"status":"error"` → Offline + incident.
79
- - **Online** requires BOTH "received in 1 min" AND Request Body **Contains** `"alarm":"OK"`
80
- — so status does not flap back to Operational while the alarm is still HIGH.
94
+ - Request Body **Contains** `"status":"error"` → Offline + incident (distinguishes a
95
+ blind/dead checker from a real backlog).
96
+ - **not received in 10 min** → Degraded.
97
+ - **received in 1 min AND Contains `"alarm":"OK"`** → Online (the OK-gated recovery
98
+ prevents flapping back to Operational while the alarm is still HIGH).
99
+ - **not received in 15 min** → Offline.
81
100
 
82
101
  ### Heartbeat / cron cadence timing
83
102
 
84
- Heartbeat missed-ping thresholds must match the cron cadence. With a **5-minute** push
85
- cadence: **Degraded at 10 min / Offline at 15 min**. An initial 3/5-min setting
86
- false-alarmed on a single missed ping. The Office Depot backlog alarm threshold is
87
- **10 aged files**.
103
+ The 10-min-Degraded / 15-min-Offline heartbeat thresholds pair with a **5-minute** cron
104
+ cadence. An initial 3/5-min setting false-alarmed on a single missed ping.
88
105
 
89
106
  ## Provisioning a monitor (runbook)
90
107
 
91
- 1. Write the monitor method on `_Worker_Monitor_Compass` (or a new `_Worker_Monitor_<X>`
92
- class). Keep it **fully self-contained**: no private helpers, no class constants — all
93
- per-monitor config lives as `ALL_CAPS` local variables inside the method (PHP disallows
94
- `const` at function scope), so the class stays clean as monitors accumulate.
95
- 2. Provision the OneUptime monitor by cloning the reusable import/export template at
96
- `monitor-import-OfficeDepot-EDI-1.0.json` (a local dev artifact, not a repo file) and
97
- importing it into OneUptime; wire the Contains criteria above.
98
- 3. Register the OneUptime push URL as a local/constant in the monitor method — it is a
108
+ 1. Write the monitor method on `_Worker_Monitor_Compass` (or a new `_Worker_Monitor_<X>`),
109
+ fully self-contained per above.
110
+ 2. Set the OneUptime heartbeat URL as an in-file constant/local in the method — it is a
99
111
  **push credential**; never log it and never record its value in a doc.
100
- 4. Add the `Core.CronJobs` row (`action = 'Monitor/<Class>/<Method>'`) with the schedule,
101
- and set OneUptime's Degraded/Offline thresholds to match the cadence.
112
+ 3. Provision the OneUptime monitor by importing the monitor's OneUptime resource-export
113
+ JSON (authored under a local staging dir, e.g. `d:\STAGE` — these are import artifacts,
114
+ not repo files) and wiring the Contains criteria above.
115
+ 4. Add the `Core.CronJobs` row: `action = 'Monitor/Compass/<Method>'`, schedule
116
+ `*/5 * * * *`, `maxExecutionTime` 120s, `parameters` NULL. Then deploy worker2.
117
+
118
+ ### CronJob scheduling for these monitors
119
+
120
+ Run **all hours**, `*/5 * * * *` (every 5 min) — **not** the 1.0 `6-20` business-hours
121
+ window. Integration backlogs must be caught outside business hours too.
122
+
123
+ ## DB monitor conventions
124
+
125
+ - Query via `new _Query($sql, _Worker_Client_Compass::DB_CLIENT_COMPASS)`.
126
+ - `_Query::fetchRow()` returns an **OBJECT** (`$row->pendingCount`), not an array.
127
+ - The client DB connection is registered by the class `initialize()` hook (delegating to
128
+ `_Worker_Client_Compass::initialize()`), not per method.
129
+ - All Compass id constants live on **`_Worker_Client_Compass`** (VENDOR_ID__*,
130
+ CUSTOMER_ID__*, UUID_*, DB_CLIENT_COMPASS) — moved out of the 1.0 `App_Client_Compass`.
131
+ Never reference `App_Client_Compass` from worker2 (see gotcha below).
132
+
133
+ ## Mailbox monitoring (EmailedAsnImport) — IMAP XOAUTH2, not Graph
134
+
135
+ Use **IMAP with XOAUTH2** via the `javanile/php-imap2` library (mirrors the 1.0 monitor),
136
+ **not** Microsoft Graph. The Graph `messages/$count` approach returned HTTP 403 because the
137
+ app lacks Graph `Mail.Read` application consent and Entra consent cannot be obtained. IMAP
138
+ XOAUTH2 uses the app's existing `IMAP.AccessAsApp` permission (the 1.0 monitor already
139
+ relies on it) — no new Entra consent.
140
+
141
+ - OAuth token scope: `https://outlook.office365.com/.default`.
142
+ - Then `imap2_open('{outlook.office365.com:993/imap/ssl/novalidate-cert}INBOX', $mailbox,
143
+ $accessToken, OP_XOAUTH2|OP_READONLY)` → `imap2_num_msg`.
144
+ - Guarded by `function_exists('imap2_open')` so a missing library reports `status:error`
145
+ rather than throwing an uncatchable `Error`.
146
+ - Requires the `[mailbox_compass_statuses]` config group (`tenant_id`, `client_id`,
147
+ `client_secret`, `mailbox`) in each `Config/*.ini`, and **ext-imap enabled** on the
148
+ worker EB tier. The `client_secret` is a credential — it lives in the ini config group,
149
+ never in a doc.
150
+
151
+ ## Composer / deploy dependency
152
+
153
+ `worker2/composer.json` added `javanile/php-imap2` (`^0.1.10`) for the IMAP path. The
154
+ `composer require` refreshed `composer.lock`, bumping in-range packages (sentry, aws-sdk,
155
+ phpseclib, phpspreadsheet) and transitive majors (psr/log 1→3, symfony/options-resolver
156
+ 5.4→7.4, paragonie/constant_time_encoding 2→3). `composer audit` reported 16 advisories.
157
+ **Review the lockfile diff + `composer audit` before a production deploy.**
102
158
 
103
159
  ## Client variations
104
160
 
105
- None — the pattern is shared infrastructure. The first monitor targets Compass USA's
106
- Office Depot inbound EDI (vendor ODP), but the class and template are client-agnostic;
107
- clone the template to provision the same check for another client.
161
+ None — the pattern is shared infrastructure. This suite targets Compass USA's Office Depot
162
+ integration, but the class and templates are client-agnostic; clone to provision the same
163
+ check for another client.
108
164
 
109
165
  ## Gotchas / known issues
110
166
 
111
167
  - OneUptime Incoming Request bodies are matched **as strings only** (Contains/NotContains) —
112
168
  no numeric comparison, no JSON key targeting. Always emit a decided token, never a raw
113
169
  number, for push monitors.
114
- - The OneUptime push URL is a credential — keep it as a local/constant in the method; never
115
- log it or write its value into knowledge docs.
170
+ - The OneUptime push URL is a credential — keep it as an in-file constant in the method;
171
+ never log it or write its value into knowledge docs.
172
+ - **`App_Client_Compass` (1.0) is unavailable in worker2.** Referencing it throws a
173
+ class-not-found `Error`, which `catch (Exception)` does NOT catch — the whole monitor
174
+ dies uncaught. Use `_Worker_Client_Compass::` constants only.
175
+ - `_Query::fetchRow()` returns an object, not an array — access `$row->field`.
116
176
  - Keep the metric POST non-fatal (`throwExceptionsOnFailure` off) so a monitoring outage
117
177
  never breaks the worker job it rides in.
178
+ - The `agilant-as2` S3 bucket is in **us-west-2**, not the config-default us-east-1 — S3
179
+ monitors must pass the region (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
118
180
 
119
181
  ## Change history
182
+ - 2026-07-14 — Grew `_Worker_Monitor_Compass` to a 9-monitor suite (S3/DB/mailbox);
183
+ finalized the payload contract (`status`/`alarm`/`checkedAtUtc`) and the not-received
184
+ 10-min-Degraded / 15-min-Offline + OK-gated recovery criteria; standardized cron on
185
+ `*/5 * * * *` all-hours (120s); chose IMAP XOAUTH2 over Graph for the emailed-ASN mailbox
186
+ (Graph 403, no Entra consent); moved Compass id constants to `_Worker_Client_Compass` and
187
+ registered its DB via `initialize()`; added `javanile/php-imap2`. (jcardinal)
120
188
  - 2026-07-13 — Ported Compass monitoring from the 1.0 worker tier into worker2 reporting to
121
- OneUptime; built `_Worker_Monitor_Compass::OfficeDepotEdiImportQueue()` (Office Depot EDI
122
- backlog), the token-based alarm contract (OneUptime Incoming Request can only string-match
123
- Contains, not compare numbers), and the 5-min cadence → 10/15-min heartbeat timing. (jcardinal)
189
+ OneUptime; built `OfficeDepotEdiImportQueue()`, the token-based alarm contract, and the
190
+ 5-min cadence → 10/15-min heartbeat timing. (jcardinal)
124
191
 
125
192
  ## Related docs
126
193
  - [Monitoring Framework](./monitoring-framework.md) — the parallel DB-driven, email-alert monitoring pattern
127
194
  - [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md) — `_Cloud::getS3Objects()` used to list the EDI bucket
128
195
  - [OneUptime 1.0 worker uptime monitoring](../../../1.0/apps/worker/features/oneuptime-worker-uptime-monitoring.md) — the 1.0 push-heartbeat predecessor
196
+ </content>
197
+ </invoke>
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
17
17
 
18
18
  ## 2.0 framework
19
19
 
20
- - **_underscore** (_Underscore) _(framework core)_ — 31 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
+ - **_underscore** (_Underscore) _(framework core)_ — 32 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
21
21
  - **worker2** (Worker) — 29 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
22
22
  - **api2** (API) — 10 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
23
23
  - **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.334",
3
+ "version": "1.0.336",
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",