toga-ai 1.0.969 → 1.0.970

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,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-09-23
9
+ updated: 2026-10-07
10
10
  owners: [jcardinal, kyalamarthi, ajean, mhammontree]
11
11
  files:
12
12
  - tools/index.php
@@ -61,6 +61,16 @@ No per-host `stylePath` subfolder — one `assets/css/style.css`. Uses TOGA Tech
61
61
 
62
62
  **All colour comes from a semantic token layer, `assets/css/theme.css`**, registered **first** in `App_FrameworkIndex_Tools::__construct()` so every other sheet resolves through it. The app supports light / dark / auto; the resolved theme is stamped on `<html>` as `data-theme` by an inline pre-paint script and the user's choice lives in `localStorage` (`toga-tools-theme`) — **not** the DB. The left sidebar and public header stay dark in **both** themes, which is why `toga-brandmark-white.png` still works everywhere. Login shows `toga-horizontal-colored.png` in light and `toga-horizontal-white.png` in dark. See `features/theme-light-dark.md`.
63
63
 
64
+ ## UI standard (internal tools)
65
+
66
+ Developer standard (jcardinal, 2026-09): the team works on wide screens, so tool pages use the space.
67
+
68
+ - **Wide-screen first (~1600px+).** Split-pane / master-detail (list left, detail right), one-row toolbars, meta laid out in grids. Never a vertical stack of full-width cards (the first `/emails` page was rejected for this).
69
+ - **Real gaps + padding between cards**, clear hierarchy, easy-to-reach actions. Opt out of the 960px cap with `.tool--wide` (see Gotchas).
70
+ - **Visible progress for anything that takes more than a moment:** a loading state, a real progress bar (N of total, %), and a live count of the work consumed (API calls, rows, budget left). Prefer honest per-step progress over a bare spinner.
71
+ - **Polished controls:** style buttons/inputs with the theme tokens (`assets/css/theme.css`), never default gray buttons.
72
+ - Check it in the browser (light + dark) before showing it.
73
+
64
74
  ## Home route
65
75
 
66
76
  `/` (`mvc/get.php`) renders a persona-filtered dashboard of available tools (tiles grouped by folder) when logged in, via `App_Nav::visibleGroups()`. When **not** logged in, `/` immediately routes to `/login` (one-step SSO sign-in — no intermediate welcome card). A persona-less but authenticated user gets the dashboard's explicit empty-state (see `features/persona-gated-navigation.md`).
@@ -93,4 +103,5 @@ Create `mvc/<folder>/<tool>/get.php` (copy `mvc/_TEMPLATE/get.php`), add one ent
93
103
  - **Framework-level Sentry error reporting is an open gap.** Prod printed "Sentry is not installed" because `sentry/sentry` was missing from `composer.json` (added `^4.10`; sibling apps use `^4.10`/`^4.19`). But even with Sentry in `vendor/`, the 1.0 framework does **not** auto-require `vendor/autoload.php` (the `App_Talos_S3` pattern `require_once`s it explicitly), so framework-level Sentry reporting for Tools still does not load. Any code path that needs a composer package must `require_once` the autoloader itself.
94
104
 
95
105
  ## Change history
106
+ - 2026-10-07 — Added "UI standard (internal tools)": wide-screen split-pane layout, real gaps, visible progress + live counts, themed controls. (jcardinal)
96
107
  - 2026-09-23 — Added gotchas from the /emails build: input :not chain, 960px cap/.tool--wide, red btn-secondary, Origin/Referer CSRF. (jcardinal)
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-25
9
+ updated: 2026-10-08
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - tools/_/app/developers/centralids.php
@@ -28,7 +28,7 @@ Replaces tracking next central ids in the developer chat. Branches hold hardcode
28
28
  ## How it works
29
29
  - Storage: prod `Core.Parameters` (`id`, `uuid` char36 unique, `key` varchar64 unique, `value` varchar255), keys `NEXT_CORE_RECORDS_ID`, `NEXT_CORE_RECORD_FIELDS_ID`. Read via config `[database_toga2core]` (link `db_toga2core`; tools is prod-only).
30
30
  - Access: Developers folder, **Development Team** persona only — hardcoded `requireAuth` in `get.php` + `post.php`, narrowed in `nav.php`.
