toga-ai 1.0.1072 → 1.0.1074

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,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-16
9
+ updated: 2026-10-18
10
10
  owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
11
11
  files:
12
12
  - worker/crons/toga2/netsuite/common_sync_togasupply.php
@@ -1868,6 +1868,11 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
1868
1868
  `c_netsuiteInternalLocationId` = <id>, then rerun." It replaced 7 `POST /locations` spots: receipt pre-check over
1869
1869
  ALL lines before any write, receipt + adjustment unit paths, TO origin + destination. A person verifies and creates
1870
1870
  the Location (client needs a Warehouse `LocationTypes` row, shared uuid `78c24f51-b33b-438b-e3f2-b6232d9ecb75`).
1871
+ - **Unknown warehouse holding the client's OWN orders → add the Location row; do not turn on
1872
+ `IS_ENABLED_SKIP_TRANSFER_ORDERS_FROM_UNKNOWN_WAREHOUSE`** (skip is for foreign locations, e.g. Elite's 5 "Main";
1873
+ it drops real orders). A cursor rewind re-reads old orders, so old parent locations surface. NYCHH 2026-10-18
1874
+ (TRUE-82859): Issue 1041, NS location 1 "New York Warehouse" (2021 TO #91091) → `Client_Nychh/2026-10-18a -
1875
+ NewYorkWarehouseLocation.sql`; self-healed, no mode reset. See [NYCHH profile](../../../../clients/nychh/profile.md).
1871
1876
  - **Unit location comes from the receipt/adjustment LINE, not `custitemnumber_location`.** That field is a bin/bay
1872
1877
  custom list whose ids overlap real location ids, so reading it as a location put units in the wrong warehouse.
1873
1878
 
@@ -2163,6 +2168,7 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
2163
2168
  [NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
2164
2169
 
2165
2170
  ## Change history
2171
+ - 2026-10-18 — NYCHH SALES_ORDERS froze on unknown warehouse NS location 1 (Issue 1041) after the 10-14 cursor rewind; fixed by adding the Location row, not the skip flag. Data only. TRUE-82859. (jcardinal)
2166
2172
  - 2026-10-16 — NYCHH SALES_ORDERS froze on a TO line removed in NetSuite (FK 1451, Issue 445); TO-line delete now clears PO/SO links first. Library only, prod-verified. TRUE-82859. (jcardinal)
2167
2173
  - 2026-10-09 — Customer PO duplicate lines: merge step (all TO clients) + SO-QTY opt-in (hh); [doc](../../library/features/customer-po-line-merge-and-quantity.md). Library + worker deploy together. TRUE-82606. (jcardinal)
2168
2174
  - 2026-10-09 — **Item-fulfillment tracking numbers now carry a carrier** (TRUE-82527). The fan-out passes the fulfillment’s shipping-method carrier into ; the number decides, the method is only a fallback, and no carrier row is ever created. (bala)
@@ -6,12 +6,13 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-13
10
- owners: [bala, ajean]
9
+ updated: 2026-10-18
10
+ owners: [bala, ajean, jcardinal]
11
11
  files:
12
12
  - _underscore/Model/Client/Logs/Record.php
13
13
  - _underscore/Model/Client/Logs/RecordField.php
14
14
  - _underscore/Model/Client/Logs/CustomRecordField.php
15
+ - api2/Component/Api/V2/V2.php
15
16
  related:
16
17
  - ../../api2/features/request-logging.md
17
18
  - ../../../1.0/apps/tools/features/compass-user-persona-admin.md
@@ -62,6 +63,8 @@ Order by `Record.timestamp` for a fixed `(recordId, primaryKeyId)` for the full
62
63
  - **A worker doing raw bulk updates is invisible here.** If a job needs auditable effects it must write its own `Record` **note** row explicitly — nothing is automatic. Real example: [Compass PEOPLE-file user lifecycle](../../../../clients/compass-usa/features/people-file-user-lifecycle.md), whose deactivation batch is a raw `UPDATE Users SET isActive = 0 WHERE id IN (...)`.
63
64
  - **⚠⚠ `_Model_Client_Logs_Record::addNote()` IS A SILENT NO-OP OUTSIDE api2.** `addNote()` only **stages** rows into the class's static `$_notes` array. The **only** thing that flushes it is api2's `Component/Api/V2/V2.php`. Nothing in `_underscore` and nothing in `worker2` flushes it. So **any worker or cron that calls `addNote()` loses the note entirely** — no error, no warning, no row. Most dangerous mode on this page: the calling code reads as if it audited itself.
64
65
  - A worker needing a note must either drain `_Model_Client_Logs_Record::$_notes` itself and INSERT into `Logs_<Tenant>.Record`, or write the `Record` rows directly. Note **`Logs_<Tenant>.Record.clientId` is `NOT NULL`**, and a worker generally has no `$api` to read the client id from — resolve from `Core` (`Clients` JOIN `Databases` on `name`). Known ids: `Client_Compass` = **2** (`Logs_Compass`), `Client_CompassCanada` = **43** (`Logs_CompassCanada`). Applies to **every** client, not just Compass.
66
+ - **⚠ In api2, `addNote()` must come AFTER any `internalRequest` in the same hook.** The internal request's record-log pass clears every note queued before it. Also, a write done through an internal request on **another** model is logged on that model's row only — not on the parent's — so add a note on the parent if its timeline / `_updated` must show it. Reference: [write intent on a PUT](../../api2/features/api-payload-interceptors.md#audited-action-as-a-table-write-the-action-table-pattern).
67
+ - **⚠ `_created` on a record = the EARLIEST `Record` log row** (`V2.php` `loadLogRecordBatch`, `MIN(id)`), not the row's insert. A row created outside the API (SQL seed, migration) has no create log, so `_created` shows **whoever first wrote it through the API**. Use the table's stored `dtCreated` for "when created".
65
68
  - **`Record.userId` is a TENANT `Users.id` — but it is NULLABLE, so a staff-actor row IS possible with no schema change** (verified 2026-09-04, correcting the earlier claim that internal-tool writes had no audit home). You cannot express "a `Client_True` staff member did this" in `userId`, so put the staff identity in **`note`**:
66
69
  - `recordId` = the `Core.Records` id of the model (**`Users` = 2**), `primaryKeyId` = the target row's id, `clientId` = the tenant's `Core.Clients` id, `userId` = **NULL**, `note` = who did it and what changed.
67
70
  - `_Model_Compass_ApprovalDecision` (`_underscore/Model/Compass/ApprovalDecision.php:1344-1366`) already hand-writes exactly this shape — an established pattern.
@@ -75,6 +78,7 @@ Order by `Record.timestamp` for a fixed `(recordId, primaryKeyId)` for the full
75
78
  - **Do not paste logged values into tickets or the KB.** `oldValue`/`newValue` and note text routinely contain user emails and other personal data. Reference rows by location; quote the mechanism, not the people.
76
79
 
77
80
  ## Change history
81
+ - 2026-10-18 — `_created` = earliest log row (seeded rows show first API writer); `addNote()` after `internalRequest`; internal writes log on their own model only (TRUE-82876). (jcardinal)
78
82
  - 2026-10-13 — Added PurchaseOrders / PurchaseOrderItems ids and this log as the surviving source once the Api log ages out. (bala)
79
83
 
80
84
  ## Related
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-17
9
+ updated: 2026-10-18
10
10
  owners: ["mhammontree", "dfranks", "bala", "snaredla", "jcardinal", "rgirish"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -28,6 +28,8 @@ files:
28
28
  - _underscore/Model/Client/UserGlobalSetting.php
29
29
  - dbchanges2/Core/2026-10-09a - UserGlobalSettingsInterceptors.sql
30
30
  - _underscore/Model/Client/CaseRecordRemoval.php
31
+ - _underscore/Model/Client/Workflow.php
32
+ - _underscore/Model/Client/WorkflowVersion.php
31
33
  related:
32
34
  - ./cross-client-data-retrieval.md
33
35
  - ../../_underscore/features/record-activity-timeline.md
@@ -289,6 +291,20 @@ Reference: `_Model_Client_CaseRecordRemoval` (TRUE-82889, CTO-reviewed).
289
291
  `postPut` **before** the stage gate, and the gate counts only rows that exist after the write —
290
292
  never trust a payload flag that says "I will create it".
291
293
 
294
+ **Write intent that writes ANOTHER record under the caller's ACL** (TRUE-82876). Reference:
295
+ `PUT /workflows/{uuid} {"status":"ACTIVE","publishDraft":true}` (`_Model_Client_Workflow`):
296
+ 1. PRE PUT strips the intent key, locks, runs the checks (e.g. overlap) first.
297
+ 2. The second record's write goes through
298
+ `_Model_Client_Ticket::runInsideRequestContext($api, fn() => _Model_Client_Ticket::internalRequest(...))`
299
+ — here `PUT /workflow-versions/{draft} {isPublished:1}` — so api2 checks the caller's ACL and that
300
+ route's `prePut`. Never write the other model directly.
301
+ 3. Any refusal throws → `MODEL_SAVE_ERROR` → 400 → `transactionRollback` of `DB_CLIENT`
302
+ (`V2.php` ~6844-6906, ~2304-2310): the whole PUT is all-or-nothing.
303
+ 4. Audit: the internal write is **not** logged on the parent's row. Call
304
+ `_Model_Client_Logs_Record::addNote(...)` **after** the internal request — its record-log pass
305
+ clears every note queued before it (see
306
+ [record change audit log](../../_underscore/features/record-change-audit-log.md#gotchas)).
307
+
292
308
  ### Own-row guards in a PRE interceptor
293
309
 
294
310
  A PRE hook that limits a user to their own rows must close four bypasses — each found by review on the `user-global-settings` guard (TRUE-82613):
@@ -358,4 +374,5 @@ Refuse API-key callers when the rule is "the signed-in user" (no user on the tok
358
374
  - 2026-10-09 — Own-row guards in PRE hooks (alias keys + parent-route bypasses, PRE before field ACL); user-global-settings reference; known open holes. (jcardinal)
359
375
  - 2026-10-15 — PRE → POST state hand-off via `$api->passThrough`; detect a real field change, not a resent one (TRUE-82727). (bala)
360
376
  - 2026-10-16 — Command keys: strip in `prePut`, park on `passThrough`, act in `postPut`; empty body → `{}` (TRUE-82849). (jcardinal)
377
+ - 2026-10-18 — Write intent that writes another record via internal request (caller ACL, rollback, `addNote` after the call) (TRUE-82876). (jcardinal)
361
378
  - 2026-10-17 — "Action table" pattern for audited actions; write intents before a stage gate; new interceptor rows ship in a file run after the code deploy (TRUE-82889). (jcardinal)
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-17
9
+ updated: 2026-10-18
10
10
  owners: [tcox, bala, apeterson, jcardinal, rgirish]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -69,6 +69,28 @@ controller.
69
69
  the join is genuinely 1:1, or the duplicates agree. Worked example:
70
70
  [category tile sort order](../../toga2-commerce/features/category-tile-sort-order.md).
71
71
 
72
+ ### Grouped / aggregate queries (`group=` + an `_`-prefixed aggregate field)
73
+
74
+ Each output row = one bucket: the group key(s) + the aggregate values.
75
+
76
+ - **Stored ROOT column key** (`group=status`): selected as `Table.field AS field` in aggregate mode
77
+ only and copied into the row (`V2.php` ~L5236 map, ~L5468 select, ~L5739 copy). Before TRUE-82876
78
+ it went into `GROUP BY` but never into the SELECT, so buckets had counts and **no key** (UI showed
79
+ "NONE"). Flat (non-aggregate) queries are unchanged.
80
+ - **The key is ACL-gated:** it is returned only if `resolveColumnReference([... 'isValueReturned'
81
+ => true])` passes, so a group column the caller cannot READ is never returned, in any
82
+ `identifier_acl_mode`.
83
+ - **Computed `FIELD_SQL` and joined-path group fields** already return their key.
84
+ - **Cross-client** `mergeGroupRows()` merges buckets by the key value — it needs the key present
85
+ (see [cross-client retrieval](cross-client-data-retrieval.md)).
86
+
87
+ **Known gaps (open, not fixed — 2026-10-18):**
88
+ - A **joined `Table.field`** group key is still not selected.
89
+ - `FOUND_ROWS` / the total count is wrong for grouped queries — do not page a grouped result by it.
90
+ - Grouped queries still add an `ORDER BY uuid` tie-breaker, which a strict `ONLY_FULL_GROUP_BY`
91
+ server rejects. Prod `sql_mode` is unverified; local MySQL accepts it (see
92
+ [local vs prod MySQL config](../../dbchanges2/workflows/local-vs-prod-mysql-config-parity.md)).
93
+
72
94
  ### ⚠ `depth` and `calcDepth` are IGNORED inside the base64 `_` options blob
73
95
 
74
96
  `fields`, `where`, `sort`, `join` work inside the `_` blob. **`depth` and `calcDepth` do not** —
@@ -628,6 +650,7 @@ If you need the behaviour, loop uuids client-side. Point 3 is the durable, non-o
628
650
  [V2 request logging](request-logging.md).
629
651
 
630
652
  ## Change history
653
+ - 2026-10-18 — Grouped queries: stored root group key now selected + ACL-gated; known gaps (joined key, total count, `ORDER BY uuid` under ONLY_FULL_GROUP_BY) (TRUE-82876). (jcardinal)
631
654
  - 2026-10-17 — Child-path `where` must start at an inherent child; FK-first path → TableView join alias (TRUE-82872). (jcardinal)
632
655
  - 2026-10-17 — Multi-sort comma list + `uuid` tie-breaker for stable paging; sort on calc works; refreshed where-path line refs. (jcardinal)
633
656
  - 2026-10-17 — POST create = 201, PUT/DELETE = 200 (TRUE-82889). (jcardinal)
@@ -19,9 +19,10 @@ project: _Underscore
19
19
  client: nychh
20
20
  type: profile
21
21
  status: active
22
- updated: 2026-10-16
22
+ updated: 2026-10-18
23
23
  owners: [jcardinal, apeterson, bala, akhokhani, mhammontree]
24
24
  files:
25
+ - dbchanges2/Client_Nychh/2026-10-18a - NewYorkWarehouseLocation.sql
25
26
  - dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
26
27
  - dbchanges2/Client_Nychh/2026-08-31a - PurchaseOrdersTableViewRebase.sql
27
28
  - dbchanges2/Client_Nychh/2026-09-02a - PurchaseOrderItemsTableViewRebase.sql
@@ -71,6 +72,7 @@ TOGA 2.0 client on `_underscore`, prod schema `Client_Nychh`. Supply-chain / ful
71
72
  - **Worker tiers:** `worker2` (2.0) runs the NYCHH asset-tag backfill; `worker`/`library` (1.0) run the NetSuite→TOGa Supply sync and the asset-tag verification/backfill diagnostics.
72
73
 
73
74
  ## Key features (this client)
75
+ - **SALES_ORDERS froze `1-RUNNING` again 2026-10-15 → 10-18 (Issue 1041) — fixed by data + prod-verified 2026-10-18 (TRUE-82859).** After the 10-14 integration restart rewound cursors, TO #91091 (NS 2143621, 2021) shipped from NS location 1 "New York Warehouse" (parent of 117 H&H, 2 DOE, 15 Prudential) → `throwIfUnknownWarehouse`. Fix: `dbchanges2/Client_Nychh/2026-10-18a - NewYorkWarehouseLocation.sql` (Location 86, Warehouse, `c_netsuiteInternalLocationId` 1). Warehouse map now 1, 2, 4, 6, 9, 15, 117, 173. Do NOT enable `IS_ENABLED_SKIP_TRANSFER_ORDERS_FROM_UNKNOWN_WAREHOUSE` here — location 1 holds 5 real H&H orders (2021-22). OPEN (undecided): Location 82 "Naperville Config" maps NS 2 = "New York Warehouse : DOE" — name mismatch, not blocking.
74
76
  - **SALES_ORDERS froze `1-RUNNING` 2026-10-15 → 10-16 (Issue 445) — fixed + prod-verified 2026-10-16 (library `f351465a`, TRUE-82859).** Stocking SO 7342575 (TO 285145) had 3 lines removed in NetSuite; their PO links blocked the line delete (FK 1451). Cursor now past `7342575`. See [per-client sync](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
75
77
  - **ITEM_FULFILLMENTS froze `1-RUNNING` 2026-09-30 → 2026-10-06 (Event issue 483) — fixed + prod-verified 2026-10-06 (library).** NS order 4512137 (#165198, stocking) existed twice: stale TransferOrder 508 (pre-2026-09-03 stocking rule) + SalesOrder 168. IF reconcile tried to delete TO line 7409 that an IA line linked to → FK 1451. New reverse retire (TO→SO) + IA-link move before IF-line deletes; TO 508 retired, cursor past `4605943`. Remaining doubles: #233366 (SO 172 / TO 1811 — retires when its IF 2392 syncs), #290752 (SO 167 / TO 1958, no IFs — waits for SALES_ORDERS feed). See [per-client sync](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
76
78
  - **Transfer-order "Order Shipped" email fires on the first tracking number, not the NetSuite shipped stage (2026-10-05, prod).** Warehouse marks shipped after delivery. NYCHH is the only client emailing off the NS shipped stage. See [TO notification emails](./features/transfer-order-notification-emails.md). Alarm when one stays owed > 24h: [emails owed monitor](./features/oneuptime-transfer-order-emails-owed-monitor.md) (TRUE-82758, not deployed 2026-10-16).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1072",
3
+ "version": "1.0.1074",
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",
@@ -73,6 +73,17 @@ rows for the developer to write by hand.
73
73
  `Core.Records` / `Core.RecordFields` rows, the `/finish` report always lists the ids used
74
74
  and the next free ids.
75
75
 
76
+ ### Local work has no "deploy" step (team rule, 2026-10-18, jcardinal, TRUE-82876)
77
+
78
+ Applies to every repo. In a local environment, code is live as soon as each repo is on the
79
+ right branch. "Deploy" only applies to non-local environments. api2 is a PHP app — there is
80
+ no "reload api2" or "restart api2" anywhere in our infrastructure.
81
+
82
+ 1. Run steps for a developer's **local** work never say "deploy `_underscore`", "reload api2"
83
+ or "restart api2".
84
+ 2. If order matters, say "make sure `<repo>` is on branch `<TICKET>`" instead.
85
+ 3. Real deploy steps for non-local environments stay as they are.
86
+
76
87
  ## No running commentary — one compiled report at the end (team rule, 2026-10-15)
77
88
 
78
89
  Developers do other work and come back when Claude asks something. So work silently, then