toga-ai 1.0.1073 → 1.0.1075

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)
@@ -4,6 +4,7 @@
4
4
  |-----|---------|
5
5
  | [Proposed — git-sourced base+overlay JSON authoring for the Surface layer](architecture/surface-authoring-proposal.md) | Draft proposal (NOT implemented) to move Surface authoring off hand-written SQL onto git-sourced base+overlay JSON; open only for that design rationale. |
6
6
  | [_underscore Framework Architecture](architecture.md) | The 2.0 `_underscore` PHP backend framework — DB abstraction, ORM models, routing, ACL, Surface, multi-tenant clusters; open for how any 2.0 app backend works. |
7
+ | [ACL Capabilities (roles = collections of capabilities; dual-read transition; role delete)](features/acl-capabilities.md) | Client-DB ACL layer between roles and permissions (Capabilities, Roles_Capabilities, capabilityId on ACL rows); open before writing any client ACL grant, readin |
7
8
  | [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 |
8
9
  | [Address Uniqueness Normalization (unit identifier + 5-digit ZIP comparison)](features/address-uniqueness-normalization.md) | Two reusable normalization primitives for any "one X per physical address" rule; open when comparing addresses for uniqueness. |
9
10
  | [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | US address validation via a carrier waterfall (USPS → FedEx → UPS) exposed as a scripted endpoint; open when validating/normalizing addresses. |
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: ACL Capabilities (roles = collections of capabilities; dual-read transition; role delete)
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-18
10
+ owners: [jcardinal]
11
+ files:
12
+ - _underscore/Model/Client/Capability.php
13
+ - _underscore/Model/Client/Roles/Capability.php
14
+ - _underscore/Model/Client/Role.php
15
+ - _underscore/Model/Client/AclRecordPermission.php
16
+ - _underscore/Model/Client/AclFieldPermission.php
17
+ - _underscore/Model/Client/AclCustomFieldPermission.php
18
+ - api2/Component/Api/V2/V2.php
19
+ - dbchanges2/Client/2026-10-18b - TRUE-82880 Capabilities.sql
20
+ - dbchanges2/Core/2026-10-18e - TRUE-82880 CapabilitiesCore.sql
21
+ related:
22
+ - acl-permission-chain.md
23
+ - record-hard-delete.md
24
+ - ../../api2/features/api-key-authentication-and-scoping.md
25
+ ---
26
+ Client-DB ACL layer between roles and permissions (Capabilities, Roles_Capabilities, capabilityId on ACL rows); open before writing any client ACL grant, reading ACL rows in SQL, deleting a role, or dropping `roleId`.
27
+
28
+ ## Summary
29
+
30
+ A **Capability** (`Client_*.Capabilities`, Core.Records **409**) is a per-client named set of
31
+ `AclRecordPermissions` (+ their logic groups), `AclFieldPermissions` and `AclCustomFieldPermissions`
32
+ rows, linked by a new `capabilityId` column. A **Role** is a set of capabilities
33
+ (`Roles_Capabilities`, Records **410**). Access = **union** of all capabilities of all the user's
34
+ roles. No per-role or per-user overrides. Authoring UI = Hub (later). Built TRUE-82880.
35
+
36
+ **Not capability-keyed (stay role-keyed):** `AclActionPermissions`, `AclRecordScripts`,
37
+ `AclCustomRecordScripts`, `Users_Roles`, `Apis_Roles`. **Core DB ACL stays role-based** — capabilities
38
+ exist only for `aclDatabase = 'CLIENT'` records.
39
+
40
+ ## How it works
41
+
42
+ - **JWT unchanged** — it still carries role ids. Per request, `V2::buildLookups()` expands
43
+ role → capability ids (`buildClientRoleIdsByCapabilityId`).
44
+ - **Dual-read (transition):** client ACL rows load with
45
+ `capabilityId IN (<caps>) OR (capabilityId IS NULL AND roleId IN (<roles>))`
46
+ (`buildClientAclDualReadCondition`). So legacy role-only rows still work.
47
+ - **Lookup buckets unchanged:** `[db][roleId][recordId]`, keyed by permission id, so a row reached
48
+ through two roles is not counted twice. `resolveClientAclRowRoleIds` maps a capability row back to
49
+ every role holding that capability.
50
+ - **Field permissions merge writable-wins:** same field from two capabilities → writable if any says writable.
51
+ - **`AclFieldPermissions.appId`:** NULL = all apps; otherwise must equal the request's app.
52
+ - **Migration** (`Client/2026-10-18b`, dynamic, re-runnable): one capability per role, named after
53
+ the role, linked only to that role; existing ACL rows get its `capabilityId`. `roleId` stays filled
54
+ and becomes nullable.
55
+ - **`roleId` drop is later:** its own forward `dbchanges2` file, after a release. Do the gotchas
56
+ below first.
57
+
58
+ ### Role delete (`_Model_Client_Role::preDelete`, PRE DELETE interceptor on record 274)
59
+
60
+ - Gated only by `allowDelete` on `roles` ACL. Hard delete. **No special system-role logic.**
61
+ - **Blocked (400)** while any `Users_Roles` or `Apis_Roles` row holds the role.
62
+ - Uses [record hard delete](record-hard-delete.md) `relatedRows()`: deletes `Roles_Capabilities`
63
+ links and role-keyed rows (`AclActionPermissions`, `AclRecordScripts`, `AclCustomRecordScripts`,
64
+ `Announcements_Roles`, `ApprovalTemplateStageAllowedRoles`, `SurfaceOverrides`).
65
+ - Then `removeAclRows()`: legacy rows (`capabilityId IS NULL`) are deleted (record permissions with
66
+ their logic groups first); capability rows are kept with `roleId = NULL`. **Capabilities are never
67
+ deleted with a role.**
68
+
69
+ ## Gotchas
70
+
71
+ - **⚠ Before the `roleId` drop: worker2 `Worker/Sync/TrackingNumbers.php` (~L460) still joins
72
+ `AclRecordPermissions` / `AclFieldPermissions` on `roleId`.** Move it to the capability path first.
73
+ Grep every repo for raw-SQL reads of `roleId` on these three tables too.
74
+ - **⚠ New unique keys with NULL `appId` / `indirectRecordId` do not block duplicates** (MySQL treats
75
+ NULLs as distinct). Today the old role-based keys still protect; after the `roleId` drop they do
76
+ not — guard inserts with `NOT EXISTS`.
77
+ - **Writers (Hub, migrations) must write `roleId = NULL` on new capability rows.** A filled `roleId`
78
+ makes the row look legacy-shaped and keeps it alive on the old key.
79
+ - **⚠ Open (security, pre-existing):** the client `Base` role (every user holds it) has full CRUD on
80
+ `user-roles`, `roles`, `acl-field-permissions`, `acl-custom-field-permissions` — a user can grant
81
+ themselves more access. Developer decision: fix when Hub is built.
82
+
83
+ ## Change history
84
+ - 2026-10-18 — Created: Capabilities layer, dual-read, writable-wins field merge, role delete rules (TRUE-82880). (jcardinal)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-06
9
+ updated: 2026-10-18
10
10
  owners: ["jcardinal", "mhammontree", "tcox", "bala", "apeterson", "rgirish"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -28,6 +28,11 @@ inserting only an `AclRecordPermissions` row is the single most common mistake
28
28
  silently does nothing without the rest of the chain. A complete grant requires **all four** tables,
29
29
  plus `AclFieldPermissions` for the fields to be readable/writable.
30
30
 
31
+ **Client-DB ACL rows now belong to a Capability** (`capabilityId`), and roles are sets of
32
+ capabilities; legacy `roleId`-only rows still work during the transition (dual-read). Read
33
+ [ACL Capabilities](acl-capabilities.md) before writing or querying client ACL rows. Core DB ACL is
34
+ still role-based.
35
+
31
36
  ## The complete chain (all four are required)
32
37
 
33
38
  ```
@@ -412,7 +417,8 @@ first (see
412
417
  - **Two separate role spaces with different ids and names:** Core roles (`Core.Roles`: Public, Integrations, Super User, Base) and per-tenant client roles (`Client_<X>.Roles`). Ids are not comparable across spaces.
413
418
  - `AclRecordPermissions.roleId` / `AclFieldPermissions.roleId` are read in the record's `aclDatabase` role space: CORE record → Core role id; CLIENT record → client role id. `roleId=1` is Core Public on a Core record but a client role on a client record — always filter by the record's `aclDatabase`.
414
419
  - **Link:** `Client.Roles.coreRoleId`. At login the user's client roles map to Core roles through their distinct non-null `coreRoleId`s, set as `id.core.roles`. CORE records filter on the user's Core roles; CLIENT records on their client roles.
415
- - **No inheritance:** no parent role / hierarchy; roles are used verbatim by the resolver.
420
+ - **No inheritance:** no parent role / hierarchy; roles are used verbatim by the resolver. Client roles
421
+ reach client ACL rows through their capabilities (union) — see [ACL Capabilities](acl-capabilities.md).
416
422
  - **Public = read-only baseline every authenticated user inherits.** Public is MERGED (additively) into the effective core roles at token build, `processRoutePairs`, and `buildLookups`; without the merge the mapped `coreRoleId`s overwrite it and a role like Core Base cannot read Core metadata (e.g. `Core.Clients.uuid`). Keep Public read-only: never grant it write on Core metadata.
417
423
  - Related: no backend ACL cache in 2.0 — a stale 403 is the UI cache or ACL logic, never a backend cache.
418
424
 
@@ -925,11 +931,13 @@ hardcoded `Core.RecordFields` id literals instead of a subselect.
925
931
  NULL plus `sqlExpression='1'` means no row scoping whatsoever (see the section above).
926
932
  - [ ] **No business-critical column is `isWritable = 1` for `Base`** unless customers are genuinely
927
933
  meant to write it via plain CRUD.
928
- - [ ] For `aclDatabase = 'CLIENT'` records: rows go in client DB(s); resolve `roleId` by subselect.
934
+ - [ ] For `aclDatabase = 'CLIENT'` records: rows go in client DB(s); resolve `roleId` by subselect;
935
+ set `capabilityId` (see [ACL Capabilities](acl-capabilities.md)).
929
936
  - [ ] The field is declared on the **generated** `_underscore/Model/Client/<Name>.php` (else `EO-1`),
930
937
  and every repo is on the **same branch** so the generated model matches the DB.
931
938
 
932
939
  ## Change history
940
+ - 2026-10-18 — Client ACL rows now carry `capabilityId`; linked the new Capabilities doc (TRUE-82880). (jcardinal)
933
941
  - 2026-09-21 — Named the mechanism behind the half-built chain: `_Model_Core_AclRecordPermission::buildSqlExpression`
934
942
  returns **`'(0)'`** with no logic group, so `allowRead = 1` alone denies everything (second symptom: 403 listing
935
943
  `aclRecordPermissionIds` with `[null, null, null]`). Added that a `CLIENT` record's grant must exist in every
@@ -6,7 +6,7 @@ project: _Underscore
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: [jcardinal]
11
11
  files:
12
12
  - _underscore/Component/RecordDelete/RecordDelete.php
@@ -53,6 +53,8 @@ Tickets, Cases, ServiceRequests, Customers, Contacts. The class DocBlock is the
53
53
  ContactNotes, ContactAttempts, ContactMessagingOptIns, Campaigns_Contacts, Campaigns_MeetWithContacts,
54
54
  Contacts_Tags, Contacts_Vendors, Contacts_Locations, Locations_Contacts, ContactAddresses / EmailAddresses /
55
55
  PhoneNumbers. `preDelete` clears the contact's `primary*` pointers first (they and the children point at each other).
56
+ - **Role rule (Records 274):** blocked by Users_Roles / Apis_Roles; ACL rows handled after `purge()` — full rule in
57
+ [ACL Capabilities § Role delete](acl-capabilities.md).
56
58
 
57
59
  ## Gotchas
58
60
 
@@ -71,5 +73,6 @@ Tickets, Cases, ServiceRequests, Customers, Contacts. The class DocBlock is the
71
73
  losing the link would lose meaning (orders, required FKs like `Contacts.customerId`).
72
74
 
73
75
  ## Change history
76
+ - 2026-10-18 — Roles opted in (TRUE-82880). (jcardinal)
74
77
  - 2026-10-17 — Contacts opted in (block / detach / cascade map, schema-drift probes); Contacts_Files added to FILE_REFERENCE_TABLES (TRUE-82871). (jcardinal)
75
78
  - 2026-10-17 — Created; Customers opted in, Customers_Files added to FILE_REFERENCE_TABLES (TRUE-82870). (jcardinal)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-15
9
+ updated: 2026-10-18
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - _underscore/Model/Core/Record.php
@@ -22,15 +22,22 @@ Per-route record meta (names, label/sublabel field, per-field display labels); o
22
22
  ## Summary
23
23
 
24
24
  `GET /v2/records/meta?route=<route>` → `_Model_Core_Record::meta()`. Returns
25
- `{route, name, pluralName, labelField, sublabelField, identifierField, fieldLabels}`.
25
+ `{route, name, pluralName, icon, iconWeight, labelField, sublabelField, identifierField, fieldLabels}`.
26
+ Batch: `?routes=a,b,c` (max `META_BATCH_MAX_ROUTES` = 400; callers chunk) →
27
+ `{records: {<route>: {route, name, pluralName, icon, iconWeight}}}` — light shape, no field labels.
28
+ Record-type icons come only from here (`Core.Records.iconToken` / `iconWeightToken`); apps never
29
+ hardcode them (rule: dbchanges2 architecture → *`Core.Records.name` + icons*).
26
30
  `fieldLabels` = `{<field>: "<label>"}` — the data-driven visible label of each field, in the session
27
31
  language. Platform rule: **a field's visible label comes from `Core.RecordFields.labelMessageId` via
28
32
  `fieldLabels`, never hardcoded** (platform apps' `PLATFORM.md` rule 15).
29
33
 
30
34
  ## How it works
31
35
 
32
- - **Names:** `resolveNames()` → `name` / `pluralName` from Messages via
33
- `_Model_Core_Surface::resolveMessageValues()`.
36
+ - **Names:** `name` / `pluralName` from Messages via `_Model_Core_Surface::resolveMessageValues()`;
37
+ fallback order: Message → frozen `DefaultRecordSettings` → `Core.Records.name`.
38
+ - **Batch (`metaForRoutes`):** one Records query, one Messages lookup, one `DefaultRecordSettings` query for all routes.
39
+ - **Response path:** record-script nesting adds the payload key — batch is read at
40
+ `data.records.meta.records.<route>` (see [record scripts](../../api2/features/record-scripts.md)).
34
41
  - **`fieldLabels`:** `resolveFieldLabels()`:
35
42
  1. Caller must have record READ (`aclRecordFlags`), else `{}`.
36
43
  2. Only fields the caller may READ (`$api->getAclFieldPermissions`, same resolver as
@@ -47,11 +54,12 @@ language. Platform rule: **a field's visible label comes from `Core.RecordFields
47
54
  ## Gotchas
48
55
 
49
56
  - **⚠ Open (security): the base meta has NO record READ check.** `route`, `name`, `pluralName`,
50
- `labelField`, `sublabelField` come back for any route to any caller (pre-existing); only
51
- `fieldLabels` is gated. A caller can probe which routes exist (unknown route → 400
57
+ `labelField`, `sublabelField`, `icon` come back for any route to any caller (pre-existing; the
58
+ batch form too); only `fieldLabels` is gated. A caller can probe which routes exist (unknown route → 400
52
59
  `No record found for route`). Not fixed yet — Desk question `lx`.
53
60
  - `fieldLabels` is **per caller** (field ACL + language) — never cache it across users or tenants.
54
61
  The rest of the meta is the same for every tenant.
55
62
 
56
63
  ## Change history
64
+ - 2026-10-18 — Added `icon` / `iconWeight`, name fallback to `Core.Records.name`, and batch `?routes=` (max 400) (TRUE-82880). (jcardinal)
57
65
  - 2026-10-15 — Added `fieldLabels` (gated by record + field READ) and blox `fieldLabelOf`; noted the ungated base meta (TRUE-82836). (jcardinal)
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-10-17
9
+ updated: 2026-10-18
10
10
  owners: [jcardinal, mhammontree, bala, ajean]
11
11
  files:
12
12
  - Core/
@@ -141,6 +141,19 @@ These **two tables, and only these two**, have **team-maintained primary keys**.
141
141
 
142
142
  This is what makes the *Database isolation* rule practical — it is the reason a `Client_<Tenant>` migration never needs to read `Core` to find a `recordFieldId`.
143
143
 
144
+ ### `Core.Records.name` + icons (team rule 2026-10-18, TRUE-82880)
145
+
146
+ - `name` is REQUIRED (NOT NULL), written for a person: Title Case, PLURAL, built from the route. `sales-orders` → "Sales Orders". Acronyms stay upper case: "ACL Record Permissions".
147
+ - Bridge record = BOTH sides are real tables/records. Both sides plural: "Locations <-> Location Attributes", "Users <-> Roles", "Users <-> Files".
148
+ - Child table whose second part is not its own table is NOT a bridge: one plural name ("Location Hours", not "Location <-> Hours"); table `LocationHours`, not `Locations_Hours`.
149
+ - Typed variants: type first: `support-methods-chat` → "Chat Support Methods".
150
+ - Product / feature names keep their form ("Meet With Contacts"). Uncountable words stay singular ("User Memory", "Conversation History", "Item Media"). Acronyms upper case ("AI Models").
151
+ - Routes match the table words: `service-request-note-types`, not `service-request-notes-types`.
152
+ - Change history: 2026-10-18 TRUE-82880 refined bridge/child/typed/uncountable rules.
153
+ - Every new `Core.Records` insert sets `name` this way.
154
+ - Two layers: `Core.Records.name` = base name, the fallback everywhere; `nameMessageId` / `pluralNameMessageId` Messages = translatable UI text, wins when set.
155
+ - Record-type icons live in `Core.Records.iconToken` (+ `iconWeightToken`): one icon per record platform-wide; the nav icon is the source when choosing. Apps read it from the records meta, never hardcode record icons. Stored as camelCase Font Awesome names without the `fa-` prefix (blox `toFaClass` normalizes).
156
+
144
157
  ### Inserting into them — ASK FIRST, never rely on AUTO_INCREMENT
145
158
 
146
159
  **When a change inserts into `Core.Records` or `Core.RecordFields`, stop and ask the developer for the next `id` value(s)**, then write them as explicit literals:
@@ -400,6 +413,7 @@ The extra `SELECT … FROM ( … ) existing` layer both dodges error 1093 and pi
400
413
 
401
414
  ## Change history
402
415
 
416
+ - 2026-10-18 — Core.Records.name required + human naming; record icons in Core.Records (TRUE-82880). (jcardinal)
403
417
  - 2026-10-17 — /finish reports central ids used + next free (TRUE-82889). (jcardinal)
404
418
  - 2026-10-17 — Second same-folder file also allowed for interceptor rows that must run after the code deploy (TRUE-82889). (jcardinal)
405
419
  - 2026-10-13 — Added rule 15: no MySQL `CHECK` constraints (TRUE-82715). (jcardinal)
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-10-06
8
+ updated: 2026-10-18
9
9
  owners: [jcardinal, ajean, apeterson, mhammontree]
10
10
  files: []
11
11
  related:
@@ -171,6 +171,10 @@ literal rather than a lookup — including in SQL embedded in PHP in `worker2` /
171
171
  `_underscore`. Always add a comment naming the record/field the number refers to; a bare `4188` is
172
172
  unverifiable later.
173
173
 
174
+ **Naming + icons.** `Core.Records.name` is required: Title Case, plural, from the route
175
+ ("Sales Orders"; bridge records, both sides plural: "Users <-> Roles"; child tables are not bridges: "Location Hours"). Record icons live in `Core.Records.iconToken`
176
+ (+ `iconWeightToken`), never hardcoded in apps. Rule + examples: dbchanges2 architecture, same section.
177
+
174
178
  This is also what makes the `dbchanges2` isolation rule above workable: a `Client_<Tenant>` change
175
179
  never has to read `Core` to resolve a `recordFieldId`. Full detail and examples:
176
180
  `2.0/apps/dbchanges2/architecture.md` → *`Core.Records` / `Core.RecordFields`*.
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
21
21
 
22
22
  ## 2.0 framework
23
23
 
24
- - **_underscore** (_Underscore) _(framework core)_ — 104 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
24
+ - **_underscore** (_Underscore) _(framework core)_ — 105 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
25
25
  - **worker2** (Worker) — 90 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
26
26
  - **api2** (API) — 27 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
27
27
  - **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
@@ -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).
@@ -17,11 +17,12 @@ apps:
17
17
  - togadesk
18
18
  - toga25-desk
19
19
  - toga25-blox
20
+ - toga25-iq
20
21
  project: _Underscore
21
22
  client: true
22
23
  type: profile
23
24
  status: active
24
- updated: 2026-10-16
25
+ updated: 2026-10-18
25
26
  owners: [jcardinal, kyalamarthi, tcox, rgirish, mhammontree, akhokhani]
26
27
  files: []
27
28
  related:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1073",
3
+ "version": "1.0.1075",
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