toga-ai 1.0.320 → 1.0.322

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.
@@ -7,6 +7,6 @@
7
7
  | [Tools MVC — Routing, CSRF & App_Database Access Patterns](features/mvc-data-access-patterns.md) | The load-bearing 1.0 (`App_`) framework conventions a developer needs when adding a page to the Tools app — URL routing, CSRF, and DB access through `App_Databa | tools/_/app/nav.php, tools/mvc/get.php |
8
8
  | [Tools Persona-Gated Navigation (App_Nav)](features/persona-gated-navigation.md) | `App_Nav` is the Tools app's two-level, **persona-gated** navigation. | tools/_/app/nav.php, tools/mvc/get.php |
9
9
  | [Tools SAML SSO Consumer & Persona-Gated Auth (App_Auth)](features/saml-sso-auth.md) | `App_Auth` is the Tools app's authentication layer: it consumes the SAML gateway `?saml=` handoff (see the 2.0 SAML downstream integration contract), establishe | tools/_/app/auth.php, tools/mvc/sso/initiate/get.php, tools/mvc/sso/get.php, tools/mvc/login/get.php, tools/mvc/login/post.php, tools/mvc/logout/get.php, tools/mvc/get.php, tools/config.production.ini, tools/config.local.ini |
10
- | [Talos Knowledge Base Admin UI (KB Documents + Vocabulary)](features/talos-kb-documents-admin.md) | A Tools (1.0) admin UI to browse/fix the Talos knowledge-base documents and manage the transcript-cleanup vocabulary — without a deploy. | tools/mvc/talos/kb-documents/get.php, tools/mvc/talos/kb-documents/post.php, tools/mvc/talos/knowledge-bases/post.php, tools/mvc/talos/vocabulary/get.php, tools/mvc/talos/vocabulary/post.php, tools/_/app/talos/s3.php, tools/_/app/talos/bedrock.php, tools/_/app/worker.php, tools/_/app/nav.php, tools/config.production.ini, tools/config.alpha.ini, tools/.platform/httpd/conf.d/timeouts.conf |
10
+ | [Talos Knowledge Base Admin UI (KB Documents + Vocabulary)](features/talos-kb-documents-admin.md) | A Tools (1.0) admin UI to browse/fix the Talos knowledge-base documents and manage the transcript-cleanup vocabulary — without a deploy. | tools/mvc/talos/kb-documents/get.php, tools/mvc/talos/kb-documents/post.php, tools/mvc/talos/knowledge-bases/get.php, tools/mvc/talos/knowledge-bases/post.php, tools/mvc/talos/vocabulary/get.php, tools/mvc/talos/vocabulary/post.php, tools/_/app/talos/s3.php, tools/_/app/talos/bedrock.php, tools/_/app/pg.php, tools/_/app/worker.php, tools/_/app/nav.php, tools/config.production.ini, tools/config.alpha.ini, tools/.platform/httpd/conf.d/timeouts.conf, tools/.platform/hooks/prebuild/01-install-php-pgsql.sh, tools/.platform/hooks/postdeploy/01-restart-php.sh |
11
11
  | [Talos Pricing UI (Onboarding, Dashboard, Benchmarks, Cost Factors + Estimator)](features/talos-pricing-ui.md) | The 1.0 (tools app) face of the **Talos Pricing Platform** — a "Talos Pricing" nav folder with four pages plus a client-side estimate engine. | tools/_/app/nav.php, tools/_/app/talos/estimator.php, tools/mvc/talos/onboarding/get.php, tools/mvc/talos/onboarding/post.php, tools/mvc/talos/pricing/get.php, tools/mvc/talos/benchmarks/get.php, tools/mvc/talos/factors/get.php, tools/mvc/talos/factors/post.php, tools/assets/css/style.css |
12
12
  | [Deploying Tools to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)](workflows/deploy-to-elastic-beanstalk-al2023.md) | How the **Tools** 1.0 app boots on Elastic Beanstalk running `PHP 8.5 on 64bit Amazon Linux 2023/4.13.1 (aarch64)`. | tools/.ebextensions/004_http_to_https.config, tools/.ebextensions/006_mount-s3fs.config, tools/.ebextensions/007_setup_export_cache_folders.config, tools/.ebextensions/008_setup_ldap.config, tools/.ebextensions/009_setup_phpini.config, tools/.ebextensions/020_setup_git_libraries.config, tools/.ebextensions/050_register_instance_to_shared_application_load_balancer.config, tools/ebs/git.json |
@@ -11,16 +11,20 @@ owners: [jcardinal]
11
11
  files:
12
12
  - tools/mvc/talos/kb-documents/get.php
13
13
  - tools/mvc/talos/kb-documents/post.php
14
+ - tools/mvc/talos/knowledge-bases/get.php
14
15
  - tools/mvc/talos/knowledge-bases/post.php
15
16
  - tools/mvc/talos/vocabulary/get.php
16
17
  - tools/mvc/talos/vocabulary/post.php
17
18
  - tools/_/app/talos/s3.php
18
19
  - tools/_/app/talos/bedrock.php
20
+ - tools/_/app/pg.php
19
21
  - tools/_/app/worker.php
20
22
  - tools/_/app/nav.php
21
23
  - tools/config.production.ini
22
24
  - tools/config.alpha.ini
23
25
  - tools/.platform/httpd/conf.d/timeouts.conf
26
+ - tools/.platform/hooks/prebuild/01-install-php-pgsql.sh
27
+ - tools/.platform/hooks/postdeploy/01-restart-php.sh
24
28
  related:
25
29
  - ./mvc-data-access-patterns.md
26
30
  - ./persona-gated-navigation.md
@@ -34,7 +38,8 @@ A Tools (1.0) admin UI to browse/fix the Talos knowledge-base documents and mana
34
38
  transcript-cleanup vocabulary — without a deploy. It is the human front-end to the worker2
35
39
  [Talos Transcript Ingestion](../../../2.0/apps/worker2/features/talos-transcript-ingestion.md)
36
40
  pipeline. A new **"Talos Knowledge Base"** nav group (personas *Development Team* / *TOGa
37
- Technology*) exposes two pages.
41
+ Technology*) exposes the pages below. **"Knowledge Bases"** is registered as the **first**
42
+ action in the `talos-kb` nav group (above "KB Documents") and inherits the group's personas.
38
43
 
39
44
  ## Pages
40
45
 
@@ -76,12 +81,32 @@ so they displayed **double-encoded** (e.g. "Grand & Toy"). `vocabulary/post.
76
81
  `html_entity_decode()`s values on save so the DB stores **raw** text; `vocabulary/get.php`
