toga-ai 1.0.441 → 1.0.443
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/architecture.md +11 -1
- package/knowledge/2.0/apps/api2/architecture.md +9 -5
- package/knowledge/2.0/apps/api2/features/v2-api-error-codes.md +5 -1
- package/knowledge/2.0/apps/api2/workflows/codepipeline-codeconnections-deploy.md +17 -1
- package/knowledge/2.0/standards/backend-php.md +32 -2
- package/knowledge/clients/rate/INDEX.md +2 -2
- package/knowledge/clients/rate/features/aig-contract-creation.md +14 -6
- package/knowledge/clients/rate/features/service-card-entitlements.md +17 -2
- package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +48 -19
- package/knowledge/sessions/2026-07-27-true-79533-pin-depth-mhammontree.md +34 -33
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: ["jcardinal", "rgirish", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/_underscore.php
|
|
@@ -341,6 +341,15 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
|
|
|
341
341
|
so folder reads always returned null; fixed. **Remaining follow-up (separate ticket):** `__get()`
|
|
342
342
|
still refetches storage unconditionally, so a dirty read (read-after-assign, pre-save) discards the
|
|
343
343
|
unsaved value.
|
|
344
|
+
- **In-request reads can't see the transaction's own uncommitted nested writes — resolve just-created
|
|
345
|
+
records from the hydrated `$payload`, not a same-request DB read-back.** A `postPost` interceptor
|
|
346
|
+
runs **inside the still-open request transaction**. A fresh `_Query` SELECT — even one forced onto
|
|
347
|
+
the write host (the api2 V2 engine already disables the read host for the whole create block) — does
|
|
348
|
+
**not** reliably resolve the rows the same transaction just inserted, especially nested/child rows.
|
|
349
|
+
Read the in-memory `$payload` node hydrated by `getFullModelData()` instead. Pairs with the
|
|
350
|
+
lazy-transaction gotcha below. (TRUE-79533: the Rate WH service-address pin — fingerprint of a lost
|
|
351
|
+
pin is `Addresses.isValidated` and `Entitlements.serviceAddressId` both-or-neither. Relevant code:
|
|
352
|
+
`_underscore/Model/Rate/Entitlement.php`, `api2/Component/Api/V2/V2.php::getFullModelData`.)
|
|
344
353
|
- **`_Database::register()` auto-starts a lazy transaction (since Apr 2 2026, commit `fa7835ed`).** Any code that calls `register()` and then writes to that DB must call `_Database::transactionCommit()` before the request ends — otherwise MySQL silently rolls back all writes when the connection closes. Lazy transactions only materialise on the first write, so read-only callers are unaffected. See `_underscore/Database.php:48`. First discovered when Rate SAML user provisioning silently discarded all new user INSERTs (Jun 2026).
|
|
345
354
|
- **PHP "Unclosed '{'" parse errors report a MISLEADING line number.** When a `.php` file loaded by the autoloader (`Loader.php`) has a dropped/unbalanced brace, PHP reports `Unclosed '{' on line N` where N is the **outermost `class X {` line** and fails at EOF — NOT at the true location of the missing `}`. Worse, because the file loads lazily via the SPL autoloader, the runtime trace points at the **caller** that triggered the autoload (e.g. a `new _Email()` call site), not the broken file. **Triage rule:** for an "Unclosed '{'" error, the real culprit is a missing `}` somewhere between the reported line and EOF of the file that failed to load — run `php -l <file>` (it reports the EOF line) and scan the whole file. **Merge-conflict resolutions are a common source of a single dropped brace** — review the entire merge, not just the one file the error appears to name. (First hit: production 500 EO-1, Jul 2026 — a `}` dropped from `_Email::send()` during merge `685e4a14` surfaced as a trace pointing at the `new _Email()` caller.)
|
|
346
355
|
|
|
@@ -368,6 +377,7 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
|
|
|
368
377
|
stack trace) to API consumers. Not yet done — tracked as a follow-up.
|
|
369
378
|
|
|
370
379
|
## Change history
|
|
380
|
+
- 2026-07-27 — Added the gotcha that **in-request reads cannot see the transaction's own uncommitted nested writes** — a `postPost` interceptor must resolve just-created records from the `getFullModelData`-hydrated `$payload`, not a same-request `_Query` read-back (even on the write host). Found on the Rate WH service-address pin (TRUE-79533). (mhammontree)
|
|
371
381
|
- 2026-06-11 — Documented lazy transaction gotcha in `_Database::register()` (rgirish)
|
|
372
382
|
- 2026-06-25 — Added the Surface platform UI presentation/configuration layer (DB-driven UI config replacing `Page::meta()`, CTO-reviewed AGREE-WITH-ADJUSTMENTS) (jcardinal)
|
|
373
383
|
- 2026-06-29 — Surface made the enforced (un-flagged) presentation path; reserved id blocks renumbered to Records 333–341 / RecordFields 2246–2433 (known seed-vs-provisioned drift). (jcardinal)
|
|
@@ -6,8 +6,8 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
10
|
-
owners: [jcardinal, bala]
|
|
9
|
+
updated: 2026-07-27
|
|
10
|
+
owners: [jcardinal, bala, mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Controller/Index.php
|
|
13
13
|
- api2/Component/Api/V2/V2.php
|
|
@@ -162,9 +162,12 @@ subject ≤80 chars, capitalized, no trailing period, **imperative mood** (rejec
|
|
|
162
162
|
|
|
163
163
|
`Config/production.ini` (and dev configs) contain **plaintext production credentials** —
|
|
164
164
|
Core/Client/Logs DB passwords, **AWS access key id + secret**, and third-party API keys
|
|
165
|
-
(FedEx, UPS, PayPal, NetSuite, Cal.com, AIG, OptimumDesk, VAPI).
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
(FedEx, UPS, PayPal, NetSuite, Cal.com, AIG, OptimumDesk, VAPI). The **`.ebextensions/git.*.json`**
|
|
166
|
+
files (per-env — e.g. `git.json`, `git.sandbox-dev.json`) each carry a **plaintext GitHub Personal
|
|
167
|
+
Access Token**, used by the `.platform/hooks/prebuild/git.sh` clone hook to pull `_underscore` at
|
|
168
|
+
build. These must be **rotated** (treat the committed tokens as compromised) and moved to SSM
|
|
169
|
+
Parameter Store / EB env properties. **Flag this if you touch config or deploy.** (Location +
|
|
170
|
+
remediation only — do not record the token value anywhere.)
|
|
168
171
|
|
|
169
172
|
**Follow-up (deferred, separate ticket): raw exception disclosure to clients.** The error paths that
|
|
170
173
|
surface a caught `\Throwable` (now that Route.php rethrows and the bootstrap guard reports failures)
|
|
@@ -188,4 +191,5 @@ internal paths, schema names, and stack frames to API consumers. Sanitize the cl
|
|
|
188
191
|
or `execute()`.
|
|
189
192
|
|
|
190
193
|
## Change history
|
|
194
|
+
- 2026-07-27 — Sharpened the committed-secret note: the plaintext GitHub PAT lives in the per-env **`.ebextensions/git.*.json`** files (used by the `prebuild/git.sh` clone hook to pull `_underscore`), must be rotated and moved to SSM / EB env properties (location + remediation only, no value). (mhammontree)
|
|
191
195
|
- 2026-07-23 — Documented the now-guarded Core/Logs DB bootstrap in the front controller: the pre-execute block runs before the `execute()` try/catch, the Core Logs schema name is resolved from a `Core.Database` row (`id = CORE_LOGS_DATABASE_ID`) so a name-mismatched local Logs DB reads as missing, and the failure is now wrapped in `try/catch (\Throwable)` returning `INVALID_CONFIGURATION` + Sentry instead of a fatal (guarded no-op rollback, `Database.php:219–226`). Added the deferred raw-getMessage/getTrace client-disclosure follow-up to the Security note. (jcardinal)
|
|
@@ -37,7 +37,7 @@ migration instead of re-deriving it. All field/script ACL rows live in the **CLI
|
|
|
37
37
|
| **EO-1** | Operation failed — identifier "There is no field called 'X' in the '_Model_Client_Y' model" | The DB column **and** `Core.RecordFields` exist, but the **generated model class** `_underscore/Model/Client/<Name>.php` doesn't declare the field. This is the **4th** requirement beyond the 3-file migration — and most often it's a **cross-repo git branch mismatch** (`_underscore` on a branch whose generated model lacks a field the DB/RecordFields already carry) | Declare the field in the generated model class (`public $field = self::FIELD_*`) and put all related repos (`_underscore`, `api2`, `dbchanges2`, `toga2-supply`) on the **same** feature branch — see the ACL doc's writable-field recipe |
|
|
38
38
|
| **EO-1** | Operation failed — **surfaced from a PHP warning/notice, not a real op error** (e.g. "Attempt to read property 'id' on bool", undefined variable) | A latent PHP warning escalates to a 500 because **Sentry's `ErrorHandler` in api2 promotes warnings/notices into thrown exceptions** (see diagnosis note 5). The known instance: in `V2.php::processRoutePairs()` an **unresolved route** leaves the local `$record = false`, and the post-processing payload interceptor layer then dereferenced `$record->id` → warning → 500 | Guard before dereferencing an unresolved record. The fix added a guard clause `if (!$record) return [$rawRequestedRouteName => $outData];` **before** the interceptor/logging layer (so a bad route returns a clean envelope, not a fatal), and initializes `$record = false;` at the top of the `foreach ($lookupByRouteNames ...)` loop so the invalid-HTTP-method / null-`$action` branch can't leave `$record` undefined (an undefined-variable warning would itself escalate to a 500) |
|
|
39
39
|
| **EZ-1** | Unauthorized record/script dispatch | Missing **`AclRecordScripts`** (scripted APIs) or the **`AclRecordPermissions`** four-table chain (records) for the caller's role | Grant `AclRecordScripts` (scripts) or complete the record-CRUD chain |
|
|
40
|
-
| **EZ-2** | Field-level authorization denied (READ) | The field is registered (`Core.RecordFields` present → no `EV-8`) but the caller's role has no **`AclFieldPermissions`** grant to **read** it. The read-side counterpart of `EV-9` (write) | Add the `AclFieldPermissions` row for the role (`isWritable=0` if the field is server-written). For a **custom** `c_` field use `AclCustomFieldPermissions` instead — see the ACL doc's standard-vs-custom table |
|
|
40
|
+
| **EZ-2** | Field-level authorization denied (READ) | The field is registered (`Core.RecordFields` present → no `EV-8`) but the caller's role has no **`AclFieldPermissions`** grant to **read** it. The read-side counterpart of `EV-9` (write). **This applies to the `id` field too:** fetching a record by its numeric `id` (`GET /v2/<record>?fields=id,...`) 403s `EZ-2` if `id` has no read grant — the `id` field is ACL-gated like any other, not implicitly readable | Add the `AclFieldPermissions` row for the role (`isWritable=0` if the field is server-written). For a **custom** `c_` field use `AclCustomFieldPermissions` instead — see the ACL doc's standard-vs-custom table |
|
|
41
41
|
| **EV-5** | Duplicate `transactionId` | The globally-unique `transactionId` was reused | Send a fresh unique `transactionId` per request |
|
|
42
42
|
| **EV-12** | Record's parent not synced | (Fulfill & Ship) POST `/item-fulfillments` when the Sales Order isn't synced into Toga yet | `GET /sales-orders/syncNetsuiteSalesOrder?netsuiteInternalSalesOrderId=<id>` first |
|
|
43
43
|
|
|
@@ -81,6 +81,10 @@ migration instead of re-deriving it. All field/script ACL rows live in the **CLI
|
|
|
81
81
|
|
|
82
82
|
## Change history
|
|
83
83
|
|
|
84
|
+
- 2026-07-27 — TRUE-79533: noted the **`id`-field** case of `EZ-2` — fetching a record by its numeric
|
|
85
|
+
`id` (`GET /v2/addresses?fields=id,...`) 403s `EZ-2` when `id` has no `AclFieldPermissions` read
|
|
86
|
+
grant; the `id` field is ACL-gated like any field. Found fixing Rate's service-card address fetch
|
|
87
|
+
(`dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql`). (mhammontree)
|
|
84
88
|
- 2026-07-24 — Added a second **`EO-1`** case: a latent PHP **warning** (unresolved route →
|
|
85
89
|
`$record->id` on `bool` in `V2.php::processRoutePairs()`, or an undefined loop variable)
|
|
86
90
|
escalates to an HTTP 500 because api2's Sentry `ErrorHandler` promotes warnings/notices into
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: ["jcardinal", "mhammontree"]
|
|
11
11
|
files: []
|
|
12
12
|
related: []
|
|
@@ -30,6 +30,21 @@ the team's process**, decoupled from the app branches; a code deploy does not ru
|
|
|
30
30
|
full feature promotion to an env = apply the `dbchanges2` migrations to that env **and** deploy the
|
|
31
31
|
app-repo `_<env>` branches.
|
|
32
32
|
|
|
33
|
+
> **Not every EB env pulls `_underscore` from its `_<env>` branch — check `git.<env>.json`.** The
|
|
34
|
+
> beta env **`API-Sandbox-Dev`** does **not** deploy `_underscore` from `_beta`. Its
|
|
35
|
+
> `.platform/hooks/prebuild/git.sh` `rm -Rf`s and **re-clones `_underscore` from the branch named in
|
|
36
|
+
> `.ebextensions/git.<env>.json`** — for this env, `git.sandbox-dev.json` → branch **`_sandbox-dev`** —
|
|
37
|
+
> **overwriting the CodePipeline-supplied copy.** So merging a change into `_beta` does nothing for
|
|
38
|
+
> this env; you must push it to **`_sandbox-dev`**. Always confirm the exact env↔branch mapping in the
|
|
39
|
+
> env's `git.<env>.json` before assuming `_<env>` applies.
|
|
40
|
+
|
|
41
|
+
### Debugging a beta env without Sentry — `Logs_<Client>.Api`
|
|
42
|
+
`Logs_<Client>.Api` is a **full server-side request log**: `queryString`, request/response payloads,
|
|
43
|
+
`direction` (`IN`/`OUT`), `instanceId`, and `executionTime`. It is the reliable way to see what a
|
|
44
|
+
beta env actually received and returned when Sentry isn't wired up. (The Rate AIG-contract monitor
|
|
45
|
+
scans this same `Logs_Rate.Api` for failed outbound calls — see
|
|
46
|
+
`clients/rate/features/aig-contract-creation.md`.)
|
|
47
|
+
|
|
33
48
|
## Steps
|
|
34
49
|
1. A push to the watched branch (e.g. `_stage`) triggers the pipeline.
|
|
35
50
|
2. The Source stage uses `CodeStarSourceConnection` actions, each referencing:
|
|
@@ -113,6 +128,7 @@ aws codeconnections get-connection --connection-arn "<CONN_ARN>" --region "$REGI
|
|
|
113
128
|
```
|
|
114
129
|
|
|
115
130
|
## Change history
|
|
131
|
+
- 2026-07-27 — Noted that not every EB env pulls `_underscore` from its `_<env>` branch: **`API-Sandbox-Dev`** re-clones `_underscore` from the branch in `.ebextensions/git.sandbox-dev.json` (**`_sandbox-dev`**), overwriting the pipeline copy — so a `_beta` merge doesn't reach it. Added the `Logs_<Client>.Api` server-side request-log note as the reliable way to debug a beta env without Sentry (TRUE-79533). (mhammontree)
|
|
116
132
|
- 2026-07-14 — Documented the branch model: app repos (`_underscore`/`api2`/`toga2-supply`) deploy from long-lived `_beta`/`_production` branches (not `_main`); api2 pulls `_underscore`'s `_<env>` branch at EB build; `dbchanges2` has only `_main` and its migrations are applied per-env by the team process (not by a code deploy). (mhammontree)
|
|
117
133
|
- 2026-06-18 — Added two EB-instance gotchas surfaced during the TOGa Supply beta/prod label deploys: (1) terminated instances must be **manually re-registered** as LB targets (we don't pay for auto-registration) — until then the new code never serves traffic and looks like deploy-lag; (2) on-instance `composer require` stopgap syntax (no space after the colon, fix root cause by committing `composer.lock`). (mhammontree)
|
|
118
134
|
- 2026-06-16 — Documented after a pipeline (API-QC-Security, account 975050298201) failed every
|
|
@@ -5,8 +5,8 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-
|
|
9
|
-
owners: [jcardinal]
|
|
8
|
+
updated: 2026-07-27
|
|
9
|
+
owners: [jcardinal, mhammontree]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
12
12
|
- ../apps/_underscore/architecture.md
|
|
@@ -542,6 +542,16 @@ $c = 'Hello ' . $world;
|
|
|
542
542
|
|
|
543
543
|
* Always use exceptions to handle unexpected conditions.
|
|
544
544
|
|
|
545
|
+
### Interceptor / model client-error rejections → throw `_Exception_Validation`, not `_Exception`
|
|
546
|
+
|
|
547
|
+
A hard-block raised inside a pre/post interceptor (or model op) to reject a **bad client request**
|
|
548
|
+
must throw **`_Exception_Validation`** (`_underscore/Exception/Validation.php`), which
|
|
549
|
+
`api2/Controller/Index.php` (≈lines 204–222) maps to `DEFINED_MESSAGE_ERROR_BAD_REQUEST` →
|
|
550
|
+
**HTTP 400** with the message carried in the response envelope (so the front end can drive a
|
|
551
|
+
toaster). Throwing a plain `_Exception` yields an **HTTP 500 + stack trace** — wrong for a
|
|
552
|
+
client-input rejection and an information leak. First applied: the three Rate WH hard-blocks
|
|
553
|
+
(missing / invalid / duplicate service address), TRUE-79533.
|
|
554
|
+
|
|
545
555
|
### Comprehensive Error Handling
|
|
546
556
|
|
|
547
557
|
* Implement comprehensive error handling to gracefully manage exceptions and provide meaningful error messages.
|
|
@@ -658,6 +668,26 @@ public static function prePut(&$api, &$payload) {
|
|
|
658
668
|
}
|
|
659
669
|
```
|
|
660
670
|
|
|
671
|
+
#### Interceptor payloads are bounded by request `depth` — a shallow request silently starves a POST interceptor
|
|
672
|
+
|
|
673
|
+
A `POST` payload interceptor (`postPost`) only receives the nested records the **built response
|
|
674
|
+
payload** includes. The V2 engine builds that payload to the request's `depth`/`calcDepth`; a low or
|
|
675
|
+
`-1` `depth` yields a shallow payload (`meta.calcDepth 1`) and any nested node the interceptor reads
|
|
676
|
+
(e.g. `contact.primaryContactAddress.address`) is **absent** — so interceptor logic that depends on
|
|
677
|
+
it silently no-ops. Because a deep-payload dev/manual request passes, this fails **only** for the
|
|
678
|
+
client sending a shallow depth.
|
|
679
|
+
|
|
680
|
+
- **`ApiPayloadInterceptors.minDepth` is the intended floor** (defaults to 3 when NULL; an interceptor
|
|
681
|
+
row can raise it — e.g. the entitlements `postPost` row = 5). **Known latent bug (needs its own
|
|
682
|
+
ticket):** for a `depth=-1` request the clamp did **not** actually deepen the payload — the `WO-1`
|
|
683
|
+
warning logged *"raised to minDepth 5"* while `calcDepth` stayed 1. Until fixed, do not rely on
|
|
684
|
+
`minDepth` to guarantee an interceptor sees its nested nodes.
|
|
685
|
+
- **Rule:** any `postPost`/`prePost` that reads nested payload data must not assume it is present —
|
|
686
|
+
guard for the node, and be aware that a client's shallow `depth` (or a front end that hard-codes
|
|
687
|
+
`depth:-1`) will starve it. First hit: the Rate WH `serviceAddressId` pin + AIG contract block
|
|
688
|
+
(TRUE-79533) — both read the same starved node. Relevant code: `api2/Component/Api/V2/V2.php`,
|
|
689
|
+
`api2/Controller/Index.php`.
|
|
690
|
+
|
|
661
691
|
### API Versioning
|
|
662
692
|
|
|
663
693
|
* Implement a new version (ie: `/v1`, `/v2`, `/v3`) when a major API framework is introduced to manage the new API framework without breaking existing clients.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
| [Rate Monthly Reconciliation Report](features/monthly-reconciliation-report.md) | 1.0 | A monthly cron that emails an Excel reconciliation report covering all Rate subscription sales orders and their linked PayPal payments for the prior calendar mo | worker/crons/notifications/reports/rate/send_monthly_rate_purchases_report.php, worker/schedules/cron.worker.notification.json |
|
|
7
7
|
| [Rate SalesOrder → NetSuite CashSale Export (postPost)](features/netsuite-cashsale-export.md) | 2.0 | Rate sells home-warranty / home-tech-support products. | _underscore/Model/Rate/SalesOrder.php, _underscore/Model/Rate/Item.php |
|
|
8
8
|
| [Rate SAML SSO](features/saml-sso.md) | 2.0 | Rate uses Azure AD as its IdP (`login.rate.com`). | _underscore/Model/Rate/ClientAuthentication.php, saml/Controller/Index.php, toga2-view/src/hooks/useAuthenticationFlow.ts |
|
|
9
|
-
| [Service Card Entitlement Display](features/service-card-entitlements.md) | 2.0 | Rate's home and services pages display one service card per purchased entitlement. | src/components/ServiceCard/ServiceCard.tsx, src/components/ServiceCard/index.ts, src/hooks/useBundleServices.ts, src/hooks/useActiveServices.ts, src/pages/Home/api/homeApi.ts, src/pages/Home/view/HomePage.tsx, src/pages/Home/viewModels/useHomePageViewModel.ts, src/pages/Services/view/ServicesPage.tsx, src/pages/Services/viewModels/useServicePageViewModel.ts, src/api/serviceAddressApi.ts, src/api/apiErrors.ts |
|
|
9
|
+
| [Service Card Entitlement Display](features/service-card-entitlements.md) | 2.0 | Rate's home and services pages display one service card per purchased entitlement. | src/components/ServiceCard/ServiceCard.tsx, src/components/ServiceCard/index.ts, src/hooks/useBundleServices.ts, src/hooks/useActiveServices.ts, src/pages/Home/api/homeApi.ts, src/pages/Home/view/HomePage.tsx, src/pages/Home/viewModels/useHomePageViewModel.ts, src/pages/Services/view/ServicesPage.tsx, src/pages/Services/viewModels/useServicePageViewModel.ts, src/api/serviceAddressApi.ts, src/api/apiErrors.ts, dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql |
|
|
10
10
|
| [Rate Service-Purchase Confirmation Emails (Tech / Warranty)](features/service-purchase-emails.md) | 2.0 | When a Rate customer purchases a service, a confirmation email is sent. | _underscore/Model/Rate/Entitlement.php, worker2/Worker/Notification/EmailTemplate.php, dbchanges2/Client_Rate/2026-06-30a - Rate purchase email templates.sql |
|
|
11
|
-
| [Rate Whole Home Warranty Per-Address Purchase Guard](features/whole-home-warranty-purchase-guard.md) | 2.0 | >
|
|
11
|
+
| [Rate Whole Home Warranty Per-Address Purchase Guard](features/whole-home-warranty-purchase-guard.md) | 2.0 | > **STATUS (2026-07-27): beta-verified end-to-end; promoting to `_production`.** The final blocker > to a correct service-address pin turned out to be a **front | _underscore/Model/Rate/Entitlement.php, _underscore/Model/Client/Entitlement.php, _underscore/Model/Client/Address.php, toga2-view/src/pages/CheckOut/api/checkoutApi.ts, dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql, dbchanges2/Core/2026-07-22a - EntitlementServiceAddressIdField.sql, dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql, test/@Mark/Rate/verify_wholehome_per_address_guard.php |
|
|
12
12
|
| [Rate](profile.md) | 2.0 | Rate is a mortgage/lending client. | |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: [mhammontree, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Rate/Entitlement.php
|
|
@@ -36,11 +36,14 @@ This is a base-`_underscore` interceptor specific to Rate's product (it lives un
|
|
|
36
36
|
`Model/Rate/`); documented here as a Rate client-feature because the behavior and its
|
|
37
37
|
monitoring are Rate-scoped.
|
|
38
38
|
|
|
39
|
-
**As of 2026-07-
|
|
40
|
-
address**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
**As of TRUE-79533 (beta-verified 2026-07-27)** the same `postPost` also persists the validated
|
|
40
|
+
**service address** — now via the **standard** `Entitlements.serviceAddressId` FK (the Rate-custom
|
|
41
|
+
`c_serviceAddressId` was promoted to a base-model standard field) plus `Addresses.isValidated=1` —
|
|
42
|
+
wrapped so it never blocks this AIG-contract flow or the confirmation email. For Whole Home Warranty
|
|
43
|
+
purchases, a `prePost` guard also **carrier-normalizes the address onto the payload before save**, so
|
|
44
|
+
the AIG contract is created with the canonical address. Note both this AIG block and the WH pin read
|
|
45
|
+
the **same** nested `contact.primaryContactAddress.address` payload node, so a shallow request
|
|
46
|
+
`depth` starves both — see the WH guard doc's depth-starvation gotcha. See the
|
|
44
47
|
[Whole Home Warranty per-address purchase guard](whole-home-warranty-purchase-guard.md).
|
|
45
48
|
|
|
46
49
|
## How it works
|
|
@@ -107,6 +110,11 @@ creation vs. cancellation. This is an accepted, documented limitation — not an
|
|
|
107
110
|
|
|
108
111
|
## Change history
|
|
109
112
|
|
|
113
|
+
- 2026-07-27 — TRUE-79533 beta-verified: updated the service-address note to the **standard**
|
|
114
|
+
`Entitlements.serviceAddressId` FK (the Rate-custom `c_serviceAddressId` was promoted to a
|
|
115
|
+
base-model standard field), and noted that this AIG block and the WH pin read the **same**
|
|
116
|
+
`contact.primaryContactAddress.address` payload node — so a shallow request `depth` starves both
|
|
117
|
+
(cross-linked the WH guard's depth-starvation gotcha). (mhammontree)
|
|
110
118
|
- 2026-07-23 — Documented that the **AIG-success branch reassigns `$payload->uuid`** (as the contract
|
|
111
119
|
number) inside the shared `postPost`, which silently broke the WH service-address pin on the
|
|
112
120
|
pre-redesign build (pin keyed off the now-reassigned uuid). Fixed by snapshotting the entitlement
|
|
@@ -6,8 +6,8 @@ project: TOGa View Frontend
|
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
10
|
-
owners: ["bala", "tcox"]
|
|
9
|
+
updated: 2026-07-27
|
|
10
|
+
owners: ["bala", "tcox", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- src/components/ServiceCard/ServiceCard.tsx
|
|
13
13
|
- src/components/ServiceCard/index.ts
|
|
@@ -20,6 +20,7 @@ files:
|
|
|
20
20
|
- src/pages/Services/viewModels/useServicePageViewModel.ts
|
|
21
21
|
- src/api/serviceAddressApi.ts
|
|
22
22
|
- src/api/apiErrors.ts
|
|
23
|
+
- dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql
|
|
23
24
|
related:
|
|
24
25
|
- clients/rate/profile.md
|
|
25
26
|
- whole-home-warranty-purchase-guard.md
|
|
@@ -118,6 +119,14 @@ product types would need `detectServiceType` extended or overridden.
|
|
|
118
119
|
`contact.primaryContactAddress` fallback made every unpinned card display the same (latest)
|
|
119
120
|
address because that field is one shared, purchase-overwritten value per contact. An unpinned
|
|
120
121
|
entitlement must render addressless.
|
|
122
|
+
- **The `id` field itself needs a read ACL when the FE fetches records by id (Client_Rate, fixed
|
|
123
|
+
2026-07-24).** `fetchAddressesByIds` issues `GET /v2/addresses?fields=id,line1,...` — but without a
|
|
124
|
+
read grant on the **`Addresses.id` field** the whole call returns **403 `EZ-2`** and no address
|
|
125
|
+
renders (the numeric `id` field is ACL-gated like any other field, not implicitly readable). Fixed
|
|
126
|
+
by `dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql`, granting READ
|
|
127
|
+
(`isWritable=0`) on `Addresses.id` to the same role(s) that already read the sibling display fields
|
|
128
|
+
(`line1` etc.). **Reusable lesson:** whenever the FE reads a record by its numeric `id` field, that
|
|
129
|
+
`id` field needs its own `AclFieldPermissions` read grant.
|
|
121
130
|
- **The services fetch degrades gracefully on field-permission errors.** `apiErrors.ts`
|
|
122
131
|
`isFieldPermissionError()` treats **`EV-8`** (field not registered — `identifiers.field`,
|
|
123
132
|
singular) the **same as `EZ-2`** (no read ACL — `identifiers.fields`, array). When the
|
|
@@ -128,6 +137,12 @@ product types would need `detectServiceType` extended or overridden.
|
|
|
128
137
|
|
|
129
138
|
## Change history
|
|
130
139
|
|
|
140
|
+
- 2026-07-27 — TRUE-79533: recorded that `fetchAddressesByIds`
|
|
141
|
+
(`GET /v2/addresses?fields=id,line1,...`) returned **403 `EZ-2`** because the **`Addresses.id`
|
|
142
|
+
field** had no read ACL grant in `Client_Rate` — the numeric `id` is ACL-gated like any field. Fixed
|
|
143
|
+
by `dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql` (READ, `isWritable=0`, to the
|
|
144
|
+
roles that already read the sibling display fields). Captured the reusable lesson: reading a record
|
|
145
|
+
by its `id` field needs a read grant on that `id` field itself. (mhammontree)
|
|
131
146
|
- 2026-07-23 — TRUE-79533 (FE side): warranty card address now comes from the entitlement's **own**
|
|
132
147
|
`serviceAddressId` pin (fetched by id via `fetchAddressesByIds`), renamed all FE
|
|
133
148
|
`c_serviceAddressId` refs to the standard `serviceAddressId`, and **removed the
|
|
@@ -5,13 +5,14 @@ repo: _underscore
|
|
|
5
5
|
project: _Underscore
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
|
-
status:
|
|
9
|
-
updated: 2026-07-
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: [mhammontree, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Rate/Entitlement.php
|
|
13
13
|
- _underscore/Model/Client/Entitlement.php
|
|
14
14
|
- _underscore/Model/Client/Address.php
|
|
15
|
+
- toga2-view/src/pages/CheckOut/api/checkoutApi.ts
|
|
15
16
|
- dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql
|
|
16
17
|
- dbchanges2/Core/2026-07-22a - EntitlementServiceAddressIdField.sql
|
|
17
18
|
- dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql
|
|
@@ -22,23 +23,20 @@ related:
|
|
|
22
23
|
- ../../../2.0/apps/_underscore/features/address-validation.md
|
|
23
24
|
---
|
|
24
25
|
|
|
25
|
-
>
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
>
|
|
30
|
-
>
|
|
31
|
-
>
|
|
32
|
-
>
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
> now ships as **`dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql`**
|
|
40
|
-
> (committed `b74f42a` on TRUE-79533) — see
|
|
41
|
-
> **[Launch blocker: standard field needs a READ ACL grant](#launch-blocker-true-79533--the-standard-field-needs-a-read-acl-grant)**.
|
|
26
|
+
> **STATUS (2026-07-27): beta-verified end-to-end; promoting to `_production`.** The final blocker
|
|
27
|
+
> to a correct service-address pin turned out to be a **front-end request-`depth` bug**: Rate's
|
|
28
|
+
> checkout was sending `{depth:-1}` on `POST /entitlements`, so V2 built a **shallow** payload
|
|
29
|
+
> (`meta.calcDepth 1`) and `postPost` never received the nested
|
|
30
|
+
> `contact.primaryContactAddress.address` node it pins from — the pin silently no-op'd on every
|
|
31
|
+
> purchase (in a way that still passed in a deep-payload dev context). **Tanner** (toga2-view FE)
|
|
32
|
+
> removed the `depth:-1` override in `checkoutApi.ts createService`; verified on **beta 2026-07-27**
|
|
33
|
+
> — post-fix WH purchases pin `Entitlements.serviceAddressId` + set `Addresses.isValidated=1` with
|
|
34
|
+
> **no backfill**. See the **"low request `depth` starves `postPost`"** gotcha below (and the 2.0
|
|
35
|
+
> framework lesson it points to). `serviceAddressId` is a **standard** FK on the **base**
|
|
36
|
+
> `_Model_Client_Entitlement` (no longer the Rate-custom `c_serviceAddressId`), and its READ ACL
|
|
37
|
+
> grant shipped (`2026-07-23a`, committed `b74f42a`). Prod launches clean (no pre-existing WH
|
|
38
|
+
> entitlements), so **no backfill ships**. See the **shared-interceptor merge hazard** and the
|
|
39
|
+
> **dangling-pin** / **unguarded prod interceptor** gotchas before merging to `_production`.
|
|
42
40
|
|
|
43
41
|
## Summary
|
|
44
42
|
|
|
@@ -330,8 +328,39 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
330
328
|
only carries type/identifier/childPolicy/precision, mirroring sibling FKs `saleItemId`/`vendorId`.
|
|
331
329
|
- **No `_Query` bind API here** — use `_Database::escape()` / int-casts for all interpolated
|
|
332
330
|
values.
|
|
331
|
+
- **Low request `depth` silently starved `postPost` of the address node (FE bug — root cause of the
|
|
332
|
+
final beta pin failures; fixed 2026-07-27).** A V2 `postPost` interceptor only receives the nested
|
|
333
|
+
records the **built response payload** includes. Rate's `toga2-view` checkout was POSTing
|
|
334
|
+
`{depth:-1}` on `POST /entitlements`, so V2 built a shallow payload (`meta.calcDepth 1`) and
|
|
335
|
+
`persistWarrantyServiceAddress()` — which reads `$payload->contact->primaryContactAddress->address`,
|
|
336
|
+
the **same node the AIG contract block reads** — got nothing, so the pin no-op'd on every purchase
|
|
337
|
+
(yet passed in deep-payload dev contexts, masking it). `ApiPayloadInterceptors.minDepth` is meant to
|
|
338
|
+
floor this (entitlements `postPost` row = 5), but the clamp did **not** actually deepen a `depth=-1`
|
|
339
|
+
request — the `WO-1` warning logged *"raised to minDepth 5"* while `calcDepth` stayed 1 (a **latent
|
|
340
|
+
framework bug worth its own ticket**). Fix that shipped: **Tanner** removed the `depth:-1` override
|
|
341
|
+
in `checkoutApi.ts createService` so the default deep payload reaches `postPost`. This starvation
|
|
342
|
+
broke BOTH the WH pin AND the pre-existing AIG contract-creation block (both read that node). See
|
|
343
|
+
the 2.0 framework lesson on payload-interceptor depth starvation.
|
|
344
|
+
- **Resolve the just-created entitlement from the hydrated `$payload`, NOT a DB read-back.** `postPost`
|
|
345
|
+
runs inside the still-open request transaction, so a fresh `_Query` SELECT (even forced onto the
|
|
346
|
+
write host — V2 already disables the read host for the whole create block) does **not** reliably
|
|
347
|
+
resolve the just-created nested rows in-request. Pin from the in-memory `$payload` node (hydrated by
|
|
348
|
+
`getFullModelData` — the same node the AIG block reads), not a read-back. Fingerprint of a lost pin:
|
|
349
|
+
`Addresses.isValidated=1` and `Entitlements.serviceAddressId` are **both-or-neither** (same persist
|
|
350
|
+
block) — check `isValidated` to see whether the pin fired. (General 2.0 framework lesson.)
|
|
333
351
|
|
|
334
352
|
## Change history
|
|
353
|
+
- 2026-07-27 — **Shipped working on beta end-to-end; status draft→active** (TRUE-79533). Found and
|
|
354
|
+
fixed the **final** pin failure: the FE was POSTing `{depth:-1}` on `POST /entitlements`, building a
|
|
355
|
+
shallow V2 payload (`calcDepth 1`) that **starved `postPost`** of the nested
|
|
356
|
+
`contact.primaryContactAddress.address` node (same node the AIG block reads), so the pin silently
|
|
357
|
+
no-op'd on every purchase — and `ApiPayloadInterceptors.minDepth` (=5) did **not** actually deepen a
|
|
358
|
+
`depth=-1` request (latent framework bug, own ticket). **Tanner** (toga2-view FE, kb-username not yet
|
|
359
|
+
known) removed the `depth:-1` override in `checkoutApi.ts createService`; verified on beta 2026-07-27
|
|
360
|
+
— WH purchases now pin `serviceAddressId` + set `Addresses.isValidated=1`, no backfill. Also recorded
|
|
361
|
+
the general lesson that `postPost` must resolve just-created records from the hydrated `$payload`, not
|
|
362
|
+
a same-request DB read-back (uncommitted nested writes aren't visible in-request). (mhammontree, with
|
|
363
|
+
Tanner)
|
|
335
364
|
|
|
336
365
|
- 2026-07-23 — **READ-ACL grant BUILT — launch blocker resolved** (TRUE-79533, committed `b74f42a`).
|
|
337
366
|
Added `dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql`: a
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: session
|
|
3
3
|
slug: true-79533-pin-depth
|
|
4
|
-
title: TRUE-79533 WH service-address pin blocked by depth=-1
|
|
4
|
+
title: TRUE-79533 WH service-address pin — SHIPPED & verified on beta (was blocked by depth=-1)
|
|
5
5
|
author: mhammontree
|
|
6
6
|
repos: [_underscore, dbchanges2, toga2-view]
|
|
7
7
|
framework: "2.0"
|
|
@@ -14,60 +14,61 @@ updated: 2026-07-27
|
|
|
14
14
|
# Session: true-79533-pin-depth
|
|
15
15
|
**Date:** 2026-07-27
|
|
16
16
|
**Project/Repo:** _underscore / dbchanges2 / toga2-view (2.0, client Rate)
|
|
17
|
-
**Task:**
|
|
17
|
+
**Task:** Pin the validated Whole Home Warranty service address (`Entitlements.serviceAddressId`) and render it on beta. **DONE + verified on beta 2026-07-27.** The blocker was the purchase POST sending `depth=-1`, which starved the `postPost` interceptor of the created address id; the front-end dropped `depth` and it now works. Remaining = deploy/merge close-out + knowledge captured.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## What WORKED
|
|
22
22
|
<!-- Include specific file paths and evidence -->
|
|
23
|
-
- **
|
|
24
|
-
-
|
|
25
|
-
- **
|
|
26
|
-
-
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
-
|
|
30
|
-
- **Interceptor config (Rohan-confirmed):** `ApiPayloadInterceptors
|
|
23
|
+
- **END-TO-END VERIFIED ON BETA (2026-07-27):** after Tanner removed `depth` from the FE purchase call, both post-fix purchases pinned on their own, no backfill — ET100068 (`serviceAddressId=67` → 233 S WACKER DR, `isValidated=1`) and ET100067 (`serviceAddressId=66` → 1864 HIGH GROVE LN STE 1900, `isValidated=1`). Everything before the FE deploy is still null (clean cutoff). Paulina is PM-testing beta.
|
|
24
|
+
- **FE fix = drop the `depth` param** at `toga2-view/src/pages/CheckOut/api/checkoutApi.ts` (`createService` no longer sends `{depth:-1}`). Deployed to beta. This reproduces the proven-good no-depth config (ET100057 had pinned the same way pre-fix).
|
|
25
|
+
- **Backend pin** in `_underscore/Model/Rate/Entitlement.php` `postPost`→`persistWarrantyServiceAddress`: reads the created address id from the in-memory hydrated payload (`$payload->contact->primaryContactAddress->address->id`), sets `Addresses.isValidated=1` + `Entitlements.serviceAddressId`. Committed `dff1fb5e`. Already live on beta (that's why ET100057/67/68 pinned).
|
|
26
|
+
- **`serviceAddressId` = STANDARD FK field** on base `_underscore/Model/Client/Entitlement.php:20`.
|
|
27
|
+
- **Display EZ-2 fixed**: `dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql` grants read on `Addresses.id` (roles 1/2/3). Committed `ea1e0fb`; applied+verified on beta.
|
|
28
|
+
- **Hard-blocks → HTTP 400** via `_Exception_Validation` (committed `41a5dc1a`), mapped by `api2/Controller/Index.php:204-222`. (On `TRUE-79533`, NOT yet on beta — see blockers.)
|
|
29
|
+
- **Root cause proven via `Logs_Rate.Api`**: local-FE and beta-FE calls hit the SAME beta instance (`i-0ce5624547cfecc93`); only the request `depth` differed (no-depth → 64 KB deep → pins; `depth=-1` → 644 B shallow, `calcDepth 1`, no `address` node → starved). The `-1` came from the FE (`checkoutApi.ts`), not the interceptor.
|
|
30
|
+
- **Interceptor config (Rohan-confirmed):** `ApiPayloadInterceptors.minDepth` defaults to 3 (null); entitlements `postPost` = 5; `prePost`/sales-orders = null→3.
|
|
31
|
+
- **Knowledge CAPTURED + PUSHED (2026-07-27):** 9 docs — Rate WH guard (draft→active), service-card Addresses.id ACL, aig-contract standard-field correction, api2 EZ-2 (`id` case), and 5 elevated standards/architecture (depth-starvation + `_Exception_Validation` in `2.0/standards/backend-php.md`; uncommitted-nested-write in `_underscore/architecture.md`; `_sandbox-dev` branch map + `Logs.Api` in the api2 deploy workflow; committed-PAT security note in `api2/architecture.md`).
|
|
31
32
|
|
|
32
33
|
## What did NOT work — DO NOT RETRY THESE
|
|
33
34
|
<!-- Exact failure reasons — do not vague-ify -->
|
|
34
|
-
- **Forcing the write host inside `persistWarrantyServiceAddress`** (commit `7b63955d`, removed in `dff1fb5e`): NO EFFECT
|
|
35
|
-
- **The 3-table JOIN lookup** (`Entitlement → Contacts.primaryContactAddressId → ContactAddresses → addressId`) in postPost: returns 0 rows *in-request* even on the writer
|
|
36
|
-
- **Payload-based `$address->id`
|
|
37
|
-
- **Merging `_underscore` to `_beta
|
|
38
|
-
- **"Removed depth" curl test (earlier)**: the edited curl STILL had `depth=-1`
|
|
39
|
-
- **Single-host vs multi-host connection theory**: DISPROVEN — same beta instance both ways; the variable
|
|
35
|
+
- **Forcing the write host inside `persistWarrantyServiceAddress`** (commit `7b63955d`, removed in `dff1fb5e`): NO EFFECT — V2 already disables the read host for the whole create block (`V2.php:5024`).
|
|
36
|
+
- **The 3-table JOIN lookup** (`Entitlement → Contacts.primaryContactAddressId → ContactAddresses → addressId`) in postPost: returns 0 rows *in-request* even on the writer (resolves only post-commit). Superseded by the payload approach.
|
|
37
|
+
- **Payload-based `$address->id` while the FE sent `depth=-1`**: null — the shallow payload (`calcDepth 1`) has NO `contact.primaryContactAddress.address` node. Code was correct; depth was the cause.
|
|
38
|
+
- **Merging `_underscore` to `_beta`**: does NOT deploy to beta — API-Sandbox-Dev pulls `_underscore` from **`_sandbox-dev`** via `api2/.platform/hooks/prebuild/git.sh` + `.ebextensions/git.sandbox-dev.json`.
|
|
39
|
+
- **"Removed depth" curl test (earlier)**: the edited curl STILL had `depth=-1` in the logged query string — proved nothing.
|
|
40
|
+
- **Single-host vs multi-host connection theory**: DISPROVEN — same beta instance both ways; the variable was request `depth`.
|
|
41
|
+
- **Relying on `minDepth` to protect the interceptor**: it's set to 5 on the postPost row yet the payload still built at `calcDepth 1` for a `depth=-1` request — the clamp is a latent no-op (framework bug to ticket). The real fix was the FE dropping `depth`.
|
|
40
42
|
|
|
41
43
|
## Not tried yet (candidates for next session)
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
- Run `/capture`.
|
|
47
|
-
- File side tickets: (1) framework `minDepth`-clamp no-op for low/`-1` depth (starves ALL payload interceptors); (2) AIG contract block has the SAME `depth` starvation — silently skipped for beta-FE purchases; (3) SECURITY: plaintext GitHub PAT committed in `api2/.ebextensions/git.*.json` — rotate + move to env config.
|
|
44
|
+
- **Merge `_underscore` `TRUE-79533` → `_sandbox-dev` + deploy** — puts the `_Exception_Validation` 400-fix (`41a5dc1a`) on beta so Paulina's *blocked-purchase* testing (duplicate / unverifiable address) shows a clean 400 toaster instead of a 500 + stack trace. Do this before she hits those paths.
|
|
45
|
+
- **Merge `dbchanges2` `TRUE-79533`** to its deploy branch for prod parity (beta already has the migrations applied).
|
|
46
|
+
- **File the framework ticket:** `ApiPayloadInterceptors.minDepth` clamp does not deepen the built payload for a `depth=-1` request (WO-1 says "raised to 5" but `calcDepth` stays 1) — starves ANY payload interceptor. Plus: AIG contract block had the same starvation; and the committed GitHub PAT in `api2/.ebextensions/git.*.json` needs rotating + moving to SSM.
|
|
47
|
+
- Optionally credit Tanner as an `owners` on the FE-touching Rate doc once his kb-username is known.
|
|
48
48
|
|
|
49
49
|
## Current file state
|
|
50
50
|
| File | Status | Notes |
|
|
51
51
|
|------|--------|-------|
|
|
52
|
-
| `
|
|
52
|
+
| `toga2-view/src/pages/CheckOut/api/checkoutApi.ts` | **FIXED + deployed to beta (Tanner)** | dropped `{depth:-1}` → deep payload reaches `postPost`. THE unblocker; pin now works. |
|
|
53
|
+
| `_underscore/Model/Rate/Entitlement.php` | Committed on `TRUE-79533` (HEAD `41a5dc1a`) | payload pin (`dff1fb5e`, already on beta) + dedup write-host + `_Exception_Validation` (`41a5dc1a`, NOT yet on beta). |
|
|
53
54
|
| `_underscore/Model/Client/Entitlement.php` | Committed | `serviceAddressId` standard FK (line 20). |
|
|
54
|
-
| `dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql` | Committed (`ea1e0fb`) |
|
|
55
|
-
| `dbchanges2` serviceAddressId col + `Core` field + ACL
|
|
56
|
-
|
|
|
57
|
-
| `test/@Mark/Rate/verify_wholehome_per_address_guard.php` | Updated (personal, not in a repo) |
|
|
55
|
+
| `dbchanges2/Client_Rate/2026-07-24a - AddressIdFieldPermission.sql` | Committed (`ea1e0fb`) | Applied on beta. Needs branch merge for prod. |
|
|
56
|
+
| `dbchanges2` serviceAddressId col + `Core` field + ACL | Committed on `TRUE-79533` | Applied on beta manually; needs merge for prod. |
|
|
57
|
+
| team KB (`~/toga-tech/knowledge/…`) | **9 docs published to `_main` (capture)** | Rate WH guard active + 5 elevated standards/arch docs. |
|
|
58
|
+
| `test/@Mark/Rate/verify_wholehome_per_address_guard.php` | Updated (personal, not in a repo) | Not team knowledge. |
|
|
58
59
|
|
|
59
60
|
## Decisions made
|
|
60
|
-
- **Fix depth on the FRONT-END (drop the `depth` param)** rather than bumping the interceptor `minDepth`. Rationale:
|
|
61
|
-
- **Pin from the in-memory hydrated payload, not a DB read-back** (in-request reads can't see uncommitted nested writes
|
|
61
|
+
- **Fix depth on the FRONT-END (drop the `depth` param)** rather than bumping the interceptor `minDepth`. Rationale: reproduces the proven ET100057 no-depth config, no DB config change, and un-starves the AIG block too. Verified working on beta. `minDepth` bump remains the fallback / the systemic guardrail (whose clamp bug is now a separate ticket).
|
|
62
|
+
- **Pin from the in-memory hydrated payload, not a DB read-back** (in-request reads can't see the txn's own uncommitted nested writes).
|
|
62
63
|
- **`serviceAddressId` = STANDARD field** (per Jeff), not `c_serviceAddressId`.
|
|
63
|
-
- **Hard-blocks throw `_Exception_Validation`** → HTTP 400 +
|
|
64
|
+
- **Hard-blocks throw `_Exception_Validation`** → HTTP 400 + message (FE expects 400).
|
|
64
65
|
|
|
65
66
|
## Blockers
|
|
66
|
-
- **
|
|
67
|
-
-
|
|
67
|
+
- **None blocking the pin** — it's verified working on beta.
|
|
68
|
+
- Soft/pending: the `_Exception_Validation` 400-fix (`41a5dc1a`) is not yet on beta (beta deploys `_underscore` from `_sandbox-dev`), so Paulina's blocked-purchase paths currently return 500 until the `_sandbox-dev` merge+deploy. Not a pin blocker.
|
|
68
69
|
|
|
69
70
|
## Exact next step
|
|
70
|
-
>
|
|
71
|
+
> Merge `_underscore` `TRUE-79533` → `_sandbox-dev` and deploy API-Sandbox-Dev (prebuild hook re-clones `_sandbox-dev`), so the `_Exception_Validation` hard-block 400s reach beta before Paulina tests duplicate/invalid-address rejections. Then merge `dbchanges2` `TRUE-79533` for prod parity, and file the framework `minDepth`-clamp-no-op ticket (+ AIG-starvation + committed-PAT follow-ups). Verify a deliberate duplicate-address purchase on beta returns HTTP 400 with the toaster message (not 500).
|
|
71
72
|
|
|
72
73
|
---
|
|
73
74
|
_Saved by /session-save on 2026-07-27_
|
package/package.json
CHANGED