toga-ai 1.0.588 → 1.0.590
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.
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/per-client-database-connections.md +42 -1
- package/knowledge/2.0/apps/api2/features/nested-relationship-writes.md +43 -1
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md +63 -3
- package/knowledge/2.0/apps/toga-blox/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga-blox/features/api-client.md +19 -1
- package/knowledge/clients/aig/INDEX.md +1 -1
- package/knowledge/clients/aig/features/entitlement-intake.md +122 -1
- package/package.json +1 -1
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| [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 |
|
|
33
33
|
| [NetSuite Sales Order sync — ship-to address, phone, and PO reference sourcing](features/netsuite-salesorder-address-phone-sync.md) | `_Trait_Netsuite_SalesOrder` is the **shared** sales-order importer composed into **22 client models** (every client on the dbchanges2 `netsuite` module). | _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Model.php, dbchanges2/_modules/netsuite/2026-08-10a - AddressPhoneNumberApiRoleAcl.sql |
|
|
34
34
|
| [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | `_Model_Core_Page::meta()` is the **legacy** page-meta resolver behind `GET /pages/meta?slug=<slug>` — still the live path for `toga2-supply` and other pre-Surf | _underscore/Model/Core/Page.php, _underscore/Model/Client/TableView.php |
|
|
35
|
-
| [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/Query.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php, api2/Controller/Index.php |
|
|
35
|
+
| [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/Model.php, _underscore/Query.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php, api2/Controller/Index.php |
|
|
36
36
|
| [Persona Name Translation (PersonaTranslations sidecar)](features/persona-name-translation.md) | Serves Persona **names** in multiple languages by adding a per-language **sidecar** table `PersonaTranslations`, reusing the platform's existing metadata-driven | _underscore/Model/Client/PersonaTranslation.php, dbchanges2/Client/2026-07-22b - PersonaTranslations.sql, dbchanges2/Core/2026-07-22a - PersonaTranslationsRecord.sql, dbchanges2/Client/2026-07-22c - PersonaTranslationsAcl.sql, dbchanges2/Client_CompassCanada/2026-07-22 - PersonaTranslationsFrench.sql, toga2-commerce/src/pages/Account/view/MySettingsView.tsx |
|
|
37
37
|
| [Record Change Audit Log (Logs_<Client>.Record / RecordField) — reading a field's history](features/record-change-audit-log.md) | Every 2.0 client schema has a sibling **logs** schema `Logs_<Tenant>` (e.g. | _underscore/Model/Client/Logs/Record.php, _underscore/Model/Client/Logs/RecordField.php, _underscore/Model/Client/Logs/CustomRecordField.php |
|
|
38
38
|
| [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, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
|
|
@@ -6,10 +6,11 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: ["dfranks", "jcardinal", "mhammontree", "apeterson", "kyalamarthi"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Database.php
|
|
13
|
+
- _underscore/Model.php
|
|
13
14
|
- _underscore/Query.php
|
|
14
15
|
- _underscore/ApiRequest.php
|
|
15
16
|
- _underscore/Model/Client/Logs/Api.php
|
|
@@ -177,8 +178,48 @@ here — they live in `Config/*.ini`.)
|
|
|
177
178
|
(`db_logs`) — the laptop trap there is documented separately in the worker NetSuite bootstrap
|
|
178
179
|
notes.
|
|
179
180
|
|
|
181
|
+
## ⚠ OPEN QUESTION — does the alias-keyed `$_modelCache` leak across tenants? (UNPROVEN)
|
|
182
|
+
|
|
183
|
+
**Do not repeat this as an established leak, and do not dismiss it either.** It needs a focused
|
|
184
|
+
security review. Observed 2026-08-17 (TRUE-80562):
|
|
185
|
+
|
|
186
|
+
An **AIG** `POST /v2/entitlements` carrying nested `entitlementUnits` emitted an
|
|
187
|
+
`INSERT INTO Units` containing `c_itemDescription`, `c_lastModified`, `c_chargingBrick`,
|
|
188
|
+
`c_deviceStatus`, `c_chargingCable`, `c_grade` → **MySQL 1054**, surfaced as EV-12 with a
|
|
189
|
+
`modelSaveError`.
|
|
190
|
+
|
|
191
|
+
**Verified facts:**
|
|
192
|
+
|
|
193
|
+
- `Client_Aig.Units` has **only** `c_netsuiteInternalInventoryAssignmentId`, and
|
|
194
|
+
`Client_Aig.CustomRecordFields` declares only that one field for `Units` (recordId 31) — AIG's
|
|
195
|
+
**schema and metadata agree**.
|
|
196
|
+
- Those six columns exist **only** in `Client_Adyen`, `Client_Elite`, `Client_Growrk`,
|
|
197
|
+
`Client_Prudential`. Five are declared **only** on `_Model_Growrk_Unit`; `_Model_Aig_Unit` extends
|
|
198
|
+
`_Model_Client_Unit` and declares none. `c_lastModified` is declared in **no** model anywhere in
|
|
199
|
+
`_underscore`.
|
|
200
|
+
- So the field list is **neither** AIG's schema, **nor** AIG's metadata, **nor** any model class AIG
|
|
201
|
+
uses — it is exactly **four other tenants'** column set.
|
|
202
|
+
|
|
203
|
+
**Hypothesis (not proven):** `_underscore/Model.php` (~676–687) caches query results as
|
|
204
|
+
`_Database::$_modelCache[$this::DATABASE][$sql]`, and `DATABASE` is the shared **alias** `'Client'`
|
|
205
|
+
— *not* the tenant schema name — so two tenants collide on the same cache key (the same alias-keying
|
|
206
|
+
already documented in the gotcha above). **Unknown: whether that static survives across requests
|
|
207
|
+
under PHP-FPM.** That single unknown is the difference between a harmless within-request artifact and
|
|
208
|
+
a **cross-tenant data-leak vector**, which is why it must be answered rather than assumed.
|
|
209
|
+
|
|
210
|
+
**Where to look:** `_underscore/Model.php` ~676–687, `api2/Component/Api/V2/V2.php` ~8918–8931,
|
|
211
|
+
`_underscore/Model/Growrk/Unit.php`, `_underscore/Model/Aig/Unit.php`. Context:
|
|
212
|
+
[AIG entitlement intake](../../../clients/aig/features/entitlement-intake.md).
|
|
213
|
+
|
|
180
214
|
## Change history
|
|
181
215
|
|
|
216
|
+
- 2026-08-17 — Recorded an **unproven open question** (TRUE-80562): an AIG nested `Units` write
|
|
217
|
+
emitted four *other* tenants' `c_` columns (1054 → EV-12) although AIG's schema, its
|
|
218
|
+
`CustomRecordFields` metadata, and its model class all declare exactly one custom field. Suspected
|
|
219
|
+
mechanism is the **alias-keyed `_Database::$_modelCache['Client'][$sql]`** collision; whether the
|
|
220
|
+
static survives across PHP-FPM requests is unknown, and that determines whether this is a
|
|
221
|
+
within-request artifact or a cross-tenant leak. Flagged for security review; no code changed.
|
|
222
|
+
(mhammontree)
|
|
182
223
|
- 2026-07-30 — Added four framework-level gotchas surfaced while building the all-client email
|
|
183
224
|
queue monitor: the log-DB name has the same never-string-build rule as the client DB;
|
|
184
225
|
`registerClientDatabases()` is unsafe for an all-client loop (INNER JOINs on client+archive
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: ["bala", "mhammontree", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -115,6 +115,38 @@ columns matter:
|
|
|
115
115
|
> Correct the data with an explicit `overrideIsIdentifier = 1`, or (systemic fix, not yet done)
|
|
116
116
|
> change the code so only an explicit `0` removes an identifier and `NULL` means "no override".
|
|
117
117
|
|
|
118
|
+
### Fix it with a migration — a manual one-row data patch on non-prod DOES NOT SURVIVE
|
|
119
|
+
|
|
120
|
+
The TRUE-79978 beta fix was applied by hand as a single `UPDATE` on 2026-07-09. The **identical
|
|
121
|
+
EV-12 recurred on beta on 2026-07-21** (3 requests, `Logs_Aig.Api` ids 93 / 95 / 98) with the same
|
|
122
|
+
signature. Evidence the tenant was **reseeded** underneath the patch: those log rows report
|
|
123
|
+
`apiId 2` as *"AIG Integrations"*, while `Client_Aig.Apis` on beta now names id 2 *"Agilant"* — the
|
|
124
|
+
tenant data was rewritten after 2026-07-21, which is also where an incidental
|
|
125
|
+
`overrideIsIdentifier = 1` on one row came from (not a deliberate re-patch).
|
|
126
|
+
|
|
127
|
+
> **Rule: any `Apis_RecordFields` identifier correction must ship as a `dbchanges2/Client_<Tenant>/`
|
|
128
|
+
> migration.** A manual patch on beta / dev-sandbox evaporates on the next tenant reseed and the
|
|
129
|
+
> bug returns looking brand new. (TRUE-80562.)
|
|
130
|
+
|
|
131
|
+
**Scope rule when correcting these rows — only touch fields whose BASE flag is already 1.** Fix a
|
|
132
|
+
`NULL` `overrideIsIdentifier` **only** where `Core.RecordFields.isIdentifier = 1`. A `NULL` override
|
|
133
|
+
on a base-`0` field is a harmless no-op; setting it to `1` **adds** an identifier the platform never
|
|
134
|
+
had and changes nested match-vs-create behavior for that API. On beta `Client_Aig` there were 15
|
|
135
|
+
override rows (all `apiId 2`) but only **5** qualified: `recordFieldId` **1202**
|
|
136
|
+
(*Entitlement fulfillment types.Name*), **1216** (*Entitlement fulfillment methods.Name*), **1259**
|
|
137
|
+
(*Entitlement units.Unitid*), **1386** (*Entitlement coverage types.Name*), **1651**
|
|
138
|
+
(*Contacts.Customerid*).
|
|
139
|
+
|
|
140
|
+
**Expect a next landmine — fix the whole set, not the one field in the error.** Correcting only
|
|
141
|
+
1202 moved the same EV-12 onto `entitlementFulfillmentMethod` (1216), because
|
|
142
|
+
`_Model_Aig_Entitlement::prePost` injects **both** name-only objects. Audit every qualifying row in
|
|
143
|
+
one migration.
|
|
144
|
+
|
|
145
|
+
**Diagnostic fingerprint:** the EV-12's `searchableIdentifierFields` list is the record's identifier
|
|
146
|
+
set **minus the stripped field** — e.g. `["id","uuid","code"]` on a record whose identifiers are
|
|
147
|
+
Id/Uuid/Code/**Name** means `name` was stripped by an override row. Read that list first; it names
|
|
148
|
+
the culprit without any DB query.
|
|
149
|
+
|
|
118
150
|
**Build divergence warning.** The `Apis_RecordFields` override feature (reading
|
|
119
151
|
`overrideIsIdentifier`/`overrideChildPolicy`) was introduced 2026-03-20 (api2 commit `624146d`
|
|
120
152
|
"Cache custom fields and API override lookups"). Environments on **older** builds ignore the
|
|
@@ -174,6 +206,16 @@ anywhere — no `messages[]`, nothing in the client `Logs` or the core/writer `L
|
|
|
174
206
|
|
|
175
207
|
## Change history
|
|
176
208
|
|
|
209
|
+
- 2026-08-17 — TRUE-80562: the TRUE-79978 **manual one-row beta patch did not survive** — the same
|
|
210
|
+
EV-12 recurred on beta 2026-07-21 (`Logs_Aig.Api` ids 93/95/98) after a tenant reseed (log rows
|
|
211
|
+
call `apiId 2` "AIG Integrations"; beta `Client_Aig.Apis` now names it "Agilant"). Identifier
|
|
212
|
+
corrections must ship as a `dbchanges2/Client_<Tenant>/` migration. Added the **scope rule** (only
|
|
213
|
+
fix `NULL` overrides where `Core.RecordFields.isIdentifier = 1` — 5 of 15 AIG rows qualified:
|
|
214
|
+
1202/1216/1259/1386/1651), the **next-landmine** warning (fixing 1202 alone moves the EV-12 to
|
|
215
|
+
1216 because `prePost` injects both objects), and the **`searchableIdentifierFields` fingerprint**
|
|
216
|
+
(base identifier set minus the stripped field names the culprit without a query). Verified
|
|
217
|
+
2026-08-17: `POST /v2/entitlements` on beta returned 201 with both injected fields resolved.
|
|
218
|
+
(mhammontree)
|
|
177
219
|
- 2026-08-03 (later pass) — **Correction, supersedes the entry below on one point.** The bare-string
|
|
178
220
|
collection shape does **not** make the addresses "vanish"/blank the consumer's field: the empty
|
|
179
221
|
`array_column()` result makes the consumer's primary-email fallback fire, so the stored value is
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
| [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | > **A local browser wizard now automates this.** Steps 2–9 below (create DBs, generate Core/API > inserts, append to `Clients_Db.txt`) — plus the dbchanges2 bla | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
|
|
8
8
|
| [Auditing a client DB that drifted from its models (partially applied module migration)](workflows/client-schema-drift-audit.md) | A recurring 2.0 failure mode: **one client's database drifts from what the PHP models declare**, usually because a `_modules/<module>/` migration was applied to | dbchanges2/Client_Growrk/2026-05-28.sql, dbchanges2/Client_Growrk/2026-08-10c - GrowrkServiceRequestCustomFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10d - GrowrkServiceRequestTypeAndDispositionSeeds.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Growrk/2026-08-10 - GrowrkUnitInventoryFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10b - GrowrkUnitItemDescriptionAcl.sql, dbchanges2/Client_Growrk/_modules.txt |
|
|
9
9
|
| [Local vs prod MySQL config parity — why “it passed locally” is not evidence](workflows/local-vs-prod-mysql-config-parity.md) | Several migration failures that look like "prod-only bugs" are actually **per-machine MySQL server-configuration differences**. | |
|
|
10
|
-
| [Repairing non-prod metadata drift (works in prod, broken in beta/dev-sandbox)](workflows/nonprod-metadata-drift-repair.md) | Almost all 2.0 platform behavior is **metadata** — `Core.Records`/`RecordFields`, `Core.RecordScripts`, `Core.ApiPayloadInterceptors`, and per-client `Acl*` row | dbchanges2/Core/2026-06-30a - ItemFulfillmentStageDefaultInterceptor.sql, dbchanges2/Client_Compass/2026-08-06 - RemoveBrokenSalesOrderItemPostPostInterceptor.sql, dbchanges2/Core/2026-07-16a - TrackingNumberSignatureTypeRecordField.sql, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql, api2/Config/beta.ini |
|
|
10
|
+
| [Repairing non-prod metadata drift (works in prod, broken in beta/dev-sandbox)](workflows/nonprod-metadata-drift-repair.md) | Almost all 2.0 platform behavior is **metadata** — `Core.Records`/`RecordFields`, `Core.RecordScripts`, `Core.ApiPayloadInterceptors`, and per-client `Acl*` row | dbchanges2/Core/2026-06-30a - ItemFulfillmentStageDefaultInterceptor.sql, dbchanges2/Client_Compass/2026-08-06 - RemoveBrokenSalesOrderItemPostPostInterceptor.sql, dbchanges2/Core/2026-07-16a - TrackingNumberSignatureTypeRecordField.sql, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql, api2/Config/beta.ini, api2/Config/sandbox-dev.ini, dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql, dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql |
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: ["mhammontree", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Core/2026-06-30a - ItemFulfillmentStageDefaultInterceptor.sql
|
|
@@ -14,6 +14,9 @@ files:
|
|
|
14
14
|
- dbchanges2/Core/2026-07-16a - TrackingNumberSignatureTypeRecordField.sql
|
|
15
15
|
- dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql
|
|
16
16
|
- api2/Config/beta.ini
|
|
17
|
+
- api2/Config/sandbox-dev.ini
|
|
18
|
+
- dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql
|
|
19
|
+
- dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql
|
|
17
20
|
related:
|
|
18
21
|
- ../architecture.md
|
|
19
22
|
- ./client-schema-drift-audit.md
|
|
@@ -41,8 +44,12 @@ exercise TOGa Supply Fulfill & Ship (TRUE-80494).
|
|
|
41
44
|
## Steps
|
|
42
45
|
|
|
43
46
|
1. **Identify which cluster the environment actually reads.** `api.beta.togahub.com` reads the
|
|
44
|
-
**dev-sandbox** cluster
|
|
45
|
-
|
|
47
|
+
**dev-sandbox** cluster — "beta" is not its own database. Fix the right DB. Confirmed again
|
|
48
|
+
2026-08-17 from the other side: **`api2/Config/sandbox-dev.ini`** carries
|
|
49
|
+
`[api] _ = "https://api.beta.togahub.com/v2"` and `hostname = dev.sandbox.database.togahub.com`.
|
|
50
|
+
So the toga-db alias to target is **`dev-sandbox`**, *not* `client-beta` — e.g. `Client_Aig`
|
|
51
|
+
exists only in **prod and dev-sandbox** and is absent from `client-beta` entirely. Picking the
|
|
52
|
+
wrong alias wastes a full diagnostic cycle.
|
|
46
53
|
2. **Full-table compare the small metadata tables, prod vs. target.** `ApiPayloadInterceptors` in
|
|
47
54
|
particular is small enough to diff wholesale, and the drift is typically **exactly one row**
|
|
48
55
|
(dev-sandbox was missing `(recordId 28, PRE, POST)`, so `prePost` never defaulted
|
|
@@ -78,6 +85,48 @@ exercise TOGa Supply Fulfill & Ship (TRUE-80494).
|
|
|
78
85
|
6. **Re-test the feature end-to-end in that environment**, then record what is still missing so the
|
|
79
86
|
next person doesn't rediscover it.
|
|
80
87
|
|
|
88
|
+
## The `Client/` fan-out is skipped independently of `Core/` — and the gap is NON-CONTIGUOUS
|
|
89
|
+
|
|
90
|
+
Measured on dev-sandbox 2026-08-17 (TRUE-80562): **31 of 33 tenant DBs lack
|
|
91
|
+
`Entitlements.serviceAddressId`** (only `Client_Rate` and `Client_Elite` have it), while
|
|
92
|
+
**Core does have the RecordField (id 2763)**. So `Core/2026-07-22a` ran and the
|
|
93
|
+
**`Client/` fan-out of the same change did not.** Symptom: **EV-10 / MySQL 1054 "Unknown column
|
|
94
|
+
'serviceAddressId'" on every entitlement write** — for 31 tenants, not just the one you are
|
|
95
|
+
debugging. That migration's own header states the column must exist for all tenants *before* the
|
|
96
|
+
`_underscore` code deploys, so the code is live against schemas that never got the column.
|
|
97
|
+
|
|
98
|
+
> **Do not conclude "beta is behind as of date X."** The gap is **partial and non-contiguous** —
|
|
99
|
+
> verified against each file's actual `ALTER`:
|
|
100
|
+
>
|
|
101
|
+
> | State | `Client/` file |
|
|
102
|
+
> |---|---|
|
|
103
|
+
> | **MISSING** | `2026-07-10a` ItemFulfillments.returnAddressId |
|
|
104
|
+
> | **MISSING** | `2026-07-16a` TrackingNumbers.signatureType |
|
|
105
|
+
> | **MISSING** | `2026-07-22a` Entitlements.serviceAddressId |
|
|
106
|
+
> | **MISSING** | `2026-07-28a` TicketStages.ticketTypeId |
|
|
107
|
+
> | PRESENT | `2026-07-17` Items.isFulfillable |
|
|
108
|
+
> | PRESENT | `2026-07-22b` TrackingNumbers.length / weightMeasureId |
|
|
109
|
+
>
|
|
110
|
+
> A skipped `07-22a` sits **beside a `07-22b` that ran**. Check file-by-file; a date cutoff will
|
|
111
|
+
> mislead you.
|
|
112
|
+
|
|
113
|
+
**Fix direction: run the existing pending `Client/` files — do NOT author a new migration**, which
|
|
114
|
+
would duplicate a file already on `_main`. A scoped manual `ALTER` on one tenant unblocks your own
|
|
115
|
+
testing but leaves the other 30 broken; say so when you do it.
|
|
116
|
+
|
|
117
|
+
**LEAD (unproven) — duplicate letter suffixes may be why the executor skipped it.** `dbchanges2/Client/`
|
|
118
|
+
contains **three** files all named `2026-07-22a` (`EntitlementServiceAddressId`, `MeasureUomColumns`,
|
|
119
|
+
`Notification&PersonaTranslations`), violating the one-letter-per-day naming contract in
|
|
120
|
+
[architecture.md](../architecture.md). The skipped file is one of that trio. **Worth checking whether
|
|
121
|
+
the executor dedupes on date+letter** — if it does, the naming violation is the root cause and the
|
|
122
|
+
same trap is live wherever letters collide.
|
|
123
|
+
|
|
124
|
+
**Rule violation worth fixing separately:** `Client/2026-07-23a -
|
|
125
|
+
EntitlementServiceAddressIdFieldPermission.sql` references **`Core.RecordFields` and `Core.Records`
|
|
126
|
+
from a `Client/` fan-out file**, breaking the hard one-database-per-file cluster-isolation rule. Its
|
|
127
|
+
own comment says *"same server"* — which is exactly the trap the rule exists for: it works on
|
|
128
|
+
dev-sandbox (shared cluster) and would fail in production, where `Core` is a separate cluster.
|
|
129
|
+
|
|
81
130
|
## Environment facts worth knowing before you test here
|
|
82
131
|
|
|
83
132
|
- **NO carrier can be tested on ANY non-production environment.** Beta runs
|
|
@@ -156,6 +205,17 @@ blocked by the id-divergence hazard in item 2 above, which is why both go to jca
|
|
|
156
205
|
|
|
157
206
|
## Change history
|
|
158
207
|
|
|
208
|
+
- 2026-08-17 — TRUE-80562: documented that the **`Client/` fan-out is skipped independently of
|
|
209
|
+
`Core/`** on dev-sandbox — 31 of 33 tenant DBs lack `Entitlements.serviceAddressId` (only
|
|
210
|
+
`Client_Rate`/`Client_Elite` have it) while Core carries the RecordField (2763), so **entitlement
|
|
211
|
+
writes EV-10/1054 for 31 tenants**. The gap is **partial and non-contiguous** (missing 07-10a,
|
|
212
|
+
07-16a, 07-22a, 07-28a; present 07-17, 07-22b), so a date cutoff misleads. Fix = run the existing
|
|
213
|
+
pending files, never author a duplicate. Recorded the unproven **duplicate-letter lead** (three
|
|
214
|
+
`Client/2026-07-22a` files; the skipped one sits beside a `07-22b` that ran — check whether the
|
|
215
|
+
executor dedupes on date+letter) and a **cluster-isolation violation** in `Client/2026-07-23a`
|
|
216
|
+
(reads `Core.RecordFields`/`Core.Records` from a `Client/` file, justified as "same server").
|
|
217
|
+
Step 1 now cites `api2/Config/sandbox-dev.ini` and the `dev-sandbox` vs `client-beta` alias trap.
|
|
218
|
+
(mhammontree)
|
|
159
219
|
- 2026-08-14 — Recorded that metadata drift runs in **both** directions: a cleanup migration that
|
|
160
220
|
*removed* a row in production is as easily skipped in non-prod as an additive one. Evidence —
|
|
161
221
|
`Client_Compass/2026-08-06 - RemoveBrokenSalesOrderItemPostPostInterceptor.sql` had never run on
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| [TOGa Blox (toga-blox) Architecture](architecture.md) | `toga-blox` (npm package **`@agilant/toga-blox`**) is the **shared React + TypeScript component / template / utility library** reused across the 2.0 frontends — | toga-blox/package.json, toga-blox/src/components/index.ts, toga-blox/src/api/index.ts, toga-blox/src/templates/index.ts |
|
|
6
6
|
| [AddNotes template — badge style/label decoupling & Badge style passthrough](features/add-notes-template.md) | The `@agilant/toga-blox` `AddNotesTemplate` renders a list of notes, each with a per-note contact **badge** (e.g. | toga-blox-npm/src/templates/AddNotes/types.ts, toga-blox-npm/src/templates/AddNotes/AddNotesTemplate.tsx, toga-blox-npm/src/components/Badge/Badge.types.tsx, toga-blox-npm/src/components/Badge/Badge.tsx |
|
|
7
7
|
| [AdvancedSelect (virtualized single/multi select)](features/advanced-select.md) | `AdvancedSelect<T>` is a **fully controlled, virtualized** single/multi select built from scratch (not react-select) on **`@tanstack/react-virtual`**, for large | toga-blox/src/components/AdvancedSelect/AdvancedSelect.tsx, toga-blox/src/components/AdvancedSelect/AdvancedSelect.types.ts, toga-blox/src/components/AdvancedSelect/AdvancedSelect.module.css |
|
|
8
|
-
| [API client (axios wrapper, auth, table-data fetchers)](features/api-client.md) | `src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages auth (Bearer + refresh), serializes complex query options, and provid | toga-blox/src/api/index.ts, toga-blox/src/api/axiosInstance.ts, toga-blox/src/api/apiFunctions.ts, toga-blox/src/api/auth.ts, toga-blox/src/api/genericApi.ts, toga-blox/src/api/types.ts, toga-blox/src/api/tableData |
|
|
8
|
+
| [API client (axios wrapper, auth, table-data fetchers)](features/api-client.md) | `src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages auth (Bearer + refresh), serializes complex query options, and provid | toga-blox/src/reactQuery/queryHelpers.ts, toga-blox/src/api/index.ts, toga-blox/src/api/axiosInstance.ts, toga-blox/src/api/apiFunctions.ts, toga-blox/src/api/auth.ts, toga-blox/src/api/genericApi.ts, toga-blox/src/api/types.ts, toga-blox/src/api/tableData |
|
|
9
9
|
| [BaseInput (react-hook-form field factory)](features/base-input.md) | `BaseInput` is a **form-field factory** driven by react-hook-form. | toga-blox/src/components/BaseInput/BaseInput.tsx, toga-blox/src/components/BaseInput/BaseInput.types.ts, toga-blox/src/components/BaseInput/BaseInput.module.css, toga-blox/src/components/BaseInput/components |
|
|
10
10
|
| [Primary Table templates (server/client, sizing, virtualization)](features/primary-table-templates.md) | `src/templates/PrimaryTable/` is the **production, wired-up table** built on the [Table component](table.md). | toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx, toga-blox/src/templates/PrimaryTable/types.ts |
|
|
11
11
|
| [TableRecordModal (record-detail modal shell)](features/table-record-modal.md) | `TableRecordModal` is a **generic, presentational modal shell** for showing a single table record (row) in detail — typically opened from a table row click. | toga-blox/src/components/TableRecordModal/TableRecordModal.tsx, toga-blox/src/components/TableRecordModal/index.ts, toga-blox/src/components/TableRecordModal/tableRecordModal.module.css |
|
|
@@ -6,9 +6,10 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
|
+
- toga-blox/src/reactQuery/queryHelpers.ts
|
|
12
13
|
- toga-blox/src/api/index.ts
|
|
13
14
|
- toga-blox/src/api/axiosInstance.ts
|
|
14
15
|
- toga-blox/src/api/apiFunctions.ts
|
|
@@ -105,8 +106,25 @@ Filterable`, `hyperlinkField`, `imageUrlField`, `sticky`, `precision`, …), `Da
|
|
|
105
106
|
`Core.Domains` row, not frontend code. See
|
|
106
107
|
[encrypted user-uuid auth handoff](../../api2/features/encrypted-user-uuid-auth-handoff.md) for
|
|
107
108
|
the same gating on the SSO handoff route.
|
|
109
|
+
- **⚠ `assembleOptionsWhere` (`reactQuery/queryHelpers.ts`) expects every `where.and`/`where.or`
|
|
110
|
+
entry to be a STRUCTURED OBJECT** (`{field:{op:value}}`). A **raw API-syntax string** clause —
|
|
111
|
+
e.g. a metadata `apiWhereClause` like `(IFNULL(Items.assetTypeId,0):ne:3)` that
|
|
112
|
+
`getDataTableData` pushes into `apiOptions.where.and` — is iterated with `for (const field in
|
|
113
|
+
entry)`, which on a string walks **character indices**, emitting garbage `index:0:char`
|
|
114
|
+
conditions (`0:0:(`, `1:0:I`, …). api2 v2 then rejects the request with **`Invalid operator '0'`
|
|
115
|
+
→ EO-1 500**. Symptom: opening a modal/table whose `tableViewMeta` carries an `apiWhereClause`
|
|
116
|
+
(seen in the Compass USA Item Fulfillment modal) 500s. Fix (in place since 2026-08-17): the
|
|
117
|
+
serializer now passes any `typeof === "string"` entry through **verbatim**, stripping one outer
|
|
118
|
+
paren pair so the group re-wrap doesn't double-wrap. Any raw pre-formatted where clause was
|
|
119
|
+
100% broken before this. If you add a new raw-string clause path, route it through this string
|
|
120
|
+
branch — do **not** feed a raw clause into an object-expecting serializer.
|
|
108
121
|
|
|
109
122
|
## Change history
|
|
123
|
+
- 2026-08-17 — Fixed `assembleOptionsWhere` spreading a raw-string `apiWhereClause` character-by-
|
|
124
|
+
character into invalid `index:0:char` conditions (api2 `Invalid operator '0'` / EO-1 500 opening
|
|
125
|
+
the Compass USA Item Fulfillment modal). String entries now pass through verbatim (single outer
|
|
126
|
+
paren pair stripped). Library-wide fix in `reactQuery/queryHelpers.ts`, not client-specific.
|
|
127
|
+
(apeterson)
|
|
110
128
|
- 2026-08-13 — TRUE-80091: recorded that `fetchPublicToken()`/`POST /auth/public` is **origin-gated
|
|
111
129
|
against `Core.Domains` (EN-6 on an unregistered origin)** and that each host variant needs its own
|
|
112
130
|
row — `www.togatech.com` is rejected while `togatech.com` and `localhost` are allowed, which
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [AIG Contract Reconciliation & Dealer Programs (STS_001 / SA_001)](features/contract-reconciliation.md) | 2.0 | How to reconcile an **AIG contract sales sheet** against the `Client_Aig` tenant, and the durable finding that the **Staples Advantage.com (`SA_001`) program do | _underscore/Model/Client/Entitlement.php, _underscore/Model/Aig/Entitlement.php, _underscore/Model/Client/Contact.php, _underscore/Model/Aig/Contact.php |
|
|
6
|
-
| [AIG Entitlement Intake & SaleItem Code Resolution](features/entitlement-intake.md) | 2.0 | The `_Model_Aig_Entitlement::prePost` interceptor below is the code path for AIG protection-plan entitlements arriving as `api2` V2 JSON POSTs — and that path * | _underscore/Model/Aig/Entitlement.php, api2/Component/Api/V2/V2.php, dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql |
|
|
6
|
+
| [AIG Entitlement Intake & SaleItem Code Resolution](features/entitlement-intake.md) | 2.0 | The `_Model_Aig_Entitlement::prePost` interceptor below is the code path for AIG protection-plan entitlements arriving as `api2` V2 JSON POSTs — and that path * | _underscore/Model/Aig/Entitlement.php, _underscore/Model/Aig/Unit.php, api2/Component/Api/V2/V2.php, api2/Config/sandbox-dev.ini, dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql, dbchanges2/Client_Aig/2026-08-17a - TRUE-80562 AIG API identifier overrides.sql |
|
|
7
7
|
| [AIG (Staples Protection Plan)](profile.md) | 2.0 | AIG is the warranty underwriter behind the **Staples Protection Plan** retail program. | |
|
|
@@ -5,12 +5,15 @@ project: API
|
|
|
5
5
|
client: aig
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-08-
|
|
8
|
+
updated: 2026-08-17
|
|
9
9
|
owners: ["mhammontree"]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Model/Aig/Entitlement.php
|
|
12
|
+
- _underscore/Model/Aig/Unit.php
|
|
12
13
|
- api2/Component/Api/V2/V2.php
|
|
14
|
+
- api2/Config/sandbox-dev.ini
|
|
13
15
|
- dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql
|
|
16
|
+
- dbchanges2/Client_Aig/2026-08-17a - TRUE-80562 AIG API identifier overrides.sql
|
|
14
17
|
related:
|
|
15
18
|
- clients/aig/features/contract-reconciliation.md
|
|
16
19
|
- 1.0/apps/library/features/toga2-api-client-and-bridge.md
|
|
@@ -18,7 +21,9 @@ related:
|
|
|
18
21
|
- 2.0/apps/api2/features/nested-relationship-writes.md
|
|
19
22
|
- 2.0/apps/api2/architecture.md
|
|
20
23
|
- 2.0/apps/dbchanges2/architecture.md
|
|
24
|
+
- 2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md
|
|
21
25
|
- 2.0/apps/_underscore/architecture.md
|
|
26
|
+
- 2.0/apps/_underscore/features/per-client-database-connections.md
|
|
22
27
|
---
|
|
23
28
|
|
|
24
29
|
## Summary
|
|
@@ -149,6 +154,93 @@ recovered on 07-29 exactly as the resolution above describes. But the creating p
|
|
|
149
154
|
rows (through 2026-08-04) was **not positively identified** — confirm it in `Logs_Aig.Api` before
|
|
150
155
|
asserting either "intake is entirely down" or "intake is healthy."
|
|
151
156
|
|
|
157
|
+
> **Partly answered 2026-08-17 (TRUE-80562):** `Logs_Aig.Api` **does** carry non-2xx entitlement
|
|
158
|
+
> writes on beta (ids 93 / 95 / 98, all 400/EV-12 on 2026-07-21), so the "failures only ever land in
|
|
159
|
+
> core `Logs`" reading is too strong — `Logs_Aig` is a usable first stop for beta 4xx. Only 5xx-class
|
|
160
|
+
> failures reliably escape to the core/writer `Logs`.
|
|
161
|
+
|
|
162
|
+
## Which environment is "beta"? (`api.beta.togahub.com` = **dev-sandbox**)
|
|
163
|
+
|
|
164
|
+
`api2/Config/sandbox-dev.ini` carries `[api] _ = "https://api.beta.togahub.com/v2"` and a database
|
|
165
|
+
`hostname` of `dev.sandbox.database.togahub.com`. So for AIG/API work, **"beta" resolves to the
|
|
166
|
+
dev-sandbox cluster** (toga-db alias `dev-sandbox`), **not** the `client-beta` alias — which has no
|
|
167
|
+
`Client_Aig` schema at all. **`Client_Aig` exists only in prod and dev-sandbox.** Targeting the wrong
|
|
168
|
+
alias costs a whole diagnostic cycle. See
|
|
169
|
+
[non-prod metadata drift repair](../../../2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md).
|
|
170
|
+
|
|
171
|
+
## EV-12 on the injected fulfillment fields — fix it with a MIGRATION (TRUE-80562)
|
|
172
|
+
|
|
173
|
+
The TRUE-79978 EV-12 (an `Apis_RecordFields` override row with `overrideIsIdentifier = NULL`
|
|
174
|
+
stripping `name` as an identifier) **came back on beta**. The 2026-07-09 manual one-row `UPDATE`
|
|
175
|
+
**did not survive**: the identical error hit `entitlementFulfillmentType` again on **2026-07-21**
|
|
176
|
+
(`Logs_Aig.Api` ids 93 / 95 / 98). The tenant had been **reseeded** — those log rows call `apiId 2`
|
|
177
|
+
*"AIG Integrations"* while beta `Client_Aig.Apis` now names id 2 *"Agilant"*.
|
|
178
|
+
|
|
179
|
+
> **Manual data patches on beta evaporate. The fix must be a `dbchanges2/Client_Aig/` migration** —
|
|
180
|
+
> shipped as `2026-08-17a - TRUE-80562 AIG API identifier overrides.sql`.
|
|
181
|
+
|
|
182
|
+
- **Scope rule:** correct a `NULL` `overrideIsIdentifier` **only** where the BASE
|
|
183
|
+
`Core.RecordFields.isIdentifier = 1`. On a base-`0` field the NULL is a harmless no-op and setting
|
|
184
|
+
it to `1` would **add** an identifier and change nested match-vs-create behavior. Beta
|
|
185
|
+
`Client_Aig` had **15** override rows (all `apiId 2`); only **5** qualified — `recordFieldId`
|
|
186
|
+
**1202** (Entitlement fulfillment types.Name), **1216** (Entitlement fulfillment methods.Name),
|
|
187
|
+
**1259** (Entitlement units.Unitid), **1386** (Entitlement coverage types.Name), **1651**
|
|
188
|
+
(Contacts.Customerid).
|
|
189
|
+
- **Fix the whole set — there is a next landmine.** Fixing only 1202 moved the same EV-12 onto
|
|
190
|
+
`entitlementFulfillmentMethod` (1216), because `prePost` injects **both** name-only objects.
|
|
191
|
+
- **Fingerprint:** the error's `searchableIdentifierFields` is the record's identifier set *minus*
|
|
192
|
+
the stripped field — `["id","uuid","code"]` on record 186 (identifiers Id/Uuid/Code/**Name**)
|
|
193
|
+
points straight at `name`.
|
|
194
|
+
- **Verified 2026-08-17:** `POST /v2/entitlements` on `api.beta.togahub.com` returned **201** with
|
|
195
|
+
both injected fields resolved, 3 coverage types and 3 contact email addresses written.
|
|
196
|
+
|
|
197
|
+
## ⚠ `Client_Aig.Contacts.c_aigCustomerId` integer overflow — destroying identifiers IN PRODUCTION
|
|
198
|
+
|
|
199
|
+
**Needs its own ticket; higher priority than TRUE-80562.**
|
|
200
|
+
|
|
201
|
+
`Client_Aig.Contacts.c_aigCustomerId` is `int unsigned` (max **4,294,967,295**) on **both beta and
|
|
202
|
+
prod**, but AIG customer ids are **13 digits** (e.g. `1000045957545`). MySQL **silently clamps** every
|
|
203
|
+
one of them to the maximum.
|
|
204
|
+
|
|
205
|
+
- **Measured on prod 2026-08-17:** 353 contacts — **314 (89%) clamped to 4294967295**, 14 NULL, only
|
|
206
|
+
**26 distinct values** left.
|
|
207
|
+
- **Why it is severe, not cosmetic:** `c_aigCustomerId` is `isIdentifier = 1` in
|
|
208
|
+
`Client_Aig.CustomRecordFields`, i.e. a **searchable identifier used for nested-object matching**.
|
|
209
|
+
314 contacts now share one identifier value, so a match on it is ambiguous and can attach an
|
|
210
|
+
entitlement / email / claim to the **wrong customer**.
|
|
211
|
+
- **Fix direction:** a `dbchanges2/Client_Aig/` migration to `BIGINT UNSIGNED` **stops the bleeding
|
|
212
|
+
but recovers nothing** — the original values are destroyed in the column. **Possible recovery
|
|
213
|
+
source:** the true ids are in the logged `requestPayload` of inbound entitlement POSTs
|
|
214
|
+
(`Logs_Aig.Api` and the core/writer `Logs`), keyed by contract number. **Confirm that before
|
|
215
|
+
assuming the loss is permanent.**
|
|
216
|
+
|
|
217
|
+
## Open question — cross-tenant field list on nested `Units` writes (mechanism UNPROVEN)
|
|
218
|
+
|
|
219
|
+
An AIG POST carrying `entitlementUnits` produced an `INSERT INTO Units` that included
|
|
220
|
+
`c_itemDescription`, `c_lastModified`, `c_chargingBrick`, `c_deviceStatus`, `c_chargingCable`,
|
|
221
|
+
`c_grade` → **MySQL 1054**, surfaced as EV-12 with a `modelSaveError`.
|
|
222
|
+
|
|
223
|
+
**Verified facts:**
|
|
224
|
+
|
|
225
|
+
- `Client_Aig.Units` has **only** `c_netsuiteInternalInventoryAssignmentId`, and
|
|
226
|
+
`Client_Aig.CustomRecordFields` declares only that same one field for `Units` (recordId 31) —
|
|
227
|
+
AIG's schema and metadata **agree**.
|
|
228
|
+
- Those six columns exist only in `Client_Adyen`, `Client_Elite`, `Client_Growrk`,
|
|
229
|
+
`Client_Prudential`. Five are declared **only** on `_Model_Growrk_Unit`; `_Model_Aig_Unit` extends
|
|
230
|
+
`_Model_Client_Unit` and declares none. `c_lastModified` is declared in **no** model anywhere in
|
|
231
|
+
`_underscore`.
|
|
232
|
+
- So the field list is neither AIG's schema, nor AIG's metadata, nor any model class AIG uses — it is
|
|
233
|
+
**exactly four other tenants' column set**.
|
|
234
|
+
|
|
235
|
+
**Hypothesis — NOT PROVEN, do not repeat as fact.** `_underscore/Model.php` (~676–687) caches query
|
|
236
|
+
results as `_Database::$_modelCache[$this::DATABASE][$sql]`, where `DATABASE` is the shared **alias**
|
|
237
|
+
(`'Client'`), not the tenant schema name. It is **unknown** whether that static survives across
|
|
238
|
+
requests under PHP-FPM. That distinction is the difference between a harmless within-request artifact
|
|
239
|
+
and a **cross-tenant data-leak vector**, so it needs a focused security review before anyone dismisses
|
|
240
|
+
it. Entry points: `_underscore/Model.php` ~676–687, `api2/Component/Api/V2/V2.php` ~8918–8931,
|
|
241
|
+
`_underscore/Model/Growrk/Unit.php`, `_underscore/Model/Aig/Unit.php`. See
|
|
242
|
+
[per-client database connections](../../../2.0/apps/_underscore/features/per-client-database-connections.md).
|
|
243
|
+
|
|
152
244
|
## Business Unit (`UserDefined2`) — answered and DROPPED (nothing to build)
|
|
153
245
|
|
|
154
246
|
AIG (Alissa, 2026-05-04) confirmed the **Business Unit / Value** field needs **nothing returned to
|
|
@@ -320,6 +412,20 @@ this interceptor or use this dual-purpose Items pattern.
|
|
|
320
412
|
find a searchable identifier for the injected object. Root cause (TRUE-79978) was an
|
|
321
413
|
`Apis_RecordFields` override row that stripped `name` as an identifier — see
|
|
322
414
|
[api2 nested-relationship writes → Per-API overrides](../../../2.0/apps/api2/features/nested-relationship-writes.md).
|
|
415
|
+
- **The empty-`partNumber` variant is CONFIRMED LIVE (observed 2026-08-17, not inferred).** A
|
|
416
|
+
payload with `saleItem.partNumber = ""` resolved to `saleItemId = 10` — on beta a row with
|
|
417
|
+
`partNumber = ''` and description *"2YR Tablet: ($300-$399.99)"*, an item **unrelated to the
|
|
418
|
+
submitted contract**. The same payload with a real code (`SP-3DAD-LTP2`) correctly resolved to
|
|
419
|
+
id 1. The doc predicted this; it now has a live sighting. The guard clause is still not written.
|
|
420
|
+
- **`prePost` OVERRIDES the client-submitted fulfillment method and nothing signals it.** A payload
|
|
421
|
+
sending `entitlementFulfillmentMethod {name: "Credit"}` produced a stored record of **"Gift Card"**
|
|
422
|
+
plus an injected type **"Reimbursement"** — the interceptor populates both from
|
|
423
|
+
`Items_EntitlementFulfillmentMethods`/`Types` for the matched sale item and **discards the
|
|
424
|
+
client-supplied value**. This may be intended (catalog is the authority), but the **201 response
|
|
425
|
+
does not signal the override**, so a partner cannot tell their value was dropped. **Open question
|
|
426
|
+
for the integration owner** — confirm intent, and consider echoing the resolved values.
|
|
427
|
+
- **Beta 4xx entitlement writes DO appear in `Logs_Aig.Api`** (ids 93/95/98, 2026-07-21). Check the
|
|
428
|
+
client log first for validation failures; reserve the core/writer `Logs` hunt for 5xx.
|
|
323
429
|
- **A lookup miss yields an empty `$itemId` that crashes the next query (confirmed in prod,
|
|
324
430
|
TRUE-79534).** The unguarded line is **`_underscore/Model/Aig/Entitlement.php:48`** in `prePost`,
|
|
325
431
|
reached from **`api2/Component/Api/V2/V2.php:2601`**. The malformed statement it emits is:
|
|
@@ -357,6 +463,21 @@ this interceptor or use this dual-purpose Items pattern.
|
|
|
357
463
|
|
|
358
464
|
## Change history
|
|
359
465
|
|
|
466
|
+
- 2026-08-17 — TRUE-80562 (beta EV-12 on entitlement intake). The TRUE-79978 **manual beta patch did
|
|
467
|
+
not survive a tenant reseed** — same EV-12 recurred 2026-07-21 (`Logs_Aig.Api` 93/95/98); the fix
|
|
468
|
+
now ships as `dbchanges2/Client_Aig/2026-08-17a`. Recorded the **scope rule** (only correct NULL
|
|
469
|
+
`overrideIsIdentifier` where base `Core.RecordFields.isIdentifier = 1` — 5 of 15 rows:
|
|
470
|
+
1202/1216/1259/1386/1651), the **next landmine** (fixing 1202 alone moves the error to 1216 since
|
|
471
|
+
`prePost` injects both objects), and the `searchableIdentifierFields` fingerprint. **Verified: 201
|
|
472
|
+
on `api.beta.togahub.com`.** Added: `api.beta.togahub.com` = **dev-sandbox** per
|
|
473
|
+
`api2/Config/sandbox-dev.ini` (`Client_Aig` exists only in prod + dev-sandbox, never
|
|
474
|
+
`client-beta`); beta **4xx** writes DO land in `Logs_Aig.Api`. New **⚠ prod data defect**:
|
|
475
|
+
`Contacts.c_aigCustomerId` is `int unsigned` while AIG ids are 13 digits — **314 of 353 prod
|
|
476
|
+
contacts clamped to 4294967295** on an `isIdentifier` field (ambiguous nested matching); needs its
|
|
477
|
+
own ticket, BIGINT migration does not recover the values. New **open question**: a nested `Units`
|
|
478
|
+
write emitted four other tenants' `c_` columns (1054) — alias-keyed `_modelCache` hypothesis,
|
|
479
|
+
unproven, needs security review. Confirmed the empty-`partNumber` trap live (`saleItemId = 10`)
|
|
480
|
+
and that `prePost` silently overrides a submitted `entitlementFulfillmentMethod`. (mhammontree)
|
|
360
481
|
- 2026-08-10 — Read-only prod investigation (no code/SQL changed). Added **Verified prod state**:
|
|
361
482
|
the TRUE-79534 catalog **is** loaded (`Client_Aig.Items` = 2,212 rows — 1,110 `ASI-*` / 558
|
|
362
483
|
`SM-*` / 37 `SP-*`; `ASI-2YG2` present), so a future 1064 here is the **empty-input** variant,
|
package/package.json
CHANGED