77
82
  already escapes once at display. Do not re-add encoding on save.
78
83
 
84
+ ### `/talos/knowledge-bases` — KB list + inline rename
85
+
86
+ The list/admin page (`get.php`) for Talos knowledge bases. Unlike KB Documents (whose KB
87
+ list is derived from **live S3 folders**), this page's list comes from the
88
+ **`Team.KnowledgeBases` MySQL table** (via `db_team`) — the two are distinct sources of
89
+ truth. Columns: Slug, Name, Bedrock KB ID, Created On (`dtCreated`, converted UTC→
90
+ America/Chicago). **Name is the only editable field** and is edited **in place** (Edit→Save);
91
+ there is **no delete option**. Server strings are escaped before `innerHTML` (XSS guard).
92
+
93
+ Rename posts `action=rename` → `UPDATE Team.KnowledgeBases SET name WHERE slug` (`db_team`),
94
+ preceded by an **existence SELECT** so a stale slug reports **"not found"** instead of a
95
+ false success.
96
+
79
97
  ### `POST /talos/knowledge-bases` — create a new KB (synchronous provisioning)
80
98
 
81
99
  Admin action that provisions a brand-new Talos knowledge base end to end in **one
82
100
  synchronous request**: creates the S3 prefix, creates the Bedrock KB and **waits for it to
83
101
  reach `ACTIVE`**, creates the Bedrock data source (`MANAGED_KNOWLEDGE_BASE_CONNECTOR`), and
