toga-ai 1.0.423 → 1.0.425

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,8 +6,8 @@ project: TOGa Blox
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-07-21
10
- owners: [jcardinal]
9
+ updated: 2026-07-23
10
+ owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - toga-blox/.github/workflows/publish.yml
13
13
  - toga-blox/src/utils/getFontAwesomeIcon.tsx
@@ -35,6 +35,26 @@ resolve in a consuming app's build, or touching `publish.yml`.
35
35
  - **Every** branch (including `_production`) publishes a **prerelease** version
36
36
  `X.Y.Z-<mode>.<run>` under dist-tag `<mode>`. There is no longer a production special case.
37
37
  Verified live: `production:1.0.315-production.96`, `sandbox-client:1.0.315-sandbox-client.97`.
38
+ Observed 2026-07-23: `sandbox-client:1.0.317-sandbox-client.101`, `sandbox-dev:1.0.316-sandbox-dev.102`.
39
+
40
+ ### Version derivation (CI owns the suffix; dev owns only the base)
41
+
42
+ - CI reads `version` from `package.json`, **strips any `-<suffix>`**, does **`patch + 1`**, then
43
+ appends `-<mode>.<GITHUB_RUN_NUMBER>`. So base `1.0.317` on `_sandbox-client` run 104 publishes
44
+ `1.0.318-sandbox-client.104` under dist-tag `sandbox-client`.
45
+ - **The developer controls only the BASE version** committed in `package.json` on the feature
46
+ branch. **Never hand-write the `-<mode>.N` prerelease suffix** — CI generates it. There is no
47
+ manual `npm publish`; CI is the only publisher.
48
+
49
+ ### Deploy procedure (e.g. `_sandbox-client`)
50
+
51
+ 1. Ensure a clean tree; optionally bump the base version in `package.json` on the feature branch.
52
+ 2. Merge the feature branch into `_sandbox-client`, then push (this triggers the CI publish).
53
+ 3. Monitor with `gh run watch`; verify with `npm dist-tag ls @agilant/toga-blox`.
54
+ 4. Consuming apps (e.g. `toga25-supply` on `_sandbox-client`) install from the `sandbox-client`
55
+ dist-tag or a pinned `1.0.x-sandbox-client.N` version and must **re-install** to pick up a new
56
+ build. The `deploy-sandbox-client` project skill (`.claude/skills/` in this repo) drives this
57
+ procedure end-to-end.
38
58
 
39
59
  ### What was removed (and why it's safe)
40
60
 
@@ -66,6 +86,12 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
66
86
  - **Publish before build.** `npm install @agilant/toga-blox@<mode>` fails `ETARGET No matching
67
87
  version` in the app build until this repo has published that channel. GitHub Actions runs the
68
88
  workflow version *on the pushed branch*, so the branch must carry the current `publish.yml`.
89
+ - **`publish.yml` has diverged across branches (2026-07-23).** The dynamic `_*` wildcard trigger
90
+ lives on `_sandbox-client`, but older/feature branches (e.g. `feature-new-table`) still carry the
91
+ *old* `publish.yml` that triggers only on `_production/_gamma/_beta`. Because Actions runs the
92
+ copy on the pushed branch, a new `_<mode>` channel will **not** publish unless the branch you push
93
+ carries the wildcard-trigger version. When standing up a new channel, merge from a branch that has
94
+ the dynamic `publish.yml` (or update it first).
69
95
  - **FontAwesome v7 `style`-prop TS2322** (fixed 2026-07-21 in `getFontAwesomeIcon.tsx`, and a
70
96
  reusable pattern for any consumer). FA v7 types the `FontAwesomeIcon` `style` prop as
71
97
  `CSSProperties & CSSVariables`, where `CSSVariables` requires a `--fa-*` custom-property index
