toga-ai 1.0.406 → 1.0.408
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: session
|
|
3
3
|
slug: surface-approve-steptwo
|
|
4
|
-
title: Surface-
|
|
4
|
+
title: Surface-driven Approve enable-gating (step-two + Quad PO) and languageId lookup
|
|
5
5
|
author: apeterson
|
|
6
6
|
repos: [toga25-supply, dbchanges2, _underscore, api2]
|
|
7
7
|
framework: "2.0"
|
|
@@ -13,58 +13,66 @@ updated: 2026-07-21
|
|
|
13
13
|
|
|
14
14
|
# Session: surface-approve-steptwo
|
|
15
15
|
**Date:** 2026-07-21
|
|
16
|
-
**Project/Repo:** toga25-supply + dbchanges2 (2.0)
|
|
17
|
-
**Task:** Move the
|
|
16
|
+
**Project/Repo:** toga25-supply + _underscore + api2 + dbchanges2 (2.0)
|
|
17
|
+
**Task:** Move the sales-order Approve button enable-gating into the metadata-driven Surface layer (Tier-1 FE rule evaluation, "Option B") — Compass/Compass Canada step-two gate and a new Quad "no PO → disabled" gate — plus fix the surface message-translation overlay so per-client disabled-tooltip text works.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## What WORKED
|
|
22
22
|
<!-- Include specific file paths and evidence -->
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
23
|
+
- **Named-predicate escape hatch in the FE rule engine.** `toga25-supply/src/surface/evaluateSurfaceRule.ts` gained a `SURFACE_NAMED_RULES` map + a `{"type": SurfaceNamedRule}` branch; `SurfaceNamedRule = "stepTwoAssigned" | "poNumberEntered"` in `src/surface/types.ts`. `stepTwoAssigned` reads `order.currentStage`/`order.stages`; `poNumberEntered` = `!!getByPath(record, "order.purchaseOrderDetails.purchaseOrder")`. `npx tsc --noEmit` → 0 errors. Verified in the API response: Quad approve element ships `enabledRule: {"type":"poNumberEntered"}`.
|
|
24
|
+
- **Row-actions stage plumbing.** `toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts` now fetches `useFetchApprovalStages` (gated `enabled && approvalsEnabled`) and merges `currentStage`/`stages` onto `record.order`, mirroring the record-modal view model. Both FE surfaces carry stage + PO data.
|
|
25
|
+
- **Compass/Compass Canada approve ENABLED_RULE** (`dbchanges2/Client_Compass/2026-07-21a`, `Client_CompassCanada/2026-07-21a`): rule `{"any":[{"all":[{"field":"order._status","op":"in","value":["pendingInitialApproval"]},{"type":"stepTwoAssigned"}]},{"field":"order._status","op":"in","value":["pendingApproval"]}]}` on both surfaces (record-actions el 17 + row-actions el 29), Admin-scoped, rule JSON in `c_longValue` (value NULL). UPDATE the existing 17a approve ENABLED_RULE row + guarded-INSERT el 29. Confirmed working after enabling the approvals gate + hard reload.
|
|
26
|
+
- **Compass/CC approvals-gate enable** (`Client_Compass/2026-07-21b`, `Client_CompassCanada/2026-07-21b`): flips the `approvalsGate` marker (`config.role='approvalsGate'` on surface `sales-order-record-approvals`) visible client-wide, mirroring Quad's `2026-07-15a`. **This was the fix that made the step-two gate observable** — without it `approvalsEnabled=false`, stage data never fetched, `stepTwoAssigned` fails open. User confirmed "works great."
|
|
27
|
+
- **Quad approve ENABLED_RULE** (`Client_Quad/2026-07-21a`): `{"type":"poNumberEntered"}` client-wide (roleId NULL) on both surfaces. Confirmed disabling correctly after the ENUM fix below.
|
|
28
|
+
- **Quad disabled-tooltip override** (`Client_Quad/2026-07-21b`): `Client_Quad.MessageTranslations` row for message `salesOrder.action.approve.disabled` → "Enter PO Details First" (English). Delivered once the languageId lookup was added.
|
|
29
|
+
- **languageId lookup in the resolver** (`_underscore/Model/Core/Surface.php`): new private `_resolveLanguageId($api)` used by `resolve()` and `metaDebug()` — prefers `id.client.languageId`, falls back to looking up the client `Languages` row by the `id.language` code (`_Model_Client_Language` load-by-`code`). `php -l` clean. This is what makes `MessageTranslations` overlays activate.
|
|
28
30
|
|
|
29
31
|
## What did NOT work — DO NOT RETRY THESE
|
|
30
32
|
<!-- Exact failure reasons — do not vague-ify -->
|
|
31
|
-
- **DO NOT
|
|
32
|
-
- **DO NOT
|
|
33
|
-
- **DO NOT
|
|
34
|
-
- **DO NOT
|
|
35
|
-
- **DO NOT
|
|
33
|
+
- **DO NOT rely on backend Tier-2 to gate approve.** No model implements `resolveSurfaceActionState`; `_resolveTier2State` always returns null. Gate must be a Tier-1 rule evaluated on the FE.
|
|
34
|
+
- **DO NOT expect a `{type:...}` node to evaluate without the named-predicate branch.** `evaluateSurfaceRule` threw `"[surface] Unknown rule node"` until the branch was added.
|
|
35
|
+
- **DO NOT use a field rule like `{field:"order.purchaseOrderDetails.purchaseOrder", op:"ne", value:null}` for PO presence.** When the join row is null the path resolves to `undefined`, and `undefined !== null` → true → button wrongly ENABLED. The frozen grammar can't express presence; use the `poNumberEntered` named predicate.
|
|
36
|
+
- **DO NOT INSERT a client ENABLED_RULE without first widening the `SurfaceOverrides.attribute` ENUM.** Quad never ran `Client/2026-07-15 - SurfaceOverridesAttributeRuleValues.sql`, so `attribute='ENABLED_RULE'` was silently coerced to `''` (MySQL non-strict). Symptom: row present in DB with correct `c_longValue` but `attribute=''`, `enabledRule` absent/wrong in the API response, and `WHERE attribute='ENABLED_RULE'` returns 0 rows. Fix = run the ENUM widener for the client, DELETE the junk `attribute=''` rows, re-run the seed.
|
|
37
|
+
- **DO NOT embed `id.client.languageId` in the JWT (V2.php `/auth/login`) as the fix — ABANDONED.** Implemented then **stashed** by the developer because it only covered the login path (refresh/oauth/api/delegator/encrypted/public would lose it after a ~3600s refresh). Replaced by the `Surface.php` `_resolveLanguageId()` lookup, which is framework-only and holds across all token paths.
|
|
38
|
+
- **DO NOT `replace_all` the `$languageId = (int)\_Object::get(... 'languageId' ...)` line in Surface.php** — the identical line lives inside the new `_resolveLanguageId()` helper and gets clobbered into infinite recursion. (Already corrected.)
|
|
39
|
+
- **DO NOT expect DB changes to show without a hard reload / re-login.** FE surface meta is `staleTime: Infinity` / `gcTime: Infinity` per session (`toga25-supply/src/surface/useFetchSurfaceMeta.ts`). Repeatedly caused "ran the SQL, nothing changed."
|
|
36
40
|
|
|
37
41
|
## Not tried yet (candidates for next session)
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
+
- **Verify Quad tooltip end-to-end** after re-login: disabled Approve should read "Enter PO Details First" and survive a token refresh (the lookup approach should make it stick).
|
|
43
|
+
- **Add the RUN ORDER dependency for Quad**: `Client/2026-07-15 - SurfaceOverridesAttributeRuleValues.sql` (ENUM widener) is missing from Quad's section in `dbchanges2/Core/2026-07-17 - README - RUN ORDER.md`. Also add the six `2026-07-21*` seed files.
|
|
44
|
+
- **Commit the changeset** across the four repos (all currently uncommitted).
|
|
45
|
+
- **Consider caching** the `_resolveLanguageId` lookup per request (currently one small `Languages` load per surface resolve when `id.client.languageId` is absent — i.e. always).
|
|
42
46
|
|
|
43
47
|
## Current file state
|
|
44
48
|
| File | Status | Notes |
|
|
45
49
|
|------|--------|-------|
|
|
46
|
-
| toga25-supply/src/
|
|
47
|
-
| toga25-supply/src/
|
|
48
|
-
| toga25-supply/src/
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
| dbchanges2/
|
|
53
|
-
| dbchanges2/
|
|
54
|
-
|
|
|
50
|
+
| toga25-supply/src/surface/evaluateSurfaceRule.ts | Modified | `SURFACE_NAMED_RULES` (stepTwoAssigned, poNumberEntered) + `{type}` branch. |
|
|
51
|
+
| toga25-supply/src/surface/types.ts | Modified | `SurfaceNamedRule = "stepTwoAssigned" \| "poNumberEntered"`. |
|
|
52
|
+
| toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts | Modified | Fetches approval stages + merges currentStage/stages onto record.order. |
|
|
53
|
+
| toga25-supply/.../useSalesOrderRecordModalLayoutModel.tsx | Reverted hardcode | `approvalsEnabled = isApprovalsSurfaceEnabled(...)` restored (was hardcoded `true` for debugging). |
|
|
54
|
+
| _underscore/Model/Core/Surface.php | Modified | `_resolveLanguageId()` helper; `resolve()`/`metaDebug()` call it. |
|
|
55
|
+
| api2/Component/Api/V2/V2.php | Stashed (reverted) | languageId JWT-embed abandoned; no net change. |
|
|
56
|
+
| dbchanges2/Client_Compass/2026-07-21a - ApproveButtonStepTwoEnabledRule.sql | Created + RUN | UPDATE el17 rule + INSERT el29; Admin; c_longValue. |
|
|
57
|
+
| dbchanges2/Client_CompassCanada/2026-07-21a - ApproveButtonStepTwoEnabledRule.sql | Created | Mirror of Compass; not confirmed run. |
|
|
58
|
+
| dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql | Created + RUN | approvalsGate marker visible; made step-two observable. |
|
|
59
|
+
| dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql | Created + RUN | Mirror. |
|
|
60
|
+
| dbchanges2/Client_Quad/2026-07-21a - ApprovePoNumberEnabledRule.sql | Created + RUN | poNumberEntered client-wide, both surfaces. Needed ENUM widener first. |
|
|
61
|
+
| dbchanges2/Client_Quad/2026-07-21b - ApproveDisabledTooltipPoNumber.sql | Created + RUN | MessageTranslations "Enter PO Details First" (en). |
|
|
62
|
+
| toga-tech/knowledge/.../surface-{resolver,frontend,layer-schema}.md | Published | /capture; languageId note later corrected to the Surface.php lookup + re-pushed. |
|
|
55
63
|
|
|
56
64
|
## Decisions made
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
- **
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
65
|
+
- **Option B (FE Tier-1 evaluation) for the approve gate**, not backend Tier-2 (no-op) or legacy JSON. Rationale: value changes live in-modal; instant FE eval beats a server snapshot.
|
|
66
|
+
- **Rule JSON in per-client SurfaceOverride `c_longValue`** (value NULL; `_castOverride` falls back). Core stays client-neutral.
|
|
67
|
+
- **Named predicates are the sanctioned escape hatch** for logic the frozen field grammar can't express, evaluated FE-side.
|
|
68
|
+
- **Quad PO gate scope:** PO-only (`{"type":"poNumberEntered"}`), signal = isPoComplete (`!!purchaseOrderDetails.purchaseOrder`), client-wide, both surfaces.
|
|
69
|
+
- **languageId fix lives in `Surface.php` (lookup), NOT in auth (JWT embed).** Rejected the V2.php embed because it only covered `/auth/login` and would be lost on refresh across other token paths; the resolver lookup is framework-only and universal.
|
|
62
70
|
|
|
63
71
|
## Blockers
|
|
64
|
-
- None hard.
|
|
72
|
+
- None hard. Quad tooltip needs a re-login to confirm end-to-end; RUN ORDER doc + commits are housekeeping.
|
|
65
73
|
|
|
66
74
|
## Exact next step
|
|
67
|
-
>
|
|
75
|
+
> Re-login to Quad (fresh token) and hard-reload, open a no-PO sales order, and confirm the disabled Approve tooltip reads "Enter PO Details First" and persists after a token refresh — proving the `Surface.php` `_resolveLanguageId()` lookup works across token paths. Then add the `Client/2026-07-15` ENUM-widener dependency (+ the six `2026-07-21*` seeds) to Quad's section of `dbchanges2/Core/2026-07-17 - README - RUN ORDER.md`.
|
|
68
76
|
|
|
69
77
|
---
|
|
70
78
|
_Saved by /session-save on 2026-07-21_
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: true-79533-wholehome-per-address-guard
|
|
4
|
+
title: Rate Whole Home Warranty — one active warranty per validated address
|
|
5
|
+
author: mhammontree
|
|
6
|
+
repos: [_underscore, dbchanges2, test]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: rate
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-07-21
|
|
11
|
+
updated: 2026-07-21
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: true-79533-wholehome-per-address-guard
|
|
15
|
+
**Date:** 2026-07-21
|
|
16
|
+
**Project/Repo:** _underscore + dbchanges2 (2.0), client Rate
|
|
17
|
+
**Task:** TRUE-79533 — implement, verify, deploy-to-beta, and CodeRabbit-harden a backend guard so a Rate Whole Home Warranty (WH) can only be purchased for a carrier-validated address, with one active WH per distinct physical address, globally.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- **prePost guard** in `_underscore/Model/Rate/Entitlement.php` — WH-only (title contains "warranty"); requires a service address; hard-blocks if carrier validation (`_Model_Client_Address::validateAddress`, USPS→FedEx→UPS) returns `success:false`; adopts the carrier-normalized address; then hard-blocks a 2nd active WH on the same address via `hasActiveWarrantyAtAddress()`. Committed on `TRUE-79533`.
|
|
23
|
+
- **postPost `persistWarrantyServiceAddress()`** — after save, sets `Addresses.isValidated=1` and `Entitlements.c_serviceAddressId`; wrapped so it never blocks the AIG-contract/email flow.
|
|
24
|
+
- **Migration** `dbchanges2/Client_Rate/2026-07-07a - WholeHomeWarrantyPerAddressGuard.sql` — adds `c_serviceAddressId` + index, guarded (`NOT EXISTS`) `CustomRecordFields`(191,NUMBER) + `ApiPayloadInterceptors`(191, PRE/POST), and an idempotent backfill of existing WH from the contact primary-address chain. Run against local + **beta** `Client_Rate`.
|
|
25
|
+
- **Verification** `test/@Mark/Rate/verify_wholehome_per_address_guard.php` — **18/18 pass** against the real migrated local `Client_Rate` (helpers + dedup matrix + prePost branches).
|
|
26
|
+
- **CodeRabbit fixes** (committed on `TRUE-79533`): (1) `c_serviceAddressId` declared as FK array form `[FIELD_FOREIGNKEY, FIELDOPT_FOREIGNKEY_MODEL => '\_Model_Client_Address']`; (2) `hasActiveWarrantyAtAddress()` now resolves the address via `COALESCE(c_serviceAddressId, contact primary address)` so an unpinned WH is still deduped. `php -l` clean.
|
|
27
|
+
- **Beta deploy + integration:** `_underscore` `TRUE-79533` pushed to beta, migration run on beta `Client_Rate`. Front-end dev (Tanner) pointed his local `toga2-view` at beta and confirmed the flow "looks good."
|
|
28
|
+
- **Knowledge captured/pushed:** rate `whole-home-warranty-purchase-guard` + `_underscore/address-validation` features, `aig-contract-creation` update, `2.0/standards/backend-testing.md` update (lazy-transaction commit-before-read), and `_underscore/workflows/running-a-2.0-app-locally.md`.
|
|
29
|
+
|
|
30
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
31
|
+
- **Running the full app login locally** never fully succeeded — do not sink more time into it; beta is the test path. The cascade encountered:
|
|
32
|
+
- Stale local `Core_2` (missing `Core/2026-06-10 - TrackingNumberBridges.sql`) still had Records row 41 (`ItemFulfillmentPackages`, model `\_Model_Client_ItemFulfillmentPackage`); V2 loops every `Core.Records` model (`$model::TABLE`, V2.php:3445) and **fatals every request** on the deleted model file. That migration is NON-IDEMPOTENT (fixed ids 317-322 / RecordFields 2171-2200).
|
|
33
|
+
- After refreshing `Core_2`, login returned the silent `Route.php:525` "Failed to determine how to render view" — the API handler returned no response. **Ruled OUT:** production env (confirmed `ENVIRONMENT=dev-markhammontree-laptop` via `?__env=1` die in api2/index.php). **Confirmed CORRECT locally:** `Core_2.Domains` row `http://rate.togaview` (port-stripped, id 580, appId 36, clientId 39); `clientId 39` = local Rate; `dev`→`localhost` DatabaseHosts for client+log+archive; login user `sa-qa-aug21-a@yopmail.com` exists, isActive=1, hasPassword.
|
|
34
|
+
- `VAR=x php ...` (bash env prefix) in **PowerShell** silently no-ops → script falls back to defaults. Use `$env:VAR='x'; php ...`.
|
|
35
|
+
- `_Query` has **no bind/params API** — use `_Database::escape()` (int-cast for ids); do NOT copy the raw-interpolation style in `_Model_Aig_Entitlement`.
|
|
36
|
+
- Standalone `test/` scripts run with **autocommit OFF + a separate read connection** — a write is invisible to a later read until `_Database::transactionCommit(DB_CLIENT)`. Seed → commit → read → assert → commit cleanup.
|
|
37
|
+
|
|
38
|
+
## Not tried yet (candidates for next session)
|
|
39
|
+
- **Local full-app login (if ever needed):** the two remaining unverified gaps are the base **`Logs`** database (DB_LOGS → `Logs.Api`, table `Api` must exist — separate from `Logs_Rate`) and **`Core_2.Apis`** being seeded (secret-bearing, excluded from backups). Provision those before re-attempting.
|
|
40
|
+
- **Optional — api2 relationship expansion for `serviceAddress`:** set `CustomRecordFields.foreignRecordId` → Addresses record (id 13) via a NEW `dbchanges2` migration (the `2026-07-07a` file already ran on beta). Not currently needed; the FK model declaration alone was the CodeRabbit ask.
|
|
41
|
+
- **Optional — extend the verify script** with an unpinned-entitlement case (c_serviceAddressId NULL + a contact primary address at the test address → expect blocked) to behaviorally cover the new fallback.
|
|
42
|
+
|
|
43
|
+
## Current file state
|
|
44
|
+
| File | Status | Notes |
|
|
45
|
+
|------|--------|-------|
|
|
46
|
+
| `_underscore/Model/Rate/Entitlement.php` | Committed (TRUE-79533) | prePost/postPost guard + CodeRabbit fixes (FK decl, COALESCE dedup fallback). NOTE: working tree may show the pre-guard TRUE-79191 version — the guard lives on branch `TRUE-79533`. |
|
|
47
|
+
| `_underscore/Model/Client/Address.php` | Committed (TRUE-79533) | Dead commented `validateAddress` block removed. |
|
|
48
|
+
| `dbchanges2/Client_Rate/2026-07-07a - WholeHomeWarrantyPerAddressGuard.sql` | Committed; run on beta | column+index+guarded registrations+backfill. |
|
|
49
|
+
| `test/@Mark/Rate/verify_wholehome_per_address_guard.php` | Committed | 18/18; does not yet cover the unpinned fallback. |
|
|
50
|
+
| `C:\WWW\true79533.md` | Local working ref | Logic-flow walkthrough with Mermaid diagrams (overlaps the captured guard doc). |
|
|
51
|
+
|
|
52
|
+
## Decisions made
|
|
53
|
+
- **Uniqueness = global per distinct address** (not per-borrower). Rationale: AIG one-plan-per-property; `line2` distinguishes apartment units so a landlord's 2A/2B/2C = 3 allowed WHs. Rejected: per-borrower scoping.
|
|
54
|
+
- **Invalid address → hard-block** the purchase (no entitlement). Rejected: allow-with-flag.
|
|
55
|
+
- **Persist `c_serviceAddressId` on the entitlement** rather than derive from the contact's (mutable) primary address; backfill existing WH. Rationale: reliable dedup + enables per-plan address display.
|
|
56
|
+
- **CodeRabbit #2 → fallback join, NOT pin-throw.** Rationale: making the pin required would reintroduce the block-the-save risk we deliberately designed out; COALESCE fallback closes the blind spot without that risk.
|
|
57
|
+
- **Deploy to beta + FE points local→beta** instead of fully rebuilding the local backend stack. Rationale: local full-app run needs base Logs + Apis seeding; beta has a complete stack; ticket verified independently (18/18).
|
|
58
|
+
|
|
59
|
+
## Blockers
|
|
60
|
+
None for the ticket — code committed on `TRUE-79533`, deployed to beta, integration-confirmed by the FE dev, CodeRabbit review resolved. (Local full-app login remains unresolved but is NOT blocking; beta is the test path.)
|
|
61
|
+
|
|
62
|
+
## Exact next step
|
|
63
|
+
> QA of TRUE-79533 on beta/QA: exercise the multi-unit landlord case (same building, different `line2` → multiple WHs allowed), the duplicate block (2nd active WH on same address rejected), invalid-address hard-block → toaster, tech-support unaffected, and the backfill against pre-existing WH. If QA files anything, use `C:\WWW\true79533.md` + the captured `clients/rate/features/whole-home-warranty-purchase-guard.md` as the reference.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
_Saved by /session-save on 2026-07-21_
|
package/package.json
CHANGED