toga-ai 1.0.259 → 1.0.260

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.
@@ -14,6 +14,6 @@
14
14
  | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
15
15
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
16
16
  | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
17
- | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
17
+ | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, _underscore/Model/Client/AclRecordScript.php, _underscore/Model/Core/RecordScript.php, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Quad/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql, dbchanges2/Client_CompassCanada/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
18
18
  | [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | api2/Component/Api/V2/V2.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Client_Prudential/2026-06-15 - ItemFulfillmentTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Quad/2026-06-18a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19b - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql, dbchanges2/Client_Quad/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
19
19
  | [Units for Items for Purchase Orders — Data Structure](features/units-for-items-for-purchase-orders.md) | Describes how unit (serialized inventory) data is linked to sales-order and purchase-order line items behind the `units-for-items-for-purchase-orders` TableView | |
@@ -6,12 +6,16 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-30
10
- owners: [jcardinal]
9
+ updated: 2026-07-01
10
+ owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - _underscore/Model/Core/Surface.php
13
+ - _underscore/Model/Client/AclRecordScript.php
14
+ - _underscore/Model/Core/RecordScript.php
13
15
  - dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql
14
16
  - dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql
17
+ - dbchanges2/Client_Quad/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql
18
+ - dbchanges2/Client_CompassCanada/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql
15
19
  - dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
16
20
  - dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql
17
21
  - dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql
@@ -113,6 +117,32 @@ which surfaced latent resolution gaps in `_buildBundle`/`_resolveElement`:
113
117
  `_Model_<Client>_*` overrides + `Client_ApiPayloadInterceptor` hooks — the Surface layer does
114
118
  **not** absorb business logic.
115
119
 
120
+ ## How a surfaces scripted-API call is authorized (route-level gate)
121
+
122
+ Authorizing a surfaces script (`meta`, `meta-group`, `debug`) is a **two-layer** check. Get the
123
+ first layer wrong and you get a **403 `EZ-1 AUTHORIZATION`** before any element ACL runs.
124
+
125
+ 1. **Route-level dispatch gate — `Client_<tenant>.AclRecordScripts`.** The surfaces dispatcher
126
+ authorizes each API script (route + method + phpMethod) via the **CLIENT-tier**
127
+ `AclRecordScripts` table (cols `uuid`, `recordScriptId` → `Core.RecordScripts`, `roleId` →
128
+ `Client_<tenant>.Roles`). The check is keyed on the JWT's **`id.client.roles`** (client roles),
129
+ **NOT** the platform `core.roles` and **NOT** the Core `AclRecordPermissions` chain.
130
+ `Core.RecordScripts` maps route/method/phpMethod → an ACL "record" (`recordId`). The three
131
+ surfaces scripts: `meta` (GET, phpMethod `meta`), `meta-group` (GET, `metaGroup`), `debug` (GET,
132
+ `metaDebug`). `_Model_Core_Surface::resolve/metaGroup` read the caller's roles from
133
+ **`id.client.roles`** (Surface.php ~lines 56/87).
134
+ 2. **Element-level ACL (runs *inside* `meta`/`metaGroup`, AFTER the route gate passes)** — dropping
135
+ individual surface elements uses a **separate** client-tier table `_Model_Client_AclActionPermission`,
136
+ gated by each `SurfaceElement.aclActionId`. This is the "composition, not absorption" step in
137
+ `resolve()` step 4; it never causes the route-level 403.
138
+
139
+ **Debugging a surfaces 403 EZ-1 (hard-won):** it is the route-level gate — a missing
140
+ `Client_<tenant>.AclRecordScripts` grant resolved by **client role**. Do **not** chase
141
+ `Core.AclRecordPermissions`, `appId` scoping, or `AclLogicGroups`/`AclRecordExpressions` — all were
142
+ investigated and ruled out as red herrings: `appId = NULL` is the normal working pattern, and the
143
+ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The gate is purely
144
+ "does this client role have an `AclRecordScripts` row for this `recordScriptId`?"
145
+
116
146
  ## Gotchas
117
147
 
118
148
  - **A Core RecordScript needs `Client.AclRecordScripts` rows per role to be invokable — record-level
@@ -167,8 +197,33 @@ which surfaced latent resolution gaps in `_buildBundle`/`_resolveElement`:
167
197
  per-role `Client.AclRecordScripts` dispatch grant. Verified via toga-db — `meta` script id 25 was
168
198
  granted roles 1/3/4 but `meta-group` (id 27) had **none** until the grant ran. Any new surfaces
169
199
  scripted-API method repeats this: record READ never authorizes dispatch.
200
+ - **The meta-group grant is seeded per-tenant, not globally — new tenants repeat the 403.** When
201
+ `meta-group` shipped, the `AclRecordScripts` grant was seeded **only** for `Client_Compass`
202
+ (recordScriptId → roles 1,3,4). `Client_Quad` and `Client_CompassCanada` had **no** grant for the
203
+ meta-group recordScriptId at all — their `meta` was granted to client role 1 (why `meta` worked but
204
+ `meta-group` 403'd for the same user/token). Same-user-different-script + same-script-different-tenant
205
+ is the fingerprint of this gap. Fix (2026-07-01): two `dbchanges2/Client_<tenant>/` migrations
206
+ granting meta-group to **client role 1** (parity with each tenant's existing `meta` grant),
207
+ id-agnostic (resolve `Core.RecordScripts` id by route/method/phpMethod, cross-DB read of `Core.*`
208
+ per the 2026-06-29c ACL pattern) and re-runnable via `NOT EXISTS`. Surface-layer DB changes ship to
209
+ the **beta** environment only. ⚠ **Root-cause not yet closed:** the meta-group grant should be
210
+ added to the `dbchanges2/Client/` **baseline (blank) seed** so newly-cut clients get it
211
+ automatically — otherwise the next migrated tenant hits the identical 403. Only Compass (already
212
+ correct), Quad, and CompassCanada are addressed. Also note **role breadth**: Compass exposes
213
+ surfaces to roles 1,3,4; Quad/CC got role 1 only (parity with their `meta`). If Quad/CC should
214
+ match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
170
215
 
171
216
  ## Change history
217
+ - 2026-07-01 — Documented the full route-level authorization mechanism (new section): the surfaces
218
+ script gate is `Client_<tenant>.AclRecordScripts` keyed on JWT `id.client.roles` (client roles,
219
+ not `core.roles`, not `Core.AclRecordPermissions`); element-drop is a separate
220
+ `_Model_Client_AclActionPermission`/`aclActionId` layer running inside `resolve()` after the route
221
+ gate; captured the red-herring list (`appId=NULL` is normal, Core grants all evaluate `all`/`"1"`).
222
+ Fixed the `meta-group` 403 EZ-1 for **Quad** and **Compass Canada** — the meta-group
223
+ `AclRecordScripts` grant had been seeded for Compass only; added two id-agnostic, NOT-EXISTS-guarded
224
+ `Client_<tenant>/` migrations granting meta-group to client role 1 (beta env only). Flagged the
225
+ open root-cause: add the grant to the `Client/` baseline seed, and decide role breadth (1 vs 1,3,4)
226
+ for Quad/CC. (apeterson)
172
227
  - 2026-06-30 — Added `metaGroup()` (`GET /v2/surfaces/meta-group?slugs=a,b,c`): batched composition
173
228
  over the existing per-slug `resolve()` cache, capped at `META_GROUP_MAX_SLUGS=25`, unknown slug →
174
229
  empty bundle (chosen over a `parentSurfaceId` GROUP row post-CTO-review). Fixed latent
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-07-01
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - toga25-supply/src/fieldsConfig/index.ts
@@ -64,6 +64,22 @@ and the `useClientFields()` hook.
64
64
  `yourKey: BY_CLIENT[clientSlug] ?? DEFAULT_*`. Avoids editing all ~13 role bundles.
65
65
  `DEFAULT_CLIENT_FIELDS` must also carry the new key (ultimate fallback).
66
66
 
67
+ ### De-roling a client×role bundle to client-only
68
+
69
+ Some bundles were role-keyed (`{ADMIN, MANAGER}`) only for historical reasons — the roles held
70
+ byte-identical config. Those are being flattened to **client-only** (part of the ongoing
71
+ backend-driven / de-role goal). `orderViewFields` is now client-only: `COMPASS` and `COMPASSCANADA`
72
+ read their flat client JSON via `mergeOrderView(<client>OrderViewFields)` in
73
+ `src/fieldsConfig/index.ts` instead of reading `.ADMIN`/`.MANAGER` off the file, and
74
+ `FIELDS/COMPASSCANADA/orderViewFields.json` was flattened from `{ADMIN, MANAGER}` to a flat object.
75
+
76
+ When de-roling, watch the read path: `COMPASS`'s JSON was **already flat** but was still being read
77
+ via `.ADMIN` → `undefined` → the client silently fell back to `DEFAULT` and lost its
78
+ `salesOrderDetailsConfig` joins. This is the flat-bundle trap from the gotcha below — the fix is to
79
+ read the bundle itself, not a role key. `approvalViewFields` and `salesOrdersPageFields` remain
80
+ role-keyed (their ADMIN/MANAGER genuinely differ); fully de-roling those and removing `resolveRole`
81
+ is a separate pending task.
82
+
67
83
  ### Hydration (non-serializable bits)
68
84
 
69
85
  Keep JSON **serializable** — strings, enums, arrays. No JSX, no functions, no Tailwind classes.
@@ -143,6 +159,12 @@ modal's chrome from the same per-client JSON, not from the component:
143
159
  `vendorItemRecordViewFields` are record-modal-layout examples living under `src/layout/.../viewModel/FIELDS/`.
144
160
 
145
161
  ## Change history
162
+ - 2026-07-01 — De-roled `orderViewFields` from client×role to **client-only**: COMPASS/COMPASSCANADA
163
+ now read a flat client JSON via `mergeOrderView(...)` in `fieldsConfig/index.ts` (COMPASSCANADA's
164
+ JSON flattened from byte-identical `{ADMIN,MANAGER}`). Fixed a latent bug where COMPASS's
165
+ already-flat JSON was read via `.ADMIN` → `undefined` → silent fallback to DEFAULT (lost its
166
+ `salesOrderDetailsConfig` joins). `approvalViewFields`/`salesOrdersPageFields` stay role-keyed;
167
+ full de-role + `resolveRole` removal remains pending. Type-check clean. (apeterson)
146
168
  - 2026-06-25 — SalesOrder action buttons render by presentation intent: `kind` (icon/text/viewLog) picks the renderer, `variant` a closed color set, `valueKey` is pure click-dispatch; a `componentRegistry` keyed by `kind` hydrates dedicated components (e.g. ViewLogButton). Added the `recordActionFields`-shape gotcha (per-persona blocks + sibling `poNumber` sub-config; walk `actionOptions` only). (apeterson)
147
169
  - 2026-06-25 — Extended to record-modal layouts: per-mode `*ViewFields`/`*EditFields` JSON under `src/layout/.../viewModel/FIELDS/`, JSON-driven modal `header` (modalTag/labelTemplate/statusBadge) + `editItem` button gated via the Flag/Rule engine, and the flat-bundle defensive-default gotcha. (apeterson)
148
170
  - 2026-06-23 — Documented from the `add-client-fields` skill during initial knowledge seed. (apeterson)
@@ -15,12 +15,13 @@ client: compass-canada
15
15
  type: profile
16
16
  status: active
17
17
  updated: 2026-06-30
18
- owners: [jcardinal, bala, tcox]
18
+ owners: [jcardinal, bala, tcox, apeterson]
19
19
  files: []
20
20
  related:
21
21
  - ../compass-usa/profile.md
22
22
  - ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
23
23
  - ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
24
+ - ../../2.0/apps/_underscore/features/surface-resolver.md
24
25
  ---
25
26
 
26
27
  ## Summary
@@ -50,6 +51,12 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
50
51
  see [Config-Driven Expedited Shipping Gating](../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md).
51
52
 
52
53
  ## Notes
54
+ - **Surface layer (beta):** like Quad, `Client_CompassCanada` had **no** `AclRecordScripts` dispatch
55
+ grant for the surfaces `meta-group` script (only `meta`, to client role 1) → `GET /v2/surfaces/meta-group`
56
+ 403'd EZ-1 while `meta` worked. Fixed 2026-07-01 via
57
+ `dbchanges2/Client_CompassCanada/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql` (meta-group →
58
+ client role 1, id-agnostic + NOT-EXISTS-guarded, beta only). Exposes surfaces to role 1 only, vs
59
+ Compass USA's 1/3/4. See [Surface Resolver](../../2.0/apps/_underscore/features/surface-resolver.md).
53
60
  - Customer language preference: `UserGlobalSettings.settingId = 2` (`en` / `fr-CA`); customer-
54
61
  facing emails are sent in EN or FR accordingly.
55
62
  - Assortment (product-grouping) names are served in fr-CA via the `AssortmentTranslations` sidecar
@@ -14,11 +14,12 @@ client: quad
14
14
  type: profile
15
15
  status: active
16
16
  updated: 2026-06-30
17
- owners: ["jcardinal", "bala"]
17
+ owners: ["jcardinal", "bala", "apeterson"]
18
18
  files: []
19
19
  related:
20
20
  - ../../2.0/apps/_underscore/features/tracking-number-bridges.md
21
21
  - ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
22
+ - ../../2.0/apps/_underscore/features/surface-resolver.md
22
23
  ---
23
24
 
24
25
  ## Summary
@@ -48,6 +49,13 @@ Client-specific DB change-sets live in `dbchanges2/Client_Quad/`.
48
49
  (`_underscore/Model/Quad/SalesOrder.php`): non-SA orders use the inline machine, `SA%` orders
49
50
  delegate to the base `_Model_Client_SalesOrder`. Contrast Compass, which is shipped-only. See
50
51
  [IF stage lifecycle & order status](../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md).
52
+ - **Surface layer (beta):** the surfaces `meta-group` script had **no** `AclRecordScripts` dispatch
53
+ grant for `Client_Quad` (only `meta` was granted, to client role 1), so `GET /v2/surfaces/meta-group`
54
+ 403'd (EZ-1) while `meta` worked. Fixed 2026-07-01 via
55
+ `dbchanges2/Client_Quad/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql` (grants meta-group to
56
+ client role 1, id-agnostic + NOT-EXISTS-guarded, beta only). Quad exposes surfaces to role 1 only,
57
+ vs Compass's 1/3/4 — revisit if broader roles are needed. See
58
+ [Surface Resolver](../../2.0/apps/_underscore/features/surface-resolver.md).
51
59
  - Tracking data: record 318 (item-level) is currently empty for this client; their tracking
52
60
  populates the IF/shipment level (record 317, ~98% coverage) and unit level (record 319). The
53
61
  rebuilt views use 318 (per the Compass pattern) and will show tracking once item-level
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.259",
3
+ "version": "1.0.260",
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",