84
- inserts 2 MySQL (`Team.KnowledgeBases`, `Team.VectorIndexes`) + 2 Postgres rows. This flow
102
+ inserts rows across **5 systems with no shared transaction**: `Team.KnowledgeBases` (`db_team`,
103
+ Team MySQL cluster), `Client_True.VectorIndexes` (`db_true`, **Client MySQL cluster** — not the
104
+ Team cluster), and 2 Talos **Postgres** rows (`public.knowledge_bases` +
105
+ `public.assistant_knowledge_bases`, the latter linking a hard-coded assistant id). Step order:
106
+ validate name (≤255) + derive slug via **`App_Talos_S3::slugify()`** (kebab-case, `iconv` ASCII
107
+ translit; rejects a duplicate slug — `slug` is UNIQUE); create 3 S3 folder markers
108
+ `development-team/{slug}/{approved,archive,upload}/` via **`App_Talos_S3::putFolderMarker()`**;
109
+ create the Bedrock KB and wait for `ACTIVE`; create the data source; then the DB inserts. This flow
85
110
  **stays synchronous by design** — moving it to worker2/SQS async was explicitly rejected;
86
111
  the fix path is to raise timeouts, never to background the work.
87
112
 
@@ -115,6 +140,13 @@ orphaned KB).
115
140
  the SDK default chain — because `App_Talos_S3` only ever talks to **togaiq**, so it needs the
116
141
  togaiq-account key (the tools `[aws]` key is the toga-private key, which can cross-account *read*
117
142
  togaiq but not *write* it).
143
+ - **`App_Pg`** (`_/app/pg.php`) — a **tools-only** minimal PDO `pgsql` wrapper for the Talos
144
+ Postgres DB (host `writer.talos.database.togahub.com:5432`, db `tenant_true`, user
145
+ `talosadmin`). Deliberately **not** added to the shared 1.0 `library` (the App_ framework has
146
+ no Postgres support). The password is **read from the existing `[database_team]` config
147
+ section** (shared with the MySQL DBs) — **no new secret introduced**. `execute()` uses real
148
+ PDO prepared statements (bound params). If the `pdo_pgsql` extension is missing it returns
149
+ `ok=false` with a generic message (detail goes only to `error_log`, never to the client).
118
150
  - **`App_Worker::enqueue()`** (`_/app/worker.php`) — inserts a `Core.WorkerJobs` row and sends
119
151
  the `{"workerJobId": id}` SQS message, so a 1.0 app can enqueue worker2 jobs. Requires the
120
152
  `[worker]` (`queue_url`/`queue_region`) and `[aws]` (main-account key for SQS `SendMessage`)
@@ -130,6 +162,12 @@ orphaned KB).
130
162
  - **`[aws]`** — main-account key for SQS `SendMessage` (toga-private key; cross-account reads
131
163
  togaiq but cannot write it — that's why `[talos]` exists).
132
164
  - `[database_toga2core]` — writable 2.0 Core (already present).
165
+ - **`pdo_pgsql` PHP extension** must be installed on the tools Elastic Beanstalk environment
166
+ (the create flow writes to Talos Postgres via `App_Pg`). Provided by two `.platform` hooks
167
+ (mirroring worker2's): `hooks/prebuild/01-install-php-pgsql.sh` (installs `php-pgsql`,
168
+ idempotent, **fails the deploy loudly** if `pgsql` won't load) and
169
+ `hooks/postdeploy/01-restart-php.sh` (restarts php-fpm/httpd). **EB runs `.platform/hooks`
170
+ regardless of the git exec bit** — worker2's equivalents are committed `100644`.
133
171
  - Apply the dbchanges2 `Team/2026-06-30a..e` + `Core/2026-06-30a` migrations in order.
134
172
  - Verify the Bedrock KB region / data-source name against the live account (TODOs in code).
135
173
 
@@ -171,12 +209,37 @@ orphaned KB).
171
209
  allows same-name KBs with new ids). After a failed create: either
172
210
  `aws bedrock-agent delete-knowledge-base` the orphan and re-run clean, or finish the DB rows
173
211
  manually.
