toga-ai 1.0.974 → 1.0.976

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: feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-08
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - tools/_/app/design/github.php
@@ -17,6 +17,7 @@ files:
17
17
  - tools/_/app/frameworkindex.php
18
18
  - tools/_/app/nav.php
19
19
  - tools/composer.json
20
+ - tools/.ebextensions/009_setup_phpini.config
20
21
  related:
21
22
  - ../architecture.md
22
23
  - ../../../standards/frontend.md
@@ -87,6 +88,12 @@ Both sides are required: decoding only reads would make `renameTab()` compare a
87
88
  - Inline `window.DESIGN_CONFIG` uses `JSON_HEX_*` flags.
88
89
  - Input length caps, version-id regex guard, HTML content sniff on upload; no client-facing leakage of GitHub/curl error detail.
89
90
 
91
+ ### Upload size + timeout
92
+ - Cap: `App_Design_Github::MAX_UPLOAD_BYTES` = 50 MB. GitHub warns on files >50 MB, blocks >100 MB.
93
+ - php.ini (`.ebextensions/009_setup_phpini.config`): `upload_max_filesize=50M` / `post_max_size=55M`. **Keep these above `MAX_UPLOAD_BYTES`** — raise both together.
94
+ - `ghApi` curl timeout = `GITHUB_TIMEOUT_SECONDS` (180s); a 50 MB file is ~67 MB as a base64 blob. If `json_encode` fails, `ghApi` logs and throws `RuntimeException` (never POSTs an empty body).
95
+ - Apache has no `LimitRequestBody`; `memory_limit` 2G is enough.
96
+
90
97
  ### Access control
91
98
  Behind Tools SSO. The page self-guards with `App_Auth::requireAuth([...])` and `App_Page::validateCrossSiteRequestForgery()`; the nav action is gated to the `TOGa Technology` and `Development Team` personas. This is the key improvement over the old `forward` tool, which had **no** authentication.
92
99
 
