toga-ai 1.0.295 → 1.0.296

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: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
10
- owners: ["jcardinal"]
9
+ updated: 2026-07-09
10
+ owners: ["jcardinal", "bala"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
13
13
  - api2/Component/Api/V2/Response/Response.php
@@ -54,8 +54,27 @@ PersonaApp, UserGlobal, UserApp — each overriding the previous when set. A Cli
54
54
  `isOverridable = 0` **locks** the value against all later layers. For the persona layers, the **first
55
55
  persona** (in the user's persona order) that sets it wins. The resolved code is validated against
56
56
  `Languages.code` (fallback `en`), embedded in the JWT `id.language` claim, and echoed on every
57
- response as `audience.language`. API-credential auth gets no language. Token refresh copies the claim,
58
- so a language change requires re-authentication.
57
+ response as `audience.language`. API-credential auth gets no language.
58
+
59
+ **Language re-resolution on token refresh (user tokens only).** `/auth/refresh` now re-resolves the
60
+ user's language and embeds it in the newly minted tokens, so a language switch takes effect on the
61
+ next hourly refresh **without a full re-login** (previously the refresh path copied the decoded JWT
62
+ payload verbatim and only reset `exp`/`iat`, so a language change was invisible until re-auth). The
63
+ re-resolution runs **only** for user tokens — it reads `id.client.user.uuid` off the token and skips
64
+ entirely for API-credential and public tokens (no language claim). It rebuilds the resolution context
65
+ from the existing token — loads `_Model_Client_User` by `uuid` (→ `userId`), `_Model_Core_App` by
66
+ `id.app.uuid` (→ `appId`), and takes `personaIds` from `id.client.personas` — then calls the **same**
67
+ private `resolveSettingValue(slug: _Model_Core_Setting::SLUG_LANGUAGE, context: {appId, personaIds,
68
+ userId})` helper the login path uses (same 8-layer cascade; the client DB is already registered at this
69
+ point). The resolved code is validated against `_Model_Client_Language` (load by `code`):
70
+ - **Valid** → sets `$this->jwtPayload->id->language` to the resolved code.
71
+ - **Unset/empty** → defaults to `'en'`.
72
+ - **Invalid** (non-empty but absent from `Languages`) → **keeps the token's current language** and adds
73
+ a `WZ-4` warning (see below). It deliberately does **not** fail the refresh — a bad setting must not
74
+ log an active user out on their hourly refresh (contrast the login path, which hard-fails with EV-16).
75
+
76
+ Placement is load-bearing: the `id.language` mutation happens **after** client-DB registration and
77
+ **before** both the access and refresh tokens are signed, so both new tokens carry the updated language.
59
78
 
60
79
  **Invalid-language hard failure (auth time).** The resolved code is validated against the client's
61
80
  `Languages` table. An **unset/empty** setting defaults to `en` (no error). But a **non-empty** code
@@ -64,6 +83,14 @@ that does **not** exist in `Languages` now **fails authentication** with HTTP 40
64
83
  with the offending `language` code in the message `identifiers` — instead of silently falling back to
65
84
  `en`. A misconfigured language must surface loudly, not silently degrade.
66
85
 
86
+ **Invalid-language soft warning (refresh time).** On the refresh path an invalid code is handled with a
87
+ distinct, non-fatal WARNING instead of EV-16: `WZ-4 DEFINED_MESSAGE_WARNING_INVALID_LANGUAGE_SETTING`
88
+ (type WARNING, group VALIDATION, `{language}` identifier placeholder; defined in `Response.php`, added
89
+ via `addDefinedMessage(...)` so the warning has a stable catalogued code). Because it is a WARNING it
90
+ carries no `status` key and does not change the response status — the refresh succeeds and the token
91
+ keeps its current language. This is deliberately softer than EV-16 (status 400), which would fail the
92
+ refresh and log the user out.
93
+
67
94
  **Auth-mint status finalization (subtle).** The auth success path sets `status = 201` and attaches the
68
95
  tokens. EV-16 is signalled by a local flag `$invalidLanguageSetting` set at the resolution site; the
69
96
  finalization branches `if (empty($invalidLanguageSetting))` → `201` + tokens, else
@@ -134,6 +161,9 @@ None — uniform across all clients. The sidecar table + ACL ship via `dbchanges
134
161
  `__get` — `isset()`/`?? null` always read false/null. See
135
162
  [_Model magic-field access](../../_underscore/features/model-magic-field-access.md); this was the
136
163
  cause of the uuid not appearing in the fallback warning.
164
+ - **Refresh re-resolution is user-token-only.** If you extend the JWT with new audience-shaped claims,
165
+ remember the refresh path rebuilds context from the *existing* token (uuid/app-uuid/personas) — it
166
+ does not re-read anything the token doesn't carry.
137
167
  - Migrations not yet executed at time of writing; needs live verification.
138
168
 
139
169
  ## Change history
@@ -149,6 +179,14 @@ None — uniform across all clients. The sidecar table + ACL ship via `dbchanges
149
179
  record `uuid` and dedupes per-record (`recordFieldId:uuid`). Documented the `isOverridable = 0`
150
180
  client-global lock as a config gotcha. Discovered the `_Model` `__isset` gap behind the missing-uuid
151
181
  bug (see linked _underscore doc). Live-tested on the local Compass DB. (jcardinal)
182
+ - 2026-07-09 — `/auth/refresh` now re-resolves the user's language (same `resolveSettingValue` cascade as
183
+ login) and embeds it in the newly minted access + refresh tokens, so a language switch applies on the
184
+ next refresh with no re-login (previously refresh copied the JWT payload verbatim, resetting only
185
+ `exp`/`iat`). User-tokens only (skips API-credential/public — no language claim); mutates `id.language`
186
+ after client-DB registration and before signing both tokens. Invalid code on refresh is a **soft**
187
+ non-fatal `WZ-4 WARNING_INVALID_LANGUAGE_SETTING` (new constant in `Response.php`) that keeps the
188
+ token's current language — vs. login's hard EV-16 400 — so a bad setting can't log an active user out.
189
+ (TRUE-80064, bala)
152
190
 
153
191
  ## Related docs
154
192
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.295",
3
+ "version": "1.0.296",
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",