212
+ - **Newest KBs use Bedrock's fully-MANAGED type** (`knowledgeBaseConfiguration.type=MANAGED`,
213
+ `managedKnowledgeBaseConfiguration.embeddingModelType=MANAGED`) — **AWS owns the vector store**,
214
+ so there is **no S3 Vectors index, no `embeddingModelArn`, and no `storageConfiguration`** to
215
+ build. Config was replicated from the existing `development-team-craftex` KB. Account
216
+ `654654170868`, region `us-east-1`, execution role `…ForKnowledgeBase_ofl5a`.
217
+ - **KB-list source differs per page.** The new `/talos/knowledge-bases` page lists from
218
+ `Team.KnowledgeBases` (`db_team`); `/talos/kb-documents` lists from live S3 folders. Don't
219
+ assume one reflects the other.
220
+ - **Schemas.** `Team.KnowledgeBases`: `id, uuid`(UNIQUE)`, slug`(UNIQUE)`, name, isGeneral,
221
+ bedrockKbId`(nullable)`, isActive, dtCreated`(datetime default `CURRENT_TIMESTAMP`)`, dtUpdated`.
222
+ `Client_True.VectorIndexes` (Client cluster): `id, uuid`(UNIQUE)`, name, docSlug`(UNIQUE,
223
+ nullable)`, sectionSlug, chunkSlug, description` — create sets `docSlug=sectionSlug=chunkSlug=
224
+ bedrockKbId`.
225
+ - **Not yet run end-to-end in production.** The whole feature is code-complete, `php -l` clean,
226
+ and reviewed, but the create flow has not been exercised in prod pending the `pdo_pgsql`
227
+ deploy plus network/IAM prerequisites (the developer is handling those).
174
228
  - No new secret literals introduced; credentials live in the config sections above (documented by
175
229
  section/account, never by value). AWS account id `654654170868`, bucket `togaiq`, and the KB
176
230
  execution-role ARN are non-secret resource identifiers.
177
231
 
178
232
  ## Change history
179
233
 
234
+ - 2026-07-13 — Added the **`/talos/knowledge-bases` list + inline-rename page** (`get.php`;
235
+ lists from `Team.KnowledgeBases`, rename-only, no delete, existence-check before update),
236
+ registered first in the `talos-kb` nav group. Added the **`App_Pg`** tools-only PDO `pgsql`
237
+ wrapper (password reused from `[database_team]`; not added to shared library) and the
238
+ **`.platform` pdo_pgsql install/restart hooks**. Documented the create flow's 5-system fan-out
239
+ (incl. `Client_True.VectorIndexes` on the **Client** cluster + 2 Postgres rows), the
240
+ `slugify()`/`putFolderMarker()` helpers, the fully-**MANAGED** Bedrock KB type (AWS-owned vector
241
+ store, no storageConfiguration), and the two DB schemas. Feature is code-complete/reviewed but
242
+ not yet run end-to-end in prod. (jcardinal)
180
243
  - 2026-07-13 — Added the **synchronous KB-create flow** (`POST /talos/knowledge-bases`) via new
181
244
  `App_Talos_Bedrock` helper. Fixed the **504 at 60s**: EB AL2023 PHP = Apache→php-fpm over
182
245
  `mod_proxy_fcgi` with a 60s default proxy read timeout; added
