toga-ai 1.0.427 → 1.0.429
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/nycdoe/features/servicenow-integration.md +59 -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,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: nycdoe
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-23
|
|
10
10
|
owners: [mhammontree, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/nycdoe/import_asn.php
|
|
@@ -248,6 +248,45 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
248
248
|
164785 (internalId 6971859 → `38S0500` synced, 15 ROs) and PO 165751 (`50M7280` + `A3L980`
|
|
249
249
|
NOT synced). PRs: library#831 (merge first) then worker#1670.
|
|
250
250
|
|
|
251
|
+
- **⚠ REGRESSION of the 2026-07-09 multi-PO fix — a cross-wired SO/PO stamp silently drops
|
|
252
|
+
install tickets (diagnosed 2026-07-23; deferred to a one-off backfill, cron NOT changed).**
|
|
253
|
+
The 2026-07-09 fix (library#831 + worker#1670) made Stage 5 resolve item receipts **only**
|
|
254
|
+
Sales-Order-wide via `App_NetSuite::listItemReceiptsCreatedFromSalesOrder($soId)` (which fans
|
|
255
|
+
across the POs from `listPurchaseOrdersCreatedFromSalesOrder($soId)`). It **replaced** —
|
|
256
|
+
rather than augmented — the old by-stamped-PO lookup (`listItemReceiptsCreateFromPurchaseOrder`).
|
|
257
|
+
So when an ASN item's stamped `netSuiteInternalPurchaseOrderId` is **not a child of** its
|
|
258
|
+
stamped `netSuiteInternalSalesOrderId`, the SO-wide fan-out never inspects the stamped PO,
|
|
259
|
+
finds zero receipts, and the cron **silently creates no repair order and raises no error** —
|
|
260
|
+
the same "serials never sync, no error" symptom as the bug it replaced, in a distinct case.
|
|
261
|
+
- **Fingerprint:** an item stamped an SO whose only child PO has no receipt, while the stamped
|
|
262
|
+
PO (often an internalId *lower* than the SO's, i.e. created earlier) carries the real receipt
|
|
263
|
+
with the matching serials.
|
|
264
|
+
- **Root cause of the cross-wired stamp (SME Skyler King confirmed) = a warehouse
|
|
265
|
+
manual-entry error, not automation.** Confirmed case ASN **26215** (customer PO
|
|
266
|
+
`S202641365`): original SO `6901611` processed only `DOE-30HSS1BB00` (qty 1), PO `6901612`,
|
|
267
|
+
item receipt `6910404` (ticketed fine, RO 811791). Two monitors `DOE-62C5GAR1US` (serial
|
|
268
|
+
`VKWD9092`) and `DOE-64B6MAR1UZ` (serial `V600F9M6`) were **manually** added to SO `6901611`
|
|
269
|
+
with a **manually** created PO `6910401`, received on item receipt `6910410`. Separately,
|
|
270
|
+
when the real ASN arrived, the automation created a **duplicate** SO `6996287` (auto-PO
|
|
271
|
+
`6996288`, never used to receive — the serials were already on `6910410`). Net: ASN items
|
|
272
|
+
53905/53914 ended stamped SO=`6996287` (the duplicate) but PO=`6910401` (the manual PO under
|
|
273
|
+
the *original* SO), so SO-wide resolution finds nothing.
|
|
274
|
+
- **SME-confirmed correct end-state:** the units belong to the **original** SO (`6901611`);
|
|
275
|
+
the automation-created duplicate SO `6996287` / PO `6996288` is a NetSuite cleanup item
|
|
276
|
+
(cancel). The owed installs (one repair order per received unit) are **independent** of the
|
|
277
|
+
NetSuite duplicate and are created **TOGa Desk-side only**. Warehouse process has since been
|
|
278
|
+
corrected by training.
|
|
279
|
+
- **Recovery (chosen fix — one-off, not a cron change):** a run-once backfill reuses
|
|
280
|
+
`doeCreateInstallationRepairOrder` verbatim from Stage 5 (`3_create_installation_ticket.php`)
|
|
281
|
+
but scoped to a single ASN (id via CLI arg), resolving receipts from the **UNION** of the
|
|
282
|
+
SO's child POs *and* the item's stamped PO. It is idempotent (only units with
|
|
283
|
+
`togadeskRepairOrderId IS NULL`), guards double dispatch (skips a serial that already has a
|
|
284
|
+
`repair_order` in `db_togadesk`), never adds new `AdvanceShippingNoticeUnits`, is
|
|
285
|
+
serialized-only, and makes no NetSuite/ServiceNow writes. Confirmed on ASN 26215 (created the
|
|
286
|
+
two owed ROs). A **permanent union fix in the cron** (resolve receipts from SO child POs plus
|
|
287
|
+
the stamped PO) was considered but **deferred** — the team chose the one-off because the
|
|
288
|
+
warehouse process error is now prevented by training.
|
|
289
|
+
|
|
251
290
|
## Debugging & DB topology (DOE 1.0 sync)
|
|
252
291
|
|
|
253
292
|
Reference for future DOE investigations — the 1.0 worker cannot run on a dev/Windows box;
|
|
@@ -265,6 +304,14 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
|
|
|
265
304
|
6971859 returned `totalRecords=1`, only `38S0500`.)
|
|
266
305
|
- **`Bridge_NetSuite`** (legacy) has `SalesOrders` / `SalesOrderItems` / `SerialNumbers` but
|
|
267
306
|
**no `PurchaseOrders` table** — POs are pulled live from the NetSuite API.
|
|
307
|
+
- **Stuck-vs-received check, per ASN item:** compare
|
|
308
|
+
`listItemReceiptsCreatedFromSalesOrder($stampedSO)` against
|
|
309
|
+
`listItemReceiptsCreateFromPurchaseOrder($stampedPO)`, dumping each receipt line's `itemName`
|
|
310
|
+
plus its `inventoryAssignment` serials. If the SO-wide call returns 0 but the stamped PO
|
|
311
|
+
returns the receipt with the matching serials, it is the cross-wired-stamp regression above.
|
|
312
|
+
- **`App_Model_Core_AdvanceShippingNotice` does NOT map `dtCreatedInstallationTicket`** — its
|
|
313
|
+
`__get` throws "No field or quick query defined for key …". Read that column via **raw SQL**,
|
|
314
|
+
not the model.
|
|
268
315
|
|
|
269
316
|
## Operating rules when changing this integration
|
|
270
317
|
|
|
@@ -278,6 +325,17 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
|
|
|
278
325
|
of the consumer query; `php -l` every touched file.
|
|
279
326
|
|
|
280
327
|
## Change history
|
|
328
|
+
- 2026-07-23 — Diagnosed a regression of the 2026-07-09 multi-PO fix: Stage 5's SO-wide receipt
|
|
329
|
+
resolution *replaced* the by-stamped-PO lookup, so a cross-wired stamp (an item's stamped PO
|
|
330
|
+
not a child of its stamped SO) silently drops install tickets with no error. Root cause of the
|
|
331
|
+
cross-wire = a warehouse manual-entry error (a duplicate SO was auto-created while the real
|
|
332
|
+
receipt sat under a manually-created PO on the original SO); SME Skyler King confirmed the
|
|
333
|
+
correct end-state (units on the original SO, duplicate SO cancelled, owed installs are TOGa
|
|
334
|
+
Desk-side only). Recovered stuck ASN 26215 with a one-off union-receipt backfill (union of SO
|
|
335
|
+
child POs + the stamped PO; idempotent; no NetSuite/ServiceNow writes) that created the two
|
|
336
|
+
owed ROs; a permanent union fix in the cron was considered but deferred (warehouse process now
|
|
337
|
+
corrected by training). Added a regression gotcha + two debugging notes. No production code
|
|
338
|
+
changed. (mhammontree; SME sking)
|
|
281
339
|
- 2026-07-09 — Fixed silent install-ticket drop when a NetSuite SO is fulfilled across
|
|
282
340
|
multiple POs: Stage 5 now resolves item receipts Sales-Order-wide via new helper
|
|
283
341
|
`App_NetSuite::listItemReceiptsCreatedFromSalesOrder` (fans across all POs from the SO,
|
|
@@ -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