31
- - UI: one card per key showing stored value + table's current `MAX(id)`; number box + Save; "Take 1" saves shown value + 1.
31
+ - UI: one card per key showing stored value + table's current `MAX(id)`; number box + Save; "Take 1" saves shown value + 1. "Copy for Claude" copies `Next Core.Records.id = N` / `Next Core.RecordFields.id = N` (labels from `KEYS`) from latest loaded/saved state; disabled until load; unset key → "not set"; `navigator.clipboard`, falls back to textarea + `execCommand('copy')` off secure context.
32
32
  - `App_Developers_CentralIds`: `all()`, `set($key, $newValue, $expected)`. `post.php` actions `load`, `set`.
33
33
  - Save rules: refused if value <= `MAX(id)` of the tracked table or > INT UNSIGNED max. Compare-and-set: `UPDATE ... WHERE value = expected`; missing row → `INSERT IGNORE` with `App_String::generateUuidV4()`. Loser of a race gets "someone changed this value". Table names only from hardcoded `KEYS` const. Each change `error_log`'d with user uuid.
34
34
  - First Save creates the Parameters row — no dbchanges2 file needed.
@@ -54,7 +54,7 @@
54
54
  | [Shipping Carriers & Carrier Account Numbers (records, matching, duplication, deletion)](features/shipping-carriers-and-accounts.md) | Per-client `ShippingCarriers` / `ShippingCarrierAccountNumbers` model, how the syncs match/create carrier rows, and the duplication + blank-carrier + pagination |
55
55
  | [_String helpers — ASCII-safe HTML entity encoding (and the parseBetween trap)](features/string-html-entity-helpers.md) | `_String` static string utilities: use `encodeToAsciiHtmlEntities` for any dynamic text going into a 2.0 email; open when escaping email text or parsing substri |
56
56
  | [Surface FIELD ↔ RecordField binding (maxLength / validationType / isWritable fold-in)](features/surface-field-recordfield-binding.md) | Resolver capability that folds `Core.RecordFields` input-contract metadata (maxLength/validationType/isWritable) onto a surface FIELD element; open when a 2.5 s |
