toga-ai 1.0.912 → 1.0.913

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.
@@ -44,6 +44,7 @@
44
44
  | [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | Legacy `Page::meta()` page-meta resolver + context-scoped `ClientRecordFieldSettings`; open when a grid column header is wrong/camelCase, or a `/pages/meta` pag |
45
45
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | `_underscore` opens three per-client DB connections (data/logs/archive) per request; open when a local or prod request 500s with `Unknown database`, when resolv |
46
46
  | [Persona Name Translation (PersonaTranslations sidecar)](features/persona-name-translation.md) | Serves Persona names in multiple languages via a `PersonaTranslations` sidecar (copy of the assortment-translation pattern); open when adding/running persona tr |
47
+ | [Persona nav-visibility gate + user nav pins (keyed by Core.SurfaceElements uuid)](features/persona-nav-visibility-gate.md) | Sidebar-nav persona gate in `_Model_Core_Surface` (`Client.PersonaNavVisibility`) and user nav pins (`Client.UserNavPreferences`), both keyed by Core `SurfaceEl |
47
48
  | [Record Change Audit Log (Logs_<Client>.Record / RecordField) — reading a field's history](features/record-change-audit-log.md) | Field-level change audit trail per tenant; open to answer "who changed this field, to what, when" or to tell a direct SQL edit from an in-app change. |
48
49
  | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | Engine that mirrors a downstream ItemFulfillment up the SO→PO→SO chain onto every customer-facing SO; open when touching IF creation, bundle scaling, or upstrea |
49
50
  | [Sales-order denial reason (`ApprovalDecisions.note`) — capture, the denial-details card, and the read-only workflow mode](features/sales-order-denial-reason.md) | Map of the SO approval denial reason (`ApprovalDecisions.note`) — where it is written, read, and displayed; open when building any denial-reason display, banner |
@@ -53,6 +54,7 @@
53
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 |
54
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 |
55
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 |
56
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 |
57
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 |
58
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 |
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: "Persona nav-visibility gate + user nav pins (keyed by Core.SurfaceElements uuid)"
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-09-29
10
+ owners: [jcardinal]
11
+ files:
12
+ - _underscore/Model/Core/Surface.php
13
+ - _underscore/Model/Client/PersonaNavVisibility.php
14
+ - _underscore/Model/Client/UserNavPreference.php
15
+ - dbchanges2/Client/2026-09-29a - NavItemSurfaceElementUuidColumns.sql
16
+ - dbchanges2/Client/2026-09-29b - DropNavItemSurfaceElementIdColumns.sql
17
+ - dbchanges2/Core/2026-09-29a - UserNavPreferencesNavItemUuidRecordField.sql
18
+ related:
19
+ - surface-resolver.md
20
+ - surface-overrides-uuid-rekey.md
21
+ - ../../dbchanges2/features/surface-layer-schema.md
22
+ ---
23
+
24
+ Sidebar-nav persona gate in `_Model_Core_Surface` (`Client.PersonaNavVisibility`) and user nav pins (`Client.UserNavPreferences`), both keyed by Core `SurfaceElements.uuid`; open before touching nav visibility, pins, or any Client row that points at a Core Surface element.
25
+
26
+ ## Summary
27
+
28
+ - Gate runs only when the surface `config.renderAs === 'nav'`. Default-deny: a nav element survives only if one of the caller's personas has a `PersonaNavVisibility` grant for it in the request app.
29
+ - Grants and pins store `navItemSurfaceElementUuid` (CHAR(36), lowercase) = `Core.SurfaceElements.uuid`. The numeric `navItemSurfaceElementId` column was dropped 2026-09-29.
30
+ - Rule: never key a Client row to a Core Surface/SurfaceElement by numeric id. Core ids shift when Core is rebuilt or copied to another env; seeded uuids are the same everywhere.
31
+
32
+ ## How it works
33
+
34
+ 1. `_buildBundle` step 5b: `$isNavGated` = `config.renderAs === 'nav'`. False → nothing runs, no element dropped (user menu uses `renderAs='menu'`). Do not widen this check.
35
+ 2. App id = `$api->app->id`, else `Surfaces.appId`.
36
+ 3. `_grantedNavItemSurfaceElementUuids($personaIds, $appId)`: one query on the client DB (`personaId IN (...) AND appId = ... AND navItemSurfaceElementUuid IS NOT NULL`) → set keyed by lowercase uuid. No personas or no app → empty set → all nav elements hidden.
37
+ 4. Per element: drop if `strtolower($element->uuid)` is not in the set.
38
+ 5. Cluster-safe: grant uuids are read on the client connection and matched in PHP against the Core elements. No cross-database SQL.
39
+
40
+ **Pins (`Client.UserNavPreferences`, Core.Records 349):** RecordField 2645 = `navItemSurfaceElementUuid` (2487 = old id field, retired). `prePost` interceptor stamps `userId` from the JWT and `appId` (`$api->app->id`, fallback 34), checks uuid format, lowercases it.
41
+
42
+ ## Gotchas
43
+
44
+ - Model field is `FIELD_CHAR`, not `FIELD_CHAR_UUID`. `FIELD_CHAR_UUID` auto-generates a value, which would overwrite the FK.
45
+ - Symptom of id drift: sidebar-nav returns only the theme (all elements dropped). Seen on qa-alpha 2026-09-29: Core rebuilt from prod, grants pointed at ids 114-134, local elements were 241-261 (+127).
46
+ - `Client/2026-09-29b` deletes old id-only grants only for a persona+app that already has uuid grants, then sets the uuid NOT NULL. A client never re-keyed fails loud (`Invalid use of NULL`) instead of losing its whole sidebar. Fix: write that client's uuid grant seed (pattern: `Client_True/2026-09-29a`), then re-run.
47
+ - Run order: `Client/2026-09-29a` + client grant seeds → code live → `Client/2026-09-29b` → `Core/2026-09-29b` (removes RecordField 2487).
48
+ - `Client.SurfaceOverrides` still uses numeric Core ids — same risk, deferred: [surface-overrides-uuid-rekey](surface-overrides-uuid-rekey.md).
49
+
50
+ ## Change history
51
+ - 2026-09-29 — Grants + pins re-keyed from numeric SurfaceElements id to uuid; `_grantedNavItemSurfaceElementIds` renamed `_grantedNavItemSurfaceElementUuids` (_underscore `8e299b1b`, dbchanges2 `66ba056`) (jcardinal)
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: "DEFERRED — re-key Client.SurfaceOverrides from numeric Core ids to uuids"
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: deferred
9
+ updated: 2026-09-29
10
+ owners: [jcardinal]
11
+ files:
12
+ - _underscore/Model/Core/Surface.php
13
+ - _underscore/Model/Client/SurfaceOverride.php
14
+ related:
15
+ - persona-nav-visibility-gate.md
16
+ - surface-resolver.md
17
+ ---
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.
20
+
21
+ ## Summary
22
+
23
+ Not built. `_Model_Core_Surface::_loadOverrides` matches `SurfaceOverrides.surfaceId` / `surfaceElementId` to Core numeric ids. Same failure as the nav gate before 2026-09-29 ([persona-nav-visibility-gate](persona-nav-visibility-gate.md)): once a client has override rows and Core is rebuilt/copied with different AUTO_INCREMENT ids, overrides silently miss or land on the wrong element.
24
+
25
+ ## How it works
26
+
27
+ Planned fix (do before first client use on such an env):
28
+ 1. Add `surfaceUuid` / `surfaceElementUuid` CHAR(36) to `Client.SurfaceOverrides` (`FIELD_CHAR`, not `FIELD_CHAR_UUID`).
29
+ 2. `_loadOverrides`: match by uuid on the client connection, join to Core elements in PHP (no cross-database SQL).
30
+ 3. Forward-only re-key seeds per client, then drop the id columns with a fail-loud NOT NULL step (same shape as `Client/2026-09-29b`).
31
+
32
+ ## Gotchas
33
+
34
+ - Elements seeded with reserved id literals are stable across envs; the risk is elements that took AUTO_INCREMENT ids (e.g. the `navigation` surface's elements). The framework-rules "Bounded exemption — the Surface tables" stays as-is until this is built.
35
+
36
+ ## Change history
37
+ - 2026-09-29 — Recorded as deferred after the nav-gate uuid fix exposed the same numeric-id risk (jcardinal)
@@ -36,6 +36,8 @@ related:
36
36
  - sales-order-status-filter-surface.md
37
37
  - surface-field-recordfield-binding.md
38
38
  - ../../toga25-supply/features/side-navigation-and-default-route.md
39
+ - persona-nav-visibility-gate.md
40
+ - surface-overrides-uuid-rekey.md
39
41
  ---
40
42
 
41
43
  Runtime for the platform Surface UI layer (`_Model_Core_Surface::resolve`/`metaGroup`, replaces `Page::meta`); open for surface resolution, the override cascade, vocab/theme/message resolution, client seeding, caching, and 403/empty-bundle debugging.
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-09-29
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - api2/health
@@ -32,6 +32,7 @@ The EB/ALB liveness probe (`/health`) is served two ways, and the **load-bearing
32
32
  - **`api2/health`** — a one-line static file (`OK`). Apache serves it directly.
33
33
  - **`api2/.platform/httpd/conf.d/health_probe.conf`** — comment-only documentation of why the probe is static (no directives; explains the decision at the Apache layer).
34
34
  - **Why it works without a rewrite fight:** `api2/.htaccess` only rewrites to `index.php` when the request target is **not a real file** (`RewriteCond %{REQUEST_FILENAME} !-f`). Because `health` is a real file, the rewrite is skipped and Apache serves it straight.
35
+ - **`api2/.ebextensions/health-check.config`** (2026-09-29) — pins the EB default process health check: `aws:elasticbeanstalk:environment:process:default` → `HealthCheckPath: /health`, `MatcherHTTPCode: 200`. Without it EB checks `/`, which returns 405 → env Red/Severe, `Target.ResponseCodeMismatch`. Applies on the next deploy of each env.
35
36
  - **Why `enforce_https` doesn't redirect it:** EB health checks arrive on **HTTP:80 with no `X-Forwarded-Proto`**, and `enforce_https.conf` only redirects requests carrying the forwarded-proto marker — so the probe is not bounced to HTTPS.
36
37
 
37
38
  **PHP short-circuit (for `/v2/health` and direct hits).** At the top of `Index.php::api()`, before host dispatch and before the Core/Logs DB bootstrap. Matches against a class constant:
@@ -56,7 +57,14 @@ So **all** these probe variants return 200 and never touch routing or the DB: `/
56
57
  - **Do not** move `/health` back into a PHP-served route or rename/remove `api2/health` — that re-couples EB liveness to PHP-FPM worker availability, the exact failure this fixed (see [api2 architecture — EB "Degraded" health](../architecture.md)).
57
58
  - Health probes must degrade to a 200 short-circuit, not enter routing. Add new probe paths to `HEALTH_CHECK_ROUTES` in normalized form, never as a new exact-string branch.
58
59
 
60
+ - Env Red/Severe with `Target.ResponseCodeMismatch` → check the target group health-check path is `/health`, not `/`. The `.ebextensions` file sets it; an env not yet redeployed still has the old path.
61
+ - Git Bash path-converts AWS CLI args (`/health` → `C:/Program Files/Git/health`). Prefix the command with `MSYS_NO_PATHCONV=1`.
62
+
59
63
  ## Related
60
64
 
61
65
  - [api2 architecture — EB "Degraded" health](../architecture.md)
62
66
  - [V2 API error codes](v2-api-error-codes.md)
67
+
68
+ ## Change history
69
+ - 2026-09-29 — Added `.ebextensions/health-check.config` (HealthCheckPath `/health`, matcher 200); api-qa-alpha was Red on `/` 405 (api2 `885d114`) (jcardinal)
70
+ - 2026-08-28 — `/health` moved to a static Apache file, off PHP-FPM (jcardinal)
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
19
19
 
20
20
  ## 2.0 framework
21
21
 
22
- - **_underscore** (_Underscore) _(framework core)_ — 90 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
22
+ - **_underscore** (_Underscore) _(framework core)_ — 92 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
23
23
  - **worker2** (Worker) — 80 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
24
24
  - **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
25
25
  - **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.912",
3
+ "version": "1.0.913",
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",