toga-ai 1.0.427 → 1.0.428
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/features/acl-permission-chain.md +13 -1
- package/knowledge/2.0/apps/toga2-view/features/query-string-builder.md +0 -2
- package/knowledge/2.0/apps/toga2-view/features/zip-validation-contact-phone.md +0 -1
- package/knowledge/clients/rate/features/aig-contract-creation.md +14 -2
- package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +31 -0
- package/knowledge/clients/rate/profile.md +3 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-23
|
|
10
10
|
owners: ["jcardinal", "mhammontree", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -56,6 +56,13 @@ writable). Without it the field is rejected on write **and** is unreadable:
|
|
|
56
56
|
- **`AclFieldPermissions` is `UNIQUE (recordFieldId, roleId)`** (Client blank DDL) — guard inserts
|
|
57
57
|
with `NOT EXISTS`. For a **read-only, server-written** field (one populated by a `postPost`
|
|
58
58
|
interceptor rather than the API caller), grant `isWritable = 0`.
|
|
59
|
+
- **A read-only field only needs a grant on the role(s) that actually READ it — find that role by
|
|
60
|
+
mirroring a sibling field of the same kind the readers already consume**, rather than blanket-granting
|
|
61
|
+
every role. E.g. the standard FK `serviceAddressId` mirrored its sibling FK `saleItemId`, whose sole
|
|
62
|
+
reader in `Client_Rate` is **role 1** (the toga2-view service cards) — so a single role-1 read grant
|
|
63
|
+
sufficed, NOT a 1/2/4 fan-out (a cross-role probe had shown roles 1/2/4 all lacked the grant, but only
|
|
64
|
+
role 1 actually reads the field). Grant `isWritable = 0` for a server-written field; resolve the role
|
|
65
|
+
by name/subselect (ids differ per client DB). Beta-proven on Rate `Client_Rate` 2026-07-23.
|
|
59
66
|
|
|
60
67
|
### Standard field vs. custom (`c_`) field — TWO different permission tables
|
|
61
68
|
|
|
@@ -199,6 +206,11 @@ the field.
|
|
|
199
206
|
|
|
200
207
|
## Change history
|
|
201
208
|
|
|
209
|
+
- **2026-07-23** — TRUE-79533: noted that a **read-only field only needs a grant on the role(s) that
|
|
210
|
+
actually read it** — mirror a sibling field of the same kind the readers already consume rather than
|
|
211
|
+
granting every role. The standard FK `serviceAddressId` mirrored `saleItemId`'s reader, **role 1** in
|
|
212
|
+
`Client_Rate` (the toga2-view service cards), so a single role-1 read grant sufficed (not a 1/2/4
|
|
213
|
+
fan-out). (tcox)
|
|
202
214
|
- **2026-07-23** — TRUE-79533: documented the **READ side** of `AclFieldPermissions` — a registered
|
|
203
215
|
field with no grant returns **`EZ-2`** on read (the counterpart of the write code `EV-9`), and
|
|
204
216
|
the **standard vs. custom** split: standard fields use `AclFieldPermissions` (keyed by
|
|
@@ -60,5 +60,3 @@ easy to misread as an API problem.
|
|
|
60
60
|
values to `String` before `urlEncode` — a numeric value (first hit via `fetchAddressesByIds`)
|
|
61
61
|
threw `input.replace is not a function` and silently aborted the request client-side. Added
|
|
62
62
|
regression tests. (tcox)
|
|
63
|
-
</content>
|
|
64
|
-
</invoke>
|
|
@@ -46,4 +46,3 @@ the store when it changed.
|
|
|
46
46
|
field (a different covered property can have a different contact number). Previously the field
|
|
47
47
|
was disabled once the store held a phone, and the post-purchase save made the first purchase lock
|
|
48
48
|
it for every later visit. `save-if-different` retained. (tcox)
|
|
49
|
-
</content>
|
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [mhammontree]
|
|
9
|
+
updated: 2026-07-23
|
|
10
|
+
owners: [mhammontree, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Rate/Entitlement.php
|
|
13
13
|
- _underscore/ApiRequest.php
|
|
@@ -96,9 +96,21 @@ creation vs. cancellation. This is an accepted, documented limitation — not an
|
|
|
96
96
|
address, so an entitlement with no address never even attempts contract creation (and
|
|
97
97
|
never logs a `/contract` call).
|
|
98
98
|
- **Auth-induced failures are blind to the monitor** — see the detection limitation above.
|
|
99
|
+
- **The AIG-success branch REASSIGNS `$payload->uuid`** (`$payload->uuid = _String::generateUuid()`,
|
|
100
|
+
repurposed as the AIG contract number) — and it does so **in the same `postPost`** that pins the WH
|
|
101
|
+
service address. On the pre-redesign build the pin step ran **after** this reassignment and keyed
|
|
102
|
+
off `$payload->uuid`, so **every successful AIG contract silently broke the WH address pin** (the
|
|
103
|
+
pin lookup matched no row). Preserve the ordering fix — snapshot the entitlement uuid **before**
|
|
104
|
+
this branch. See the
|
|
105
|
+
[WH per-address purchase guard](whole-home-warranty-purchase-guard.md) "AIG-success clobbers the
|
|
106
|
+
pin" gotcha (`_underscore` `_beta` `9c2e4b1c`).
|
|
99
107
|
|
|
100
108
|
## Change history
|
|
101
109
|
|
|
110
|
+
- 2026-07-23 — Documented that the **AIG-success branch reassigns `$payload->uuid`** (as the contract
|
|
111
|
+
number) inside the shared `postPost`, which silently broke the WH service-address pin on the
|
|
112
|
+
pre-redesign build (pin keyed off the now-reassigned uuid). Fixed by snapshotting the entitlement
|
|
113
|
+
uuid before the AIG branch (`_underscore` `_beta` `9c2e4b1c`). Cross-linked the WH guard doc. (tcox)
|
|
102
114
|
- 2026-07-07 — `postPost` now also persists the validated service address
|
|
103
115
|
(`Entitlements.c_serviceAddressId`, `Addresses.isValidated=1`), wrapped so it never blocks the
|
|
104
116
|
AIG-contract/email flow; a new WH `prePost` guard carrier-normalizes the address onto the
|
|
@@ -106,6 +106,10 @@ Ticket: TRUE-79533. Business rule owner: PM Paulina.
|
|
|
106
106
|
`Addresses.isValidated = 1` and writes `Entitlements.serviceAddressId`. This block is
|
|
107
107
|
wrapped so a failure never interrupts the AIG-contract creation or the confirmation email
|
|
108
108
|
(see the [AIG contract creation doc](aig-contract-creation.md) — the same `postPost`).
|
|
109
|
+
**The entitlement UUID is snapshotted up front** (`$entitlementUuid = $payload->uuid`) at the
|
|
110
|
+
top of `postPost`, **before** the AIG branch runs — because the AIG-success branch reassigns
|
|
111
|
+
`$payload->uuid` (see the "AIG-success clobbers the pin" gotcha). The pin lookup must use the
|
|
112
|
+
snapshot, not `$payload->uuid`.
|
|
109
113
|
6. All SQL uses `_Database::escape()` / int-casts — `_Query` has **no bind API** in this path.
|
|
110
114
|
|
|
111
115
|
## Field & model: `serviceAddressId` is now a STANDARD field (reviewer Jeff)
|
|
@@ -280,6 +284,22 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
280
284
|
- **`ApiPayloadInterceptors` (Client_Rate) has no `phpMethod` column.** The interceptor method
|
|
281
285
|
is resolved **by convention** from `(prePostProcessing, httpMethod)`: `PRE+POST → prePost`,
|
|
282
286
|
`POST+POST → postPost` (matches the framework `[pre|post][HttpMethod]` convention).
|
|
287
|
+
- **ROOT CAUSE of NULL pins on the pre-redesign build: the pin silently fails whenever the AIG
|
|
288
|
+
contract is created SUCCESSFULLY.** The old `postPost` pinned the entitlement by `$payload->uuid`,
|
|
289
|
+
but the **AIG-success branch reassigns `$payload->uuid = _String::generateUuid()`** (reusing it as
|
|
290
|
+
the AIG contract number) **before** the pin step. So on an AIG success the pin lookup by
|
|
291
|
+
`$payload->uuid` matches **no row**, logs `"TRUE-79533: could not resolve service address"`, and
|
|
292
|
+
gives up — leaving `Entitlements.serviceAddressId` (and, pre-redesign, `c_serviceAddressId`) NULL
|
|
293
|
+
and the purchase's `Addresses.isValidated` NULL (that pair is the fingerprint of a lost pin).
|
|
294
|
+
Pinning is therefore **intermittent — it works only when AIG auth FAILS** (the branch that
|
|
295
|
+
clobbers the uuid never runs). Proven on beta `Client_Rate` 2026-07-23: entitlement 68 pinned,
|
|
296
|
+
69/70/71 unpinned despite an intact Contact→`primaryContactAddressId`→ContactAddresses chain.
|
|
297
|
+
**Fixed in `_underscore` `_beta` commit `9c2e4b1c`**: `postPost` snapshots
|
|
298
|
+
`$entitlementUuid = $payload->uuid` up front, before the AIG branch, and pins by the snapshot.
|
|
299
|
+
**DEPLOY DEPENDENCY:** any environment still running a **pre-redesign** `api2`/`_underscore` build
|
|
300
|
+
loses the address pin every time AIG succeeds; the fix only takes effect on **EB redeploy**
|
|
301
|
+
(api2 pulls `_underscore` at deploy). This is the mechanism behind the COALESCE fallback and the
|
|
302
|
+
dangling-pin gotcha below — the fallback is what keeps the dedup correct while pins are missing.
|
|
283
303
|
- **Dedup is defeated by a DANGLING pin (not just a NULL one).** The COALESCE fallback covers a
|
|
284
304
|
**NULL** `serviceAddressId`, but if `serviceAddressId` points at a **deleted `Addresses` row**,
|
|
285
305
|
the join drops that entitlement and it becomes **invisible to the guard** — a duplicate WH slips
|
|
@@ -324,6 +344,17 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
324
344
|
(verified in `api2/Component/Api/V2/V2.php` `getAclFieldPermissions()` ~line 5731), which is why
|
|
325
345
|
a custom→standard promotion must re-create the read grant as an `AclFieldPermissions` row.
|
|
326
346
|
(mhammontree)
|
|
347
|
+
- 2026-07-23 — **Root cause of intermittent NULL pins + guard hardening confirmed** (tcox). (1) The
|
|
348
|
+
`postPost` service-address pin **silently fails whenever the AIG contract creation SUCCEEDS**,
|
|
349
|
+
because the AIG-success branch reassigns `$payload->uuid` before the pin step, so pinning by
|
|
350
|
+
`$payload->uuid` matches no row (fingerprint: `serviceAddressId` + `Addresses.isValidated` both
|
|
351
|
+
NULL). Proven on beta (entitlement 68 pinned, 69/70/71 not). Fixed in `_underscore` `_beta`
|
|
352
|
+
`9c2e4b1c` (snapshot `$entitlementUuid` up front). Deploy-dependency: pins are lost on AIG success
|
|
353
|
+
on any pre-redesign build until EB redeploy. (2) The dedup blind spot (unpinned entitlements
|
|
354
|
+
invisible to `hasActiveWarrantyAtAddress` — demonstrated live: two active WH at "STE 250" slipped
|
|
355
|
+
through on the old build) is **CLOSED in `_beta` `a9f408bc`** — the dedup joins Addresses via
|
|
356
|
+
`COALESCE(serviceAddressId, ContactAddresses.addressId)`, disables the query cache for the read,
|
|
357
|
+
and documents an accepted TOCTOU. (tcox)
|
|
327
358
|
- 2026-07-23 — **LAUNCH BLOCKER found: the standard `serviceAddressId` field has NO read ACL grant**
|
|
328
359
|
(tcox). Probe-verified on beta: `GET /v2/entitlements?fields=serviceAddressId` → 403 `EZ-2` for
|
|
329
360
|
roles 1/2/4 (`aclDatabase=CLIENT`). Standard fields are authorized via **`AclFieldPermissions`**
|
|
@@ -3,6 +3,7 @@ title: "Rate"
|
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
apps:
|
|
5
5
|
- _underscore
|
|
6
|
+
- api2
|
|
6
7
|
- saml
|
|
7
8
|
- toga2-view
|
|
8
9
|
- worker
|
|
@@ -12,8 +13,8 @@ project: SAML SSO Gateway
|
|
|
12
13
|
client: rate
|
|
13
14
|
type: profile
|
|
14
15
|
status: active
|
|
15
|
-
updated: 2026-
|
|
16
|
-
owners: ["rgirish", "bala", "mhammontree"]
|
|
16
|
+
updated: 2026-07-23
|
|
17
|
+
owners: ["rgirish", "bala", "mhammontree", "tcox"]
|
|
17
18
|
files: []
|
|
18
19
|
related:
|
|
19
20
|
- clients/rate/features/saml-sso.md
|
package/package.json
CHANGED