57
- | [DEFERRED — re-key Client.SurfaceOverrides from numeric Core ids to uuids](features/surface-overrides-uuid-rekey.md) | Deferred: `Client.SurfaceOverrides.surfaceId`/`surfaceElementId` still point at Core rows by numeric id; open before a client's first override rows on an env wh |
57
+ | [DECLINED 2026-10-08 — re-key Client.SurfaceOverrides from numeric Core ids to uuids](features/surface-overrides-uuid-rekey.md) | **Decision 2026-10-08 (jcardinal): keep numeric ids; no uuid re-key.** The plan below is not scheduled; see the Layer boundary in [surface-resolver](surface-res |
58
58
  | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | Runtime for the platform Surface UI layer (`_Model_Core_Surface::resolve`/`metaGroup`, replaces `Page::meta`); open for surface resolution, the override cascade |
59
59
  | [Table-View Hyperlink Columns (meta → ACL → computed URL → render)](features/tableview-hyperlink-columns.md) | How a 2.0 table-view column renders its value as a clickable link (`hyperlinkRecordFieldId` → ACL field-read → computed URL → render); open when a configured hy |
60
60
  | [TableView joins (TableViewJoins → SQL) — aliasing, chained multi-hop joins, ACL](features/tableview-joins.md) | How `TableViewJoins` rows become a view's join SQL — aliasing, chained multi-hop joins, ACL gating, and the three ways a view breaks; open before adding/rebasin |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-24
9
+ updated: 2026-10-08
10
10
  owners: [tcox, bala, jcardinal, apeterson]
11
11
  files:
12
12
  - _underscore/Model/Core/Page.php
@@ -41,7 +41,7 @@ So a currency/dollar icon is a **type** problem (fix the field's `type` — Core
41
41
 
42
42
  **⚠ A CUSTOM record field's label row is UNREACHABLE from the grid header (legacy path only).** `Page.php` emits `ClientCustomRecordFieldSettings` in its own block (~L900–935) and keys it by a record route resolved through `$lookupRecordIdFromCustomRecordFieldId` → `$lookupRecordFromRecordId[...]->route`. For a column pointed at a `CustomRecordFields` row, that resolves to an **empty** record-route key — the row lands at `settings.fields[""][<field>][…]`, a bucket `convertColumnTitles` never reads. **A `ClientCustomRecordFieldSettings` `label-singular` row for a custom column is therefore dead metadata**, and no amount of adding/correcting those rows moves the header. (Measured 2026-08-26: two hours lost adding label rows that could never be read.)
43
43
 
44
- > **⚠ SCOPE — this "dead metadata" is the legacy `Page.php` / `toga2-supply` path ONLY. On the `toga25-supply` table path it WORKS.** `toga25-supply` reads its grid headers from `/table-views/meta` (`_Model_Client_TableView::meta()`), which fetches `ClientCustomRecordFieldSettings` directly — so a **base** custom-field column (both context columns NULL) IS labeled by a `ClientCustomRecordFieldSettings` row with `settingId = 5` (`Core.Settings.slug = 'label-singular'`), `isOverridable = 1`. Verified live 2026-09-10: exactly how `Client_Elite` labels its custom unit columns (`c_grade` → "Grade", `c_chargingBrick` → "Charging Brick", …), and how NYCHH's `_transferLocation` column on `units-for-items-for-purchase-orders` gets its "Location" header (`Client_Nychh/2026-09-10c - TransferLocationColumnLabel.sql`). Do **not** move a custom field to a Core `RecordField` just to get a header on a `toga25-supply` grid — the client custom-field settings row is enough. The dead-metadata trap above still holds for the old `Page.php` consumers.
44
+ > **⚠ CORRECTION (2026-10-08): `/table-views/meta` does NOT carry labels.** `_Model_Client_TableView::meta()` reads the settings tables only to decide the context columns (`settings.contextTable` / `contextField`); it returns no label values. Grid header text comes from `/pages/meta` `settings.fields[<record route>][<field>]['label-singular']`. An earlier version of this note said `table-views/meta` fetches `ClientCustomRecordFieldSettings` and labels base custom-field columns directly. That is wrong. The Elite / NYCHH custom-column headers (2026-09-10 seeds, e.g. `Client_Nychh/2026-09-10c - TransferLocationColumnLabel.sql`) were seen live, but the label path that served them is not proven; re-check against `Page::meta` before relying on a client custom-field settings row for a header. Boundary for new apps: see "Layer boundary (2026-10-08)" in [surface-resolver](surface-resolver.md).
45
45
 
46
46
  Where the label must come from instead, for such a column:
47
47
  - **`Core.DefaultRecordFieldSettings`** on the underlying **Core** `RecordField`, or
@@ -103,3 +103,4 @@ Pages --< PageRecords >-- Records --< RecordFields
103
103
 
104
104
  ## Change history
105
105
  - 2026-09-24 — Documented the Core-only label chain (`Pages`→`PageRecords`→`RecordFields`→`DefaultRecordFieldSettings` `label-singular`, setting id 5) and that `Sections` are a separate path listing pages never have. (apeterson)
106
+ - 2026-10-08 — Corrected: `/table-views/meta` returns no labels (header text = `/pages/meta` `settings.fields`). Frozen for old apps per the Layer boundary in surface-resolver (jcardinal)
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: "DEFERRED — re-key Client.SurfaceOverrides from numeric Core ids to uuids"
2
+ title: "DECLINED 2026-10-08 — re-key Client.SurfaceOverrides from numeric Core ids to uuids"
3
3
  framework: "2.0"
4
4
  repo: _underscore
5
5
  project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
- status: deferred
9
- updated: 2026-09-29
8
+ status: deprecated
9
+ updated: 2026-10-08
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - _underscore/Model/Core/Surface.php
@@ -16,7 +16,7 @@ related:
16
16
  - surface-resolver.md
17
17
  ---
18
18
 
19
- Deferred: `Client.SurfaceOverrides.surfaceId`/`surfaceElementId` still point at Core rows by numeric id; open before a client's first override rows on an env whose Core can be rebuilt.
19
+ **Decision 2026-10-08 (jcardinal): keep numeric ids; no uuid re-key.** The plan below is not scheduled; see the Layer boundary in [surface-resolver](surface-resolver.md). Was: `Client.SurfaceOverrides.surfaceId`/`surfaceElementId` still point at Core rows by numeric id; open before a client's first override rows on an env whose Core can be rebuilt.
20
20
 
21
21
  ## Summary
22
22
 
@@ -35,3 +35,4 @@ Planned fix (do before first client use on such an env):
35
35
 
36
36
  ## Change history
37
37
  - 2026-09-29 — Recorded as deferred after the nav-gate uuid fix exposed the same numeric-id risk (jcardinal)
38
+ - 2026-10-08 — Declined: keep numeric ids, no uuid re-key (jcardinal)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-08
10
10
  owners: [jcardinal, apeterson, bala]
11
11
  files:
12
12
  - _underscore/Model/Client/Language.php
@@ -313,6 +313,20 @@ Same discipline as the reserved-id blocks: **read the id out of the target envir
313
313
  - **The meta-group dispatch grant is per-client-DB too.** A 403 EZ-1 on `meta-group` on dev-sandbox was the same mechanism as the original `meta` 403: the `meta-group` RecordScript needs its own per-role `Client.AclRecordScripts` dispatch grant. Verified via toga-db — `meta` script id 25 was granted roles 1/3/4 but `meta-group` (id 27) had **none** until the grant ran. Any new surfaces scripted-API method repeats this: record READ never authorizes dispatch.
314
314
  - **The meta-group grant is seeded per-tenant, not globally — new tenants repeat the 403.** When `meta-group` shipped, the `AclRecordScripts` grant was seeded **only** for `Client_Compass` (recordScriptId → roles 1,3,4). `Client_Quad` and `Client_CompassCanada` had **no** grant for the meta-group recordScriptId at all — their `meta` was granted to client role 1 (why `meta` worked but `meta-group` 403'd for the same user/token). Same-user-different-script + same-script-different-tenant is the fingerprint of this gap. Fix (2026-07-01): two `dbchanges2/Client_<tenant>/` migrations granting meta-group to **client role 1** (parity with each tenant's existing `meta` grant), id-agnostic (resolve `Core.RecordScripts` id by route/method/phpMethod, cross-DB read of `Core.*` per the 2026-06-29c ACL pattern) and re-runnable via `NOT EXISTS`. Surface-layer DB changes ship to the **beta** environment only. ⚠ **Root-cause not yet closed:** the meta-group grant should be added to the `dbchanges2/Client/` **baseline (blank) seed** so newly-cut clients get it automatically — otherwise the next migrated tenant hits the identical 403. Only Compass (already correct), Quad, and CompassCanada are addressed. Also note **role breadth**: Compass exposes surfaces to roles 1,3,4; Quad/CC got role 1 only (parity with their `meta`). If Quad/CC should match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
315
315
 
316
+ ## Layer boundary (2026-10-08)
317
+
318
+ Developer decision (jcardinal, 2026-10-08), platform apps (Desk, IQ, template). Which layer owns what:
319
+
320
+ 1. **Surface owns what users SEE.** `Core.Surfaces` / `SurfaceElements` / `Messages` + Client `MessageTranslations` + Client `SurfaceOverrides`: copy, labels, order, visibility, required, icons, colours. Client changes = `SurfaceOverrides` (client / persona / role); translations = `MessageTranslations`.
321
+ 2. **`*Settings` keeps ONLY preferences:** `language`, `date-format`, `time-format`, `time-zone`, `currency`, `persona`. One shared preferences resolver (to move from api2 `V2::resolveSettingValue` into `_underscore`), honouring `isOverridable`. UI keys are FROZEN, new apps never read them: `label`, `label-singular`, `icon`, `tooltip`, `placeholder`, `sort-order`, `is-required`, `default-value`, `min-required-siblings`, `max-required-siblings`.
322
+ 3. **Record + field names move to `Core.Messages`** (translatable) via message-id link columns on `Core.Records` / `Core.RecordFields`, first seeded from `DefaultRecordSettings` / `DefaultRecordFieldSettings` labels; until seeded, fall back to the settings label.
323
+ 4. **No per-user text overrides.** User choices = preferences, saved views, column order. Add a Surface lock only when a real need appears.
324
+ 5. **`SurfaceOverrides` keep numeric Core ids** in client DBs (no uuid re-key) - see [surface-overrides-uuid-rekey](surface-overrides-uuid-rekey.md).
325
+ 6. **Permissions** (record CRUD + field writable) get their own once-per-session call for new apps instead of `GET /pages/meta`. Shape TBD; table-based preferred; a scripted API needs the developer's OK.
326
+ 7. **Old apps** (`toga2-supply`, `toga2-commerce`) keep `GET /pages/meta` frozen: bug + security fixes only. `toga25-supply` (uses both) moves with the platform apps.
327
+
328
+ Precedence is per layer and never mixed on one value: Surface = base < client < persona < role (language = filter); Settings = Default < Client < Persona < User (< Language where the reader applies it). Rejected: settings absorbs Surface; keep both + a rule. Detail: Desk ADR 0140 (Settings holds preferences only).
329
+
316
330
  ## Related
317
331
 
318
332
  - [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)
@@ -322,3 +336,6 @@ Same discipline as the reserved-id blocks: **read the id out of the target envir
322
336
  - [sales-order-status-filter-surface](sales-order-status-filter-surface.md)
323
337
  - [surface-field-recordfield-binding](surface-field-recordfield-binding.md)
324
338
  - [side-navigation-and-default-route](../../toga25-supply/features/side-navigation-and-default-route.md)
339
+
340
+ ## Change history
341
+ - 2026-10-08 — Added "Layer boundary": Surface owns what users see, `*Settings` = preferences only (jcardinal decision; see Desk ADR 0140) (jcardinal)
@@ -6,12 +6,14 @@ project: SAML SSO Gateway
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-02
9
+ updated: 2026-10-07
10
10
  owners: ["rgirish", "mhammontree"]
11
11
  files:
12
12
  - saml/Controller/Index.php
13
13
  - _underscore/String.php
14
14
  - _underscore/Model/True/ClientAuthentication.php
15
+ - _underscore/Model/Compass/ClientAuthentication.php
16
+ - _underscore/Model/Core/ClientAuthentication.php
15
17
  related:
16
18
  - ../architecture.md
17
19
  - ./gateway-environments-and-redirect-safety.md
@@ -78,6 +80,40 @@ the window. App-side fix: a used-payload table. Gateway-side fix: nonce + iat +
78
80
  payload. Changing `encryptWithKey()` affects every caller — it is also
79
81
  the `?saml=` handoff and the api2 encrypted-user-uuid auth, so this is not a local change.
80
82
 
83
+ ## Planned fix (deferred 2026-09-25)
84
+
85
+ Proposed, **not approved and not built** — deferred by the developer (jcardinal) as too large for
86
+ that moment. Needs a `cto` pass (schema) and a `cso` review before any code.
87
+
88
+ **Rollout — per client, LOG_ONLY then ENFORCE:**
89
+
90
+ 1. Add to `Core.ClientAuthentications_Sso`: `idpEntityIdentifier`, `idpSigningCertificate`,
91
+ `signatureMode` ENUM(`LOG_ONLY`, `ENFORCE`).
92
+ 2. Ship every SSO client in `LOG_ONLY` (verify the IdP signature, log the result, still allow).
93
+ 3. Flip a client to `ENFORCE` after one real login passes verification. No cert on file = refuse.
94
+
95
+ **Turn on for everyone at once (no per-client stage):** reject unsigned assertions; check the
96
+ assertion time window; Audience = our `/meta`; Destination = our `/acs`; RelayState expiry; bind
97
+ RelayState's `domainUuid` to the SSO client (today `_underscore/Model/Core/ClientAuthentication.php`
98
+ accepts any `domainUuid`).
99
+
100
+ **Remove now-known unsafe code in the same change:**
101
+
102
+ - **Compass backdoor:** `_underscore/Model/Compass/ClientAuthentication.php` (~lines 17-20) maps any
103
+ `@togatech.com` NameID straight to one fixed internal user. Combined with Gap 1, any trusted IdP can
104
+ sign in as that user. Remove it.
105
+ - A comment near line ~235 of `saml/Controller/Index.php` holds a **real customer SAMLResponse**
106
+ (personal data). Delete it; never copy its content anywhere.
107
+
108
+ **Inputs still needed (IdP metadata / signing certs):** Compass (Shibboleth IdP — the old commented
109
+ code carries a cert valid to 2031), Rate (Okta metadata URLs — today stored in `entityIdentifier`),
110
+ True + Compass Sandbox Client (Microsoft Entra — app metadata URL needed from the developer).
111
+ `entityIdentifier` is used two ways today (our SP id for Compass/True, the IdP metadata URL for Rate)
112
+ — settle that before adding the new columns.
113
+
114
+ Prod SSO clients in scope (`Core.ClientAuthentications`, type SSO): Compass Group (Production,
115
+ Sandbox Client), CompassCanada, Rate (Production, Beta), True.
116
+
81
117
  ## Gotchas
82
118
 
83
119
  - **Do not describe the gateway as "authenticating" anyone to a client or an auditor.** Until Gap 1
@@ -89,5 +125,6 @@ the `?saml=` handoff and the api2 encrypted-user-uuid auth, so this is not a loc
89
125
  citing this doc as sign-off.
90
126
 
91
127
  ## Change history
128
+ - 2026-10-07 — Added "Planned fix (deferred 2026-09-25)": per-client LOG_ONLY → ENFORCE signature rollout, IdP metadata still needed, Compass `@togatech.com` backdoor + stored SAMLResponse comment to remove (jcardinal)
92
129
  - 2026-10-02 — Added Tools + TOGa Desk as affected consumers; recorded handoff replay (Gap 3) (mhammontree)
93
130
  - 2026-09-25 — Created; recorded that `/acs` verifies no IdP signature and no Audience/Destination/InResponseTo/NotBefore/NotOnOrAfter, and that RelayState uses unauthenticated AES-256-CBC with an attacker-supplied IV (rgirish)
@@ -6,7 +6,7 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-05
9
+ updated: 2026-10-07
10
10
  owners: [tcox, bala, apeterson]
11
11
  files:
12
12
  - src/api/sessionTokens.ts
@@ -96,11 +96,13 @@ Seeds a long-lived `ADMIN_ACCESS_JWT`; stubs `/auth/delegator` → `mock-delegat
96
96
 
97
97
  - On a 401 logout the in-memory zustand stores are not reset, so a store write in the ≤1.5s before the redirect can write `zu-cart` / `order-for-zu` back. Low risk: login runs `resetClientState` (cso NIT, 2026-09-30).
98
98
  - `SENTRY_AUTH_TOKEN` is committed in 25 tracked `.env*` files — treat it as compromised and rotate it. (Never store the value in this KB.)
99
+ - **`GET /users/encryptedUser` permission gap (found 2026-09-25, deferred).** The RecordScript (`Core.RecordScripts` id 16; `_underscore/Model/Client/User.php` ~line 353, `@TODO` with no check) was granted in prod to ordinary roles across many clients (e.g. Compass Base + Manager), likely from `dbchanges2/Core/HISTORIC/2025/2025-01-13 - 55324 - GetEncryptedUser.sql`. Fix = a real permission check in code, not only pulling grants. The `canUserOrderForOtherUser` gate (Gotchas) covers it on beta / client-sandbox — confirm it is live in prod before closing this item.
99
100
  - ~~Confirm api2 authorizes `/auth/delegator` **server-side**~~ — **answered 2026-09-21:** it now re-checks `_Model_Client_User::canUserOrderForOtherUser` at mint time (and `/auth/refresh` re-checks it), so a captured encrypted pair cannot be replayed. See [order-for delegation](../../_underscore/features/order-for-delegation.md).
100
101
  - ~~The parked-token slot is shared with "Order For" — decide precedence~~ — **settled 2026-09-22:** one slot, no nesting; the mode is told apart by the Order For marker, not the slot.
101
102
 
102
103
  ## Change history
103
104
 
105
+ - 2026-10-07 — Open item: `/users/encryptedUser` permission gap found 2026-09-25 (prod grants to ordinary roles); confirm the delegation gate is live in prod. (jcardinal)
104
106
  - 2026-09-30 — Order For merged onto the TRUE-81991 session layer on `_sandbox-client`: parked pair cleared via `AUTH_STORAGE_KEYS`; return and switch use `clearPersistedQueryCache`; a refresh that lost the identity race now returns the current token instead of `null` (stopped a false logout on return); View As `onError` restores the admin's language and logs out if the parked pair is gone. (tcox)
105
107
  - 2026-09-22 — Refresh identity binding upgraded to an epoch bumped on the view-as swap; `Bearer undefined` swap bug fixed. TRUE-81991 (`e546db3d`). (apeterson)
106
108
  - 2026-09-22 — Return moved to the shared `useReturnToOriginalUser` hook (state cleared before any await); mode now tested with `useIsViewAsActive` because Order For parks the same user; View As start calls `endDelegation()` and its cart-clear effect is gated; recorded the 403 EZ-1 when the target is not on the delegation list. (tcox)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.969",
3
+ "version": "1.0.970",
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",