@@ -78,6 +104,11 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
78
104
  the pattern the consuming apps should follow (see the commerce workflow's secret-hygiene note).
79
105
 
80
106
  ## Change history
107
+ - 2026-07-23 — Documented CI version derivation (strip suffix → patch+1 → append `-<mode>.<run>`;
108
+ dev owns only the base version, never the suffix) and the `_sandbox-client` deploy procedure now
109
+ driven by the `deploy-sandbox-client` project skill. Recorded that `publish.yml` has diverged
110
+ across branches: the dynamic `_*` trigger is on `_sandbox-client` while older/feature branches
111
+ still trigger only on `_production/_gamma/_beta`. (apeterson)
81
112
  - 2026-07-21 — Made `publish.yml` fully dynamic: trigger `_*`, branch→dist-tag by stripping the
82
113
  leading underscore (mode == channel 1:1), every branch (incl. `_production`) publishes a
83
114
  `X.Y.Z-<mode>.<run>` prerelease; removed the `_production`→`latest` special case (workflow no
@@ -8,5 +8,5 @@
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
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 |
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 | > **⚠ DEPLOYING beta→production, NOT YET PROD-VERIFIED (as of 2026-07-23).** TRUE-79533 is > beta-verified (PM Paulina tested the WH purchase flow on beta) and | _underscore/Model/Rate/Entitlement.php, _underscore/Model/Client/Entitlement.php, _underscore/Model/Client/Address.php, dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql, dbchanges2/Core/2026-07-22a - EntitlementServiceAddressIdField.sql, dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql, test/@Mark/Rate/verify_wholehome_per_address_guard.php |
11
+ | [Rate Whole Home Warranty Per-Address Purchase Guard](features/whole-home-warranty-purchase-guard.md) | 2.0 | > **⚠ DEPLOYING beta→production, NOT YET PROD-VERIFIED (as of 2026-07-23).** TRUE-79533 is > beta-verified (PM Paulina tested the WH purchase flow on beta) and | _underscore/Model/Rate/Entitlement.php, _underscore/Model/Client/Entitlement.php, _underscore/Model/Client/Address.php, 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. | |
@@ -14,7 +14,7 @@ files:
14
14
  - _underscore/Model/Client/Address.php
15
15
  - dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql
16
16
  - dbchanges2/Core/2026-07-22a - EntitlementServiceAddressIdField.sql
17
- - dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql
17
+ - dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql
18
18
  - test/@Mark/Rate/verify_wholehome_per_address_guard.php
19
19
  related:
20
20
  - clients/rate/profile.md
@@ -32,11 +32,12 @@ related:
32
32
  > **shared-interceptor merge hazard** gotcha and the **dangling-pin** / **unguarded prod
33
33
  > interceptor** gotchas before merging to `_production`.
34
34
  >
35
- > **🚨 LAUNCH BLOCKER (added 2026-07-23, tcox) — the standard `serviceAddressId` field has NO
36
- > READ ACL grant.** The custom→standard redesign shipped the column + `Core.RecordFields` row
37
- > but **not** the `AclFieldPermissions` read grant, so `GET /v2/entitlements?fields=serviceAddressId`
38
- > returns **403 `EZ-2`** for roles 1/2/4. Prod does **not** launch clean without the new
39
- > `Client/2026-07-23a` field-permission migration below — see
35
+ > **🚨 LAUNCH BLOCKER — RESOLVED (migration built 2026-07-23, mhammontree; found 2026-07-23,
36
+ > tcox) — the standard `serviceAddressId` field had NO READ ACL grant.** The custom→standard
37
+ > redesign shipped the column + `Core.RecordFields` row but **not** the `AclFieldPermissions`
38
+ > read grant, so `GET /v2/entitlements?fields=serviceAddressId` returned **403 `EZ-2`**. The fix
39
+ > now ships as **`dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql`**
40
+ > (committed `b74f42a` on TRUE-79533) — see
40
41
  > **[Launch blocker: standard field needs a READ ACL grant](#launch-blocker-true-79533--the-standard-field-needs-a-read-acl-grant)**.
41
42
 
42
43
  ## Summary
@@ -144,7 +145,7 @@ during backend review (Jeff). This is the single biggest change since the beta b
144
145
 
145
146
  **The custom→standard redesign shipped the column and the `Core.RecordFields` row but NOT the
146
147
  `AclFieldPermissions` READ grant.** Probe-verified on **beta 2026-07-23**:
147
- `GET /v2/entitlements?fields=serviceAddressId` returns **403 `EZ-2`** for roles 1, 2, and 4
148
+ `GET /v2/entitlements?fields=serviceAddressId` returns **403 `EZ-2`** (no read ACL row)
148
149
  (Entitlements has `Core.Records.aclDatabase = 'CLIENT'`, so the grant lives in each client DB).
149
150
  Without the grant, **production launches with the exact `EZ-2` failure beta hit** — the field is
150
151
  registered but unreadable, so the service cards cannot fetch the pin.
@@ -157,16 +158,20 @@ field is authorized through **`AclFieldPermissions`**, keyed by the **`Core.Reco
157
158
  because the `CustomRecordFields` row for `c_serviceAddressId` was deleted in the standard-field
158
159
  redesign. The read grant now has to be an **`AclFieldPermissions`** row instead.
159
160
 
160
- **Needed migration (proposed, on the TRUE-79533 branch):**
161
- `dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql`, following the
162
- existing `Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql` precedent:
163
- - Resolve field ids via `Core.RecordFields` / `Core.Records` subselects (route `'entitlements'`,
164
- fields `'serviceAddressId'` **and** its sibling `'number'`) — never hardcode ids.
165
- - **Mirror the sibling `number` field's per-role grants**, but force **`isWritable = 0`**:
166
- `serviceAddressId` is written **server-side** by the guard's `postPost`, never by the API caller,
167
- so it must be read-only.
168
- - Guard each INSERT with `NOT EXISTS` (verified against the Client blank DDL:
169
- `AclFieldPermissions` is `UNIQUE (recordFieldId, roleId)`).
161
+ **The migration that ships (built + committed `b74f42a` on TRUE-79533):**
162
+ `dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql`:
163
+ - Resolves field ids via cross-schema `Core.RecordFields` / `Core.Records` subselects (route
164
+ `'entitlements'`, field `'serviceAddressId'`) — never hardcodes ids.
165
+ - **Grants READ only (`isWritable = 0`)** — `serviceAddressId` is written **server-side** by
166
+ `_Model_Rate_Entitlement::postPost` via raw SQL, never by the API caller.
167
+ - **Mirrors the reader ROLE(s) of the sibling standard FK `saleItemId` — which is `role 1` in
168
+ `Client_Rate`** (the same role the earlier `c_serviceAddressId` custom grant used). Although
169
+ the cross-role probe showed roles 1/2/4 all lacked a grant, **role 1 is the only actual reader**
170
+ of this field (Rate's toga2-view service cards), so the grant is role-1 only.
171
+ - Guards each INSERT with `NOT EXISTS` — safe no-op until the field is registered
172
+ (`AclFieldPermissions` is `UNIQUE (recordFieldId, roleId)`).
173
+ - **Scoped to `Client_Rate`, not a `Client/` fan-out** — Rate is the only client whose UI reads
174
+ the field today. A `Client/` fan-out grant is the follow-up if another client's UI needs it.
170
175
 
171
176
  ## Beta data migration — old-column pins are orphaned by the redesign (beta only, NO prod impact)
172
177
 
@@ -289,6 +294,17 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
289
294
  the deleted `2026-07-07a` interceptor registration was redundant — and why keeping it would have
290
295
  been a latent hazard: a fresh environment running both would **double-register**, firing
291
296
  `prePost` twice. **Recommend adding a `NOT EXISTS` guard to the `2026-07-15` file.**
297
+ - **`AclFieldPermissions` has NO `isReadable` column — a ROW grants READ; `isWritable`
298
+ *additionally* grants WRITE** (reusable platform fact, verified against the api2 enforcement
299
+ code, not just schema). In `api2/Component/Api/V2/V2.php`, `getAclFieldPermissions()` (~line
300
+ 5731) sets the field key regardless of `isWritable`, and the read-authorization checks test key
301
+ **presence** (`isset`/`array_key_exists`). Consequences: to make a field API-**readable** you
302
+ insert an `AclFieldPermissions` row (`isWritable = 0` for read-only); to make it **writable** set
303
+ `isWritable = 1`. And promoting a custom (`c_`) field to a **standard** field means its read grant
304
+ must be **re-created** as a standard `AclFieldPermissions` row (keyed by the `Core.RecordFields`
305
+ id) — the old `AclCustomFieldPermissions` grant (keyed by `CustomRecordFields` id) does **not**
306
+ carry over. This is exactly why the `c_serviceAddressId` grant did not cover the standard field
307
+ and the launch-blocker migration above was needed.
292
308
  - **`Core.RecordFields` has no `foreignRecordId` column** — FK resolution for `serviceAddressId`
293
309
  is entirely **model-side** (the `_Model_Client_Entitlement` FK declaration). The RecordField row
294
310
  only carries type/identifier/childPolicy/precision, mirroring sibling FKs `saleItemId`/`vendorId`.
@@ -297,6 +313,17 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
297
313
 
298
314
  ## Change history
299
315
 
316
+ - 2026-07-23 — **READ-ACL grant BUILT — launch blocker resolved** (TRUE-79533, committed `b74f42a`).
317
+ Added `dbchanges2/Client_Rate/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql`: a
318
+ READ-only (`isWritable = 0`) `AclFieldPermissions` grant for the standard `serviceAddressId`,
319
+ resolving field ids cross-schema (route `entitlements`), `NOT EXISTS`-guarded. Final scope
320
+ corrected vs. the earlier proposal: **`Client_Rate` (not a `Client/` fan-out), role 1 only** —
321
+ mirroring the sibling standard FK `saleItemId`'s reader role (role 1 is the sole actual reader,
322
+ the toga2-view service cards). Recorded the reusable platform fact that **`AclFieldPermissions`
323
+ has no `isReadable` column — row presence grants READ, `isWritable` additionally grants WRITE**
324
+ (verified in `api2/Component/Api/V2/V2.php` `getAclFieldPermissions()` ~line 5731), which is why
325
+ a custom→standard promotion must re-create the read grant as an `AclFieldPermissions` row.
326
+ (mhammontree)
300
327
  - 2026-07-23 — **LAUNCH BLOCKER found: the standard `serviceAddressId` field has NO read ACL grant**
301
328
  (tcox). Probe-verified on beta: `GET /v2/entitlements?fields=serviceAddressId` → 403 `EZ-2` for
302
329
  roles 1/2/4 (`aclDatabase=CLIENT`). Standard fields are authorized via **`AclFieldPermissions`**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.423",
3
+ "version": "1.0.425",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",