toga-ai 1.0.294 → 1.0.295
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,12 +6,13 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-07-09
|
|
10
|
+
owners: ["bala", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
related:
|
|
14
14
|
- ../architecture.md
|
|
15
|
+
- ../../../clients/aig/features/entitlement-intake.md
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
## Summary
|
|
@@ -57,6 +58,37 @@ rows on every write.
|
|
|
57
58
|
If you need to link by a business key (like an employee XID), **resolve that key to a uuid in your
|
|
58
59
|
caller first** (e.g. build a `key → uuid` map), then send `{uuid}`.
|
|
59
60
|
|
|
61
|
+
## Per-API overrides (`Apis_RecordFields`)
|
|
62
|
+
|
|
63
|
+
The base identifier flags above (`Core.RecordFields.isIdentifier`) can be **overridden per API**
|
|
64
|
+
via rows in a tenant's `Apis_RecordFields` table, read by the V2 resolver's override block
|
|
65
|
+
(`V2.php` ~6926–6936; child policy in `getChildPolicy()` ~8019–8041; loader ~8526–8538). Two
|
|
66
|
+
columns matter:
|
|
67
|
+
|
|
68
|
+
- **`overrideIsIdentifier`** — overrides whether the field is a searchable identifier for that API.
|
|
69
|
+
- **`overrideChildPolicy`** — overrides link-vs-create for the nested record (e.g. `MATCH_CREATE`
|
|
70
|
+
= match an existing child by identifier, else create it).
|
|
71
|
+
|
|
72
|
+
> **Trap: a `NULL` `overrideIsIdentifier` is read as "not an identifier", not "no opinion".**
|
|
73
|
+
> The override block evaluates `!$row->overrideIsIdentifier`, so a `NULL` **unsets** the field
|
|
74
|
+
> from the API's searchable identifiers even when the base `Core.RecordFields` row has
|
|
75
|
+
> `isIdentifier=1`. Any `Apis_RecordFields` row created **only** to set a child policy
|
|
76
|
+
> (leaving `overrideIsIdentifier` NULL) will silently strip that field as an identifier. The
|
|
77
|
+
> nested object then has no searchable identifier and the write fails **EV-12 VALIDATION**
|
|
78
|
+
> (`V2.php:7094`) — the `MATCH_CREATE` that should have saved it never runs on that path.
|
|
79
|
+
> Correct the data with an explicit `overrideIsIdentifier = 1`, or (systemic fix, not yet done)
|
|
80
|
+
> change the code so only an explicit `0` removes an identifier and `NULL` means "no override".
|
|
81
|
+
|
|
82
|
+
**Build divergence warning.** The `Apis_RecordFields` override feature (reading
|
|
83
|
+
`overrideIsIdentifier`/`overrideChildPolicy`) was introduced 2026-03-20 (api2 commit `624146d`
|
|
84
|
+
"Cache custom fields and API override lookups"). Environments on **older** builds ignore the
|
|
85
|
+
override and keep the base identifier, so a tenant with identical Core metadata + override rows +
|
|
86
|
+
data can behave differently across environments purely by build version. A NULL-`overrideIsIdentifier`
|
|
87
|
+
override row that works on prod (older build) will start throwing EV-12 once prod gets the newer
|
|
88
|
+
build — audit such rows before/with the deploy. First seen in AIG (TRUE-79978): prod resolved,
|
|
89
|
+
beta/QA threw EV-12 on the injected `entitlementFulfillmentType` — see
|
|
90
|
+
[AIG entitlement intake](../../../clients/aig/features/entitlement-intake.md).
|
|
91
|
+
|
|
60
92
|
## Gotcha
|
|
61
93
|
|
|
62
94
|
- **Nested write with only a non-identifier field silently creates duplicates.** This is a
|
|
@@ -68,6 +100,11 @@ caller first** (e.g. build a `key → uuid` map), then send `{uuid}`.
|
|
|
68
100
|
|
|
69
101
|
## Change history
|
|
70
102
|
|
|
103
|
+
- 2026-07-09 — Documented the per-API `Apis_RecordFields` override layer (`overrideIsIdentifier`,
|
|
104
|
+
`overrideChildPolicy`) and the trap that a `NULL` `overrideIsIdentifier` is read as "not an
|
|
105
|
+
identifier" (`!$row->overrideIsIdentifier`) — an override row set only for a child policy
|
|
106
|
+
silently strips the field as an identifier → EV-12 at `V2.php:7094`. Added the build-divergence
|
|
107
|
+
warning (override feature added 2026-03-20, commit `624146d`). Surfaced by AIG TRUE-79978. (mhammontree)
|
|
71
108
|
- 2026-07-07 — Documented that api2 nested-relationship writes match an existing child **only** by
|
|
72
109
|
identifier (`uuid`); a non-unique custom field is not a match key and forces a new-record insert.
|
|
73
110
|
Correct pattern is `contact: {uuid}` (resolve business keys to uuid in the caller). Surfaced by
|
|
@@ -78,3 +115,5 @@ caller first** (e.g. build a `key → uuid` map), then send `{uuid}`.
|
|
|
78
115
|
- [api2 architecture](../architecture.md) — the V2 CRUD engine (`processRoutePairs`).
|
|
79
116
|
- [Prudential device import + contact linking](../../../clients/prudential/features/device-information-import-and-contact-linking.md)
|
|
80
117
|
— the production incident that exposed this behavior.
|
|
118
|
+
- [AIG entitlement intake](../../../clients/aig/features/entitlement-intake.md) — the TRUE-79978
|
|
119
|
+
EV-12 that exposed the `Apis_RecordFields` NULL-`overrideIsIdentifier` trap + build divergence.
|
|
@@ -5,12 +5,13 @@ project: API
|
|
|
5
5
|
client: aig
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-
|
|
8
|
+
updated: 2026-07-09
|
|
9
9
|
owners: ["mhammontree"]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Model/Aig/Entitlement.php
|
|
12
12
|
- dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql
|
|
13
13
|
related:
|
|
14
|
+
- 2.0/apps/api2/features/nested-relationship-writes.md
|
|
14
15
|
- 2.0/apps/api2/architecture.md
|
|
15
16
|
- 2.0/apps/dbchanges2/architecture.md
|
|
16
17
|
- 2.0/apps/_underscore/architecture.md
|
|
@@ -42,7 +43,9 @@ the FK can't resolve and intake fails with **"Missing AIG item ID."** Keeping
|
|
|
42
43
|
- reads `partNumber = payload->saleItem->partNumber` and runs
|
|
43
44
|
`SELECT id FROM Items WHERE partNumber = '{partNumber}' LIMIT 1` to get `$itemId`;
|
|
44
45
|
- uses `$itemId` to load fulfillment **types** (`Items_EntitlementFulfillmentTypes`) and
|
|
45
|
-
**methods** (`Items_EntitlementFulfillmentMethods`) and injects them into the payload
|
|
46
|
+
**methods** (`Items_EntitlementFulfillmentMethods`) and injects them into the payload as
|
|
47
|
+
**name-only** related objects (e.g. `entitlementFulfillmentType: {name: "Repair"}`). AIG
|
|
48
|
+
never sends `entitlementFulfillmentType`; it is populated entirely by this interceptor.
|
|
46
49
|
3. The V2 engine resolves `Entitlement.saleItemId` (FK → `_Model_Client_Item`) from that same
|
|
47
50
|
`saleItem.partNumber`. **No matching `Items` row → unresolvable FK → "Missing AIG item ID".**
|
|
48
51
|
4. `postPost` then emails the contact a Staples Protection Plan registration link
|
|
@@ -118,9 +121,28 @@ this interceptor or use this dual-purpose Items pattern.
|
|
|
118
121
|
codes are **not** in the SaleItemID spreadsheet and must be sourced separately if missing.
|
|
119
122
|
- **No unique key on `Items.partNumber`** — use an anti-join for idempotent loads, not
|
|
120
123
|
`INSERT IGNORE`.
|
|
124
|
+
- **An `entitlementFulfillmentType` EV-12 VALIDATION error is NOT a bad client payload —
|
|
125
|
+
it's this interceptor's injected field failing to resolve.** AIG never sends that field;
|
|
126
|
+
the interceptor injects it as `{name: "Repair"}`. The error means the V2 resolver could not
|
|
127
|
+
find a searchable identifier for the injected object. Root cause (TRUE-79978) was an
|
|
128
|
+
`Apis_RecordFields` override row that stripped `name` as an identifier — see
|
|
129
|
+
[api2 nested-relationship writes → Per-API overrides](../../../2.0/apps/api2/features/nested-relationship-writes.md).
|
|
130
|
+
- **Empty `saleItem.partNumber` silently resolves to the wrong item.** The lookup is
|
|
131
|
+
`WHERE partNumber = '{$partNumber}' LIMIT 1`; when `partNumber` is `""`, it matches item
|
|
132
|
+
`id=10` ("2YR Tablet") — the first row with an empty part number — which carries a "Repair"
|
|
133
|
+
fulfillment type. So a blank part number does not fail loudly; it injects a spurious
|
|
134
|
+
name-only fulfillment type. The interceptor should guard an empty/failed lookup before
|
|
135
|
+
injecting (not yet fixed).
|
|
121
136
|
|
|
122
137
|
## Change history
|
|
123
138
|
|
|
139
|
+
- 2026-07-09 — TRUE-79978: root-caused an `entitlementFulfillmentType` EV-12 on beta/QA. The
|
|
140
|
+
field is injected (name-only) by `prePost`; the beta `Client_Aig.Apis_RecordFields` override
|
|
141
|
+
row (apiId=2, recordFieldId=1202) had `overrideIsIdentifier=NULL` + `overrideChildPolicy=MATCH_CREATE`,
|
|
142
|
+
which the newer V2 build reads as "not an identifier", stripping `name` → no searchable id →
|
|
143
|
+
EV-12. Unblocked with a manual one-row beta data fix (`SET overrideIsIdentifier=1`); prod runs
|
|
144
|
+
an older build without the override feature so it still resolves. See the api2 feature doc for
|
|
145
|
+
the shared resolver behavior. (mhammontree)
|
|
124
146
|
- 2026-06-19 — Documented intake flow; loaded 2,200 new SaleItem codes (1,700 STS `ASI-*`/`SM-*`
|
|
125
147
|
+ 500 unique numeric `SA`) into `Client_Aig.Items` via TRUE-79534, fixing "Missing AIG item ID"
|
|
126
148
|
on the new `ASI-*` scheme. Follow-up to TRUE-79441. (mhammontree)
|
package/package.json
CHANGED