@@ -98,6 +105,7 @@ Behind Tools SSO. The page self-guards with `App_Auth::requireAuth([...])` and `
98
105
  - **Contents API inlines only ≤1 MB** — read exports with the raw media type (see above).
99
106
  - **Publishing pushes to `forward`'s `_main`** and triggers EB auto-deploy; no staging.
100
107
  - **Double-escaped names (`H&H` shown literally, rename fails with "No such tab.")** — caused by values that arrived **already HTML-encoded** and were then correctly escaped once more at display. The render path was never wrong; no current code path produces the encoding (`createTab`/`createProject` and the JS transport `window.prompt` → `URLSearchParams` → `$postField` trim/`mb_substr` are clean), so encoded values entered from outside — a paste from rendered HTML, or a pre-fix build. Fixed defensively with `decodeEntities()` on both the read and request sides (see above). Generated project stubs were affected identically, because `buildStub()`'s single correct `htmlspecialchars()` was a *second* escape on a pre-encoded title; storing titles plain fixes the stubs too.
108
+ - **Upload fails on a big export (e.g. 28 MB)** — hit the old 25 MB cap in php.ini and `MAX_UPLOAD_BYTES`. Fixed 2026-10-08 (now 50 MB). Change both places together.
101
109
  - Never hardcode the token in tracked source — a leaked `ghp_`/PAT must be rotated.
102
110
 
103
111
  ## Related
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-08
10
10
  owners: [jcardinal, bala]
11
11
  files:
12
12
  - tools/.ebextensions/004_http_to_https.config
@@ -44,7 +44,7 @@ When migrating any 1.0 app's `.ebextensions` from AL2 to AL2023, rename/remove t
44
44
 
45
45
  The old approach ran `ebs/setup_phpini.php` (now superseded) which called `php_ini_scanned_files()`, parsed it to find the scan dir, and wrote there. On AL2023 that failed silently (it was wrapped in `ignoreErrors: true`), so `include_path` never picked up `/var/www/library`, producing fatal `require_once('_.php')` errors at runtime.
46
46
 
47
- The `application.ini` sets `include_path = ".:/var/www/library"`, upload/post limits (`upload_max_filesize=25M` / `post_max_size=30M`, raised from 16M so multi-MB design-demo HTML exports up to ~25MB post through — see `features/design-demo-admin.md`), `memory_limit = 2G`, `display_errors`, and loads the `redis.so` extension.
47
+ The `application.ini` sets `include_path = ".:/var/www/library"`, upload/post limits (`upload_max_filesize=50M` / `post_max_size=55M`, kept above the design-demo 50 MB cap — see `features/design-demo-admin.md`), `memory_limit = 2G`, `display_errors`, and loads the `redis.so` extension.
48
48
 
49
49
  > **Do NOT configure the rediscluster session handler in `application.ini` (or any INI file) on this platform — it silently fails.** See "Redis sessions" below. Session `save_handler`/`save_path` are now set at **runtime via `ini_set()` in `tools/_/app/framework.php`**, not in `009_setup_phpini.config`. Two INI-file facts drove this (both verified 2026-08-14 on EB PHP 8.5 / AL2023):
50
50
  > - **The INI-file parser silently drops a `session.save_path` value of the form `"seed[]=host:port"`** — in *both* `/etc/php.d/*.ini` and the php-fpm pool (`www.conf` `php_value[...]`). `session.save_handler=rediscluster` applies but `save_path` stays `/tmp`, so `session_start()` fatals ("Failed to initialize storage module: rediscluster (path: /tmp)") on **every** request → full outage. CLI `php -d` with the same value works, confirming it is the INI-file parser, not the value itself.
@@ -93,7 +93,7 @@ The deploy clones `agilantsolutions/library` and `resources` via `020_setup_git_
93
93
  ### Config selection & DB wiring
94
94
  - **Which config file loads is chosen by the `ENVIRONMENT` env var, NOT the hostname.** `App_Config::getConfigFilename` resolves `config.<ENVIRONMENT>.ini`.
95
95
  - Config maps DB key **`db_<x>` → `[database_<x>]`** section; `App_Registry::get()` connects **lazily** on first use. `Client_True` is **`db_true`** (`reader1.client.database.togahub.com`).
96
- - Prod php.ini caps `upload_max_filesize=25M` / `post_max_size=30M` (`009_setup_phpini.config`) — multi-file uploads (e.g. the Legacy Email Notifier's xlsx trace exports) must stay under this.
96
+ - Prod php.ini caps `upload_max_filesize=50M` / `post_max_size=55M` (`009_setup_phpini.config`) — multi-file uploads (e.g. the Legacy Email Notifier's xlsx trace exports) must stay under this.
97
97
 
98
98
  ### Local development
99
99
  - **Run locally with the `ENVIRONMENT` var pointing at your local config:**
@@ -10,6 +10,7 @@ owners: [bala, ajean, rgirish]
10
10
  files:
11
11
  - toga2-commerce/src/pages/Cart/api/CartApi.ts
12
12
  - toga2-commerce/src/pages/Cart/viewModel/useCartViewModel.ts
13
+ - toga2-commerce/src/pages/Cart/view/cartForm/EditCart.tsx
13
14
  - worker1.5/crons/toga2/compass/import_locations.php
14
15
  - _underscore/Model/Compass/SalesOrder.php
15
16
  - api2/Component/Api/V2/V2.php
@@ -22,6 +23,7 @@ related:
22
23
  - ../../../2.0/apps/toga2-commerce/features/cart-page-config-architecture.md
23
24
  - ../../../1.0/apps/tools/features/compass-user-persona-admin.md
24
25
  - ./people-file-user-lifecycle.md
26
+ - ../workflows/granting-api-access.md
25
27
  ---
26
28
  A Compass "cost center" is a Unit-level Location keyed by `c_erpSystemEntityId` (NOT a `CostCenters` row), numeric-only since 2026-07-06; open for cost-center dropdown, import, or the numeric-only policy. US Compass only.
27
29
 
@@ -38,6 +40,11 @@ US Compass only (`Client_Compass`). Compass Canada is explicitly out of scope. S
38
40
  - Entity `1001` = US cost centers. Entity `1019` rows look like old / reused CCs (feed active flag `U`, not `Y`).
39
41
  - The `CostCenters` table is empty for Compass; nothing reads `Users_CostCenters` / `Locations_CostCenters` / `SalesOrders.costCenterId` / `Users.defaultCostCenterId`.
40
42
 
43
+ ### Where a user's cost center lives
44
+ - **`Users.c_erpEntityId` = the user's cost center number.** Cart reads it in `fetchUser` (`CartApi.ts` ~464; `EditCart.tsx` ~446-461), then gets the name from `/locations` by `c_erpSystemEntityId` with `distinct=` (`fetchCostCenter`, ~518) and takes `result[0]`.
45
+ - **`Users.locationId` / `location.*` is NOT the cost center.** Example: `RoachC01` has no location but cost center `121109`. Any API consumer looking up a user's cost center reads `Users.c_erpEntityId` (see [API access](../workflows/granting-api-access.md)).
46
+ - Any `/locations` cost-center call must send `distinct=` — prod has ~550 active duplicate rows per number (e.g. `10021` → 545 rows "Cape Cod Mall"); 174 numbers map to more than one active name (2026-10-08).
47
+
41
48
  ### The commerce dropdown
42
49
  - Built by `fetchCostCenter()` in `toga2-commerce/src/pages/Cart/api/CartApi.ts`, which calls `GET /locations` filtered on `c_erpSystemEntityId LIKE %search% AND isActive = 1`, fields `c_erpSystemEntityId,name`, distinct (exact `=` match on the display path).
43
50
  - `useCartViewModel.ts` (~426-460) labels options `"<CC> - <name>"` and dedupes by `c_erpSystemEntityId` — **first row wins**. So when one CC number exists under several entities, the shown name can belong to the wrong entity.
@@ -92,3 +99,4 @@ Deactivation proven safe against prod data:
92
99
 
93
100
  ## Change history
94
101
  - 2026-10-08 — Documented dropdown first-row-wins dedupe (wrong name = display only), entity 1001 vs 1019, the INNER JOIN import bug that inserts duplicate address-less units every run, uuid churn, and the hardcoded SFTP credential. (rgirish)
102
+ - 2026-10-08 — Added: user cost center = `Users.c_erpEntityId` (not `locationId`); `/locations` calls need `distinct=`. (rgirish)
@@ -6,8 +6,8 @@ project: Database Changes
6
6
  client: compass-usa
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: ["bala"]
9
+ updated: 2026-10-08
10
+ owners: ["bala", "rgirish"]
11
11
  files:
12
12
  - dbchanges2/Client_Compass/2026-09-10 - RefreshTeamApiAccess.sql
13
13
  related:
@@ -16,6 +16,7 @@ related:
16
16
  - ../../../2.0/apps/api2/features/api-key-authentication-and-scoping.md
17
17
  - ../../../2.0/apps/_underscore/features/acl-permission-chain.md
18
18
  - ../../../2.0/apps/api2/features/request-logging.md
19
+ - ../features/cost-centers.md
19
20
  ---
20
21
  How to build a Compass API key with one dedicated read-only role (the Refresh-team template); open for any "give team X scoped API access" request.
21
22
 
@@ -30,7 +31,18 @@ The Refresh-team key (worked example, landed on **production** 2026-09-10):
30
31
  - Read-only: **9 records**, **83 field permissions**, **18 custom-field permissions**, **0 writable anywhere**.
31
32
  - Records granted (`Core.Records.id`): **2** users, **11** locations, **37** contacts, **38** contact-email-addresses, **39** contact-phone-numbers, **40** personas, **201** user-personas, **273** user-roles, **274** roles.
32
33
  - Field grants were **cloned from the shared API role (roleId 3)** for those same records, minus the two fields dropped in the steps below.
33
- - The consumer calls four requests: auth, cost center by number (`/locations`), user by email (`/users`), and user by Compass username (`/users`).
34
+ - Not readable by this key: `Users.c_isVip`, `Users.c_supportedByUserId` (not in the cloned grants).
35
+ - Auth: `POST /v2/auth/api` with `{client: <Compass client uuid>, api: Apis.uuid, secret: Apis.secret}`. `Apis.secret` is stored **plain text** in `Client_Compass.Apis` — fetch it from there; never copy the value into docs or git.
36
+ - Separate from the MITS (`Apis.id 2`) and "Compass Group" (`Apis.id 3`) keys.
37
+
38
+ ### Refresh-team calls (decided 2026-10-08: Users only, no `/locations`)
39
+ Cost center lookups go through `Users.c_erpEntityId`, the same field the cart reads ([Cost Centers](../features/cost-centers.md)). Calls:
40
+ 0. `POST /v2/auth/api` (above).
41
+ 1. Search cost centers: `/users?recordsPerPage=25&fields=c_erpEntityId&where=(c_erpEntityId:like:%25{text}%25,AND,isActive:eq:1)&distinct=`
42
+ 2. User by email: `/users?fields=_name,firstName,lastName,email,c_erpEntityId,_personaIds,userPersonas.persona.name&where=(email:eq:{email})`
43
+ 3. User by HR username: same fields, `where=(c_hrEmpUsername:eq:{username})`
44
+
45
+ Delivered as a Postman collection (`Compass_Refresh_Team_API.postman_collection.json`, kept on the developer's machine, not in git), credentials as placeholders.
34
46
 
35
47
  ## Steps
36
48
  1. Create the `Client_Compass.Roles` row for the consumer (one role, one key). `coreRoleId` may stay NULL — every record here has `Core.Records.aclDatabase = 'CLIENT'`.
@@ -49,7 +61,7 @@ The Refresh-team key (worked example, landed on **production** 2026-09-10):
49
61
  - Call each intended route with the new key, then read `Logs_Compass.Api` filtered on the new `apiId` to confirm the exact routes and response payloads ([V2 Request Logging](../../../2.0/apps/api2/features/request-logging.md)).
50
62
 
51
63
  ### The consumer-facing reference document
52
- Each API consumer gets a Word reference document, mirroring the existing MITS one. Generated with `python-docx` using the MITS `.docx` as the style template, covering the four requests, real sample responses lifted from `Logs_Compass.Api`, Postman links, token lifetimes, and a plain-English "what your key can access" section. **Credentials in the document are placeholders only.** The file lives outside the repos (the developer's local Downloads) — the binary is not stored in git or the knowledge base.
64
+ Each API consumer gets a Word reference document, mirroring the existing MITS one. Generated with `python-docx` using the MITS `.docx` as the style template, covering the requests, real sample responses lifted from `Logs_Compass.Api`, Postman links, token lifetimes, and a plain-English "what your key can access" section. **Credentials in the document are placeholders only.** The file lives outside the repos (the developer's local Downloads) — the binary is not stored in git or the knowledge base.
53
65
 
54
66
  ## Gotchas
55
67
  - **⚠ The four older Compass keys are NOT scoped.** All four pre-existing `Client_Compass.Apis` rows — **Agilant**, **MITS Service Hub**, **Compass Group**, **Office Depot (Cxml)** — are wired in `Apis_Roles` to the **same two roles**:
@@ -65,6 +77,8 @@ Each API consumer gets a Word reference document, mirroring the existing MITS on
65
77
  - The **negative test has not been run** — calling a route outside the grant (e.g. `/items`) with the Refresh key to confirm `UNAUTHORIZED`.
66
78
  - The script was run **directly on production**; the intended client-sandbox dry run was skipped.
67
79
  - Migrating the four older keys off the shared Base + API roles onto per-key roles is **not started**.
80
+ - (2026-10-08) The Postman collection is **not tested live**. `userPersonas.persona.name` is unverified — copied from the `userRoles.role.name` shape.
81
+ - The old `Refresh_Team_API_Reference.docx` used `/locations` / the user's location for cost center — **wrong**; regenerate it from the Users-only calls.
68
82
 
69
83
  ## Related
70
84
  - [Compass USA profile](../profile.md)
@@ -72,3 +86,4 @@ Each API consumer gets a Word reference document, mirroring the existing MITS on
72
86
  - [API-Key Authentication & scoping](../../../2.0/apps/api2/features/api-key-authentication-and-scoping.md)
73
87
  - [ACL Permission Chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md)
74
88
  - [V2 Request Logging](../../../2.0/apps/api2/features/request-logging.md)
89
+ - [Cost Centers](../features/cost-centers.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.974",
3
+ "version": "1.0.976",
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",