@@ -2,6 +2,7 @@
2
2
 
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
+ | [Proposed — git-sourced base+overlay JSON authoring for the Surface layer](architecture/surface-authoring-proposal.md) | A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model move off hand-authored SQL against the `SurfaceOverrides` E | _underscore/Model/Core/Surface.php, _underscore/Model/Client/SurfaceOverride.php |
5
6
  | [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
6
7
  | [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
7
8
  | [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php |
@@ -0,0 +1,67 @@
1
+ ---
2
+ title: Proposed — git-sourced base+overlay JSON authoring for the Surface layer
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: architecture
8
+ status: draft
9
+ updated: 2026-07-13
10
+ owners: [apeterson]
11
+ files:
12
+ - _underscore/Model/Core/Surface.php
13
+ - _underscore/Model/Client/SurfaceOverride.php
14
+ related:
15
+ - ../features/surface-resolver.md
16
+ ---
17
+
18
+ ## Summary
19
+
20
+ A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model
21
+ move off hand-authored SQL against the `SurfaceOverrides` EAV table onto **git-sourced JSON**: a full
22
+ neutral `_base/<surface>.json` per surface plus partial per-client overlay files holding only diffs,
23
+ merged by the resolver at read time. The runtime resolver is kept for the dynamic parts (per-record
24
+ Tier-2 evaluation + ACL element-drop). This doc records the motivation, the design, and interim
25
+ guardrails so the lead dev can decide.
26
+
27
+ **Critical rules:** This is a **proposal, status: draft — nothing here is implemented.** Do not treat
28
+ any of it as the current system. The live authoring path is still hand-written SQL against
29
+ `SurfaceOverrides`; if you stay on it, apply the interim guardrails below (unique index, strict SQL
30
+ mode, autocommit on, idempotent migrations) or you will hit the silent EAV failure modes.
31
+
32
+ ## Problem
33
+
34
+ Surface config is authored as hand-written SQL against the `SurfaceOverrides` EAV table. This has:
35
+ - **No git diff / review / rollback** — config changes bypass code review and CI.
36
+ - **No team-merge story** — concurrent work on a shared surface collides with no merge tooling.
37
+ - **Silent DB failure modes** — ENUM coercion of unknown values to `''` (non-strict mode), a missing
38
+ unique constraint letting duplicate override rows pile up, and autocommit-off swallowing writes.
39
+
40
+ Cost signal: 2 of ~17 surfaces took ~5 days.
41
+
42
+ ## Recommendation
43
+
44
+ **Source of truth = git.** For each surface author a full neutral `_base/<surface>.json`, plus partial
45
+ per-client overlay files holding **only diffs**, merged by the resolver at read time. Keep the
46
+ resolver for the dynamic parts it must still compute (per-record Tier-2 evaluation + ACL
47
+ element-drop — see [surface-resolver](../features/surface-resolver.md)).
48
+
49
+ **Framing: "source of truth = git; read substrate = a choice."** Three read substrates, in order of
50
+ preference:
51
+ 1. Read the JSON files directly at read time.
52
+ 2. Seed the JSON documents into a DB column at deploy (git still authors; DB is a materialized cache).
53
+ 3. (Avoid) DB-as-master authored by hand — the status quo.
54
+
55
+ ## Optional future — bounded live toggles
56
+
57
+ An admin UI (a screen + a validating API endpoint + an `admin_flags` DB table) that writes a
58
+ **bounded, whitelisted** set of per-client feature toggles, merged at **highest precedence**. This
59
+ gives live no-deploy toggles without undermining git-as-truth, because the whitelist bounds what can
60
+ be changed outside git.
61
+
62
+ ## Interim guardrails (if staying on SQL/EAV)
63
+
64
+ - Unique index on `SurfaceOverrides(surfaceElementId, attribute, roleId, personaId, languageId)`.
65
+ - Strict SQL mode (so unknown ENUM values error instead of coercing to `''`).
66
+ - Autocommit on.
67
+ - Idempotent, re-runnable migrations.
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-01
9
+ updated: 2026-07-13
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - _underscore/Model/Core/Surface.php
@@ -137,6 +137,70 @@ breaks logic.
137
137
  `_Model_<Client>_*` overrides + `Client_ApiPayloadInterceptor` hooks — the Surface layer does
138
138
  **not** absorb business logic.
139
139
 
140
+ ## Per-client override of the Tier-1 gating RULES (not just static flags)
141
+
142
+ Originally only static flags (`isVisible`/`isEnabled`) and `config` were per-client overridable
143
+ via `SurfaceOverrides`; the **Tier-1 gating rules** (`visibilityRule`/`enabledRule`) were frozen on
144
+ the Core element. That meant two-stage-approval clients could not be expressed as data — they needed
145
+ per-client PHP. Two new override attributes make the declarative rules per-client overridable so the
146
+ two-stage behavior is **pure data**:
147
+
148
+ - `_Model_Client_SurfaceOverride` now declares `ATTRIBUTE__VISIBILITY_RULE` and
149
+ `ATTRIBUTE__ENABLED_RULE` constants.
150
+ - In `Surface.php` `_resolveElement`, the resolver decodes and applies both attributes: the locals
151
+ are seeded from the Core element's own `visibilityRule`/`enabledRule`, then overridden by any client
152
+ override row. `_castOverride` decodes them as JSON alongside `CONFIG`.
153
+
154
+ > **⚠ LOCAL only (as of 2026-07-13).** The `_underscore` code change **and** the ENUM migration below
155
+ > must ship together to beta/prod on promotion. php-reviewer pass + a PHPUnit regression test for the
156
+ > rule-override path are still outstanding.
157
+
158
+ **Mandatory ENUM migration (ships with the code).** Each client schema's
159
+ `SurfaceOverrides.attribute` ENUM must be widened *before* any rule-override row is inserted:
160
+ `ALTER TABLE Client_*.SurfaceOverrides MODIFY attribute ENUM(...,'VISIBILITY_RULE','ENABLED_RULE') NOT NULL`
161
+ across all 32 `Client_*` schemas. In **non-strict SQL mode** an insert of an unknown ENUM value
162
+ **silently coerces to `''`** — which makes the resolver ignore the rows and `NOT EXISTS` guards never
163
+ match (duplicate rows pile up). Widen the ENUM first, always.
164
+
165
+ ### Override semantics: REPLACE, not deep-merge
166
+ A `CONFIG` (and now `VISIBILITY_RULE`/`ENABLED_RULE`) override **replaces the whole decoded object**
167
+ — it does **not** deep-merge (`Surface.php` ~line 818). So a `CONFIG` override value must carry the
168
+ **full** config (e.g. `role` + `filterKey` + `additionalData`), not just the changed key.
169
+
170
+ ### Two separate gating mechanisms on the sales-order listing (do not conflate)
171
+ - The **Approvals FILTER** on the sales-order listing is driven by `config.additionalData._status`.
172
+ - The **per-button record actions** are gated by `visibilityRule`/`enabledRule`.
173
+ These are separate mechanisms on separate surfaces; a filter change is a `CONFIG` override, a
174
+ button-gating change is a rule override.
175
+
176
+ ## Core re-baselined to a neutral default + per-client diffs (decision, 2026-07-13)
177
+
178
+ Core was effectively "Client Zero = Compass", not a neutral default, so every client inherited
179
+ Compass's shape. Core was re-baselined as a true shared default so clients store **only diffs**:
180
+
181
+ 1. The approvals gate is governed by the **`navigation-approvals` ACL grant**, not per-tenant
182
+ `IS_VISIBLE` overrides.
183
+ 2. Default approvals filter = `pendingApproval` only (single-stage).
184
+ 3. No Tier-2 `config.tier2` caveat baked into Core.
185
+ 4. Edit-in-Commerce only for Compass / CompassCanada.
186
+ 5. Columns button hidden by default.
187
+
188
+ Per-client diffs layered on top:
189
+ - **Quad** — Approve + Deny only; filter narrowed to `pendingApproval`; filter button visible.
190
+ - **Compass USA / Compass Canada** — two-stage approvals: record-action `visibilityRule`/`enabledRule`
191
+ = `in [pendingApproval, pendingInitialApproval]`. **Compass Canada** additionally gets a `CONFIG`
192
+ override on the approvals filter button (surface `sales-order-listing-actions`) to add
193
+ `pendingInitialApproval`.
194
+
195
+ > ⚠ **Regression left open:** the Compass / CompassCanada **non-rule** overrides (Edit-in-Commerce
196
+ > flags, role-8 Manager hides) were **wiped during the rebuilds and not re-added**. The two-stage rule
197
+ > overrides are also LOCAL only (see above).
198
+
199
+ ### Operational gotcha — raw SQL bypasses the cache-bust
200
+ Raw SQL against the surface tables bypasses the model-layer cache-bust, so the on-disk bundle cache
201
+ at `~/toga/cache/surface_meta/*.json` **must be cleared manually** after any raw-SQL surface edit.
202
+ (Cache confusion earlier was a local-setup artifact, not an inherent design issue.)
203
+
140
204
  ## How a surfaces scripted-API call is authorized (route-level gate)
141
205
 
142
206
  Authorizing a surfaces script (`meta`, `meta-group`, `debug`) is a **two-layer** check. Get the
@@ -234,6 +298,17 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
234
298
  match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
235
299
 
236
300
  ## Change history
301
+ - 2026-07-13 — Made the Tier-1 gating RULES per-client overridable: added
302
+ `ATTRIBUTE__VISIBILITY_RULE`/`ATTRIBUTE__ENABLED_RULE` to `_Model_Client_SurfaceOverride`;
303
+ `_resolveElement` seeds locals from the Core element's rules then applies overrides;
304
+ `_castOverride` decodes them as JSON — so two-stage approvals are pure data, not per-client PHP.
305
+ Requires widening `Client_*.SurfaceOverrides.attribute` ENUM (32 schemas) first (non-strict mode
306
+ silently coerces unknown ENUM → `''`). Documented override REPLACE-not-merge semantics, the
307
+ filter (`config.additionalData._status`) vs button-rule split, the raw-SQL cache-bust bypass
308
+ (`~/toga/cache/surface_meta/*.json`), and the Core neutral-default re-baseline (ACL-gated
309
+ approvals, single-stage default, per-client diffs: Quad = Approve+Deny, Compass/CC = two-stage).
310
+ ⚠ Code + ENUM migration are LOCAL only; non-rule Compass/CC overrides wiped in rebuild not yet
311
+ re-added; php-reviewer + regression test outstanding. (apeterson)
237
312
  - 2026-07-01 — Clarified vocabulary/label per-client relabeling (new section): Vocabulary `slug`
238
313
  and VocabularyTerm `value` are stable internal ids never shown to users; the displayed label
239
314
  always resolves from `Core.Messages`; clients relabel via a `SurfaceOverrides` `LABEL_MESSAGE`
@@ -8,5 +8,5 @@
8
8
  | [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
9
9
  | [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
10
10
  | [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
11
- | [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts |
11
+ | [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts |
12
12
  | [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-30
9
+ updated: 2026-07-13
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - toga25-supply/src/surface/useFetchSurfaceMeta.ts
@@ -27,6 +27,7 @@ files:
27
27
  - toga25-supply/src/surface/index.ts
28
28
  - toga25-supply/src/pages/Login/LoginPage.tsx
29
29
  - toga25-supply/src/pages/SalesOrders/SalesOrders.tsx
30
+ - toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx
30
31
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
31
32
  - toga25-supply/src/pages/Items/ItemsPage.tsx
32
33
  - toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx
@@ -245,10 +246,24 @@ seeding NYCHH/Prudential/SPGlobal is deferred; the Client-DB prod cross-cluster
245
246
  (the path form 404s EV-6).
246
247
  - **Tier-1 only on the client.** Business-logic gates (Tier-2) arrive as resolved booleans from the
247
248
  backend (`meta.surface`); never re-encode business rules in the FE evaluator.
249
+ - **A React portal overlay's click bubbles through the COMPONENT tree, not the DOM tree.** The
250
+ SalesOrders row-actions menu (`SurfaceRowActions.tsx`) renders its dismiss overlay via a portal;
251
+ an outside-click on that fixed overlay propagated up the React component tree to the row's
252
+ `handleRowClick` and **opened the record modal**. Fix: call `e.stopPropagation()` (plus close) in
253
+ the overlay's `onClick`. Portal DOM position does not stop React synthetic-event bubbling.
254
+ - **The sticky row-actions column must be gated on the active filter.** In `SalesOrders.tsx` the
255
+ `actionColumns` array is only included when `activeFilterKey === APPROVALS_FILTER_KEY`
256
+ (`"approvals"`); otherwise the row-actions column showed unconditionally.
248
257
  - **`tsc` not yet run** this session (the private `@agilant/toga-blox` registry needs npm creds);
249
258
  treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
250
259
 
251
260
  ## Change history
261
+ - 2026-07-13 — Fixed two SalesOrders row-actions bugs: (1) the portal dismiss overlay in
262
+ `SurfaceRowActions.tsx` propagated an outside-click up the React component tree to the row handler
263
+ and opened the record modal — added `e.stopPropagation()` (+ close) on the overlay `onClick`;
264
+ (2) gated the sticky `actionColumns` array in `SalesOrders.tsx` on
265
+ `activeFilterKey === APPROVALS_FILTER_KEY` so the row-actions column only shows under the Approvals
266
+ filter. (apeterson)
252
267
  - 2026-06-30 — Follow-up: fixed the GROUPED meta fetch returning `{}` (every migrated-client
253
268
  SalesOrders section had vanished). The meta-group envelope double-nests the slug-keyed `surfaces`
254
269
  map under an outer ROUTE-keyed `surfaces` slot; the old `extractGroup` grabbed the first (outer,
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
17
17
 
18
18
  ## 2.0 framework
19
19
 
20
- - **_underscore** (_Underscore) _(framework core)_ — 28 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
+ - **_underscore** (_Underscore) _(framework core)_ — 29 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
21
21
  - **worker2** (Worker) — 27 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
22
22
  - **api2** (API) — 10 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
23
23
  - **dbchanges2** (Database Changes) _(framework core)_ — 3 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.320",
3
+ "version": "1.0.322",
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",