toga-ai 1.0.838 → 1.0.840
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.
- package/knowledge/1.0/apps/togaview/INDEX.md +1 -0
- package/knowledge/1.0/apps/togaview/architecture.md +5 -2
- package/knowledge/1.0/apps/togaview/features/login-flows.md +5 -1
- package/knowledge/1.0/apps/togaview/features/msp-dashboard.md +7 -2
- package/knowledge/1.0/apps/togaview/features/ticket-export.md +61 -0
- package/knowledge/1.0/standards/backend-php.md +13 -1
- package/knowledge/2.0/apps/api2/features/v2-rest-query-contract.md +21 -1
- package/knowledge/2.0/apps/toga-blox/workflows/dynamic-publish-pipeline.md +21 -2
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
| [MSP Dashboard & Ticket Visibility Rules](features/msp-dashboard.md) | Why tickets "disappear" in the TOGa View client portal — the per-page ticket scoping and the `tickets.customerid` / `SMBContracts` dependencies; open when an MS |
|
|
8
8
|
| [Retail Commerce Lifecycle (winback, purchase, claims, activation)](features/retail-commerce-lifecycle.md) | TOGa View retail tech-support commerce flows (winback renewal, purchase, service activation, AIG claims — chiefly ODP, Walmart B2B, Staples Protection); open fo |
|
|
9
9
|
| [Ticket Detail Page Security & Its Three Forks (common/*/ticket.php)](features/ticket-detail-page.md) | Security of the TOGa View ticket detail page `common/*/ticket.php` and its THREE near-duplicate forks (IDOR ownership checks, int-cast, name-split guard, sqlPro |
|
|
10
|
+
| [MSP Ticket Export (.xlsx)](features/ticket-export.md) | The MSP dashboard "Export Tickets" .xlsx download — single-request design, why a two-request (build then download) export cannot work behind the shared load bal |
|
|
10
11
|
| [Git & php -l Tooling Against the `_/` App Root (MSYS path mangling)](workflows/git-and-php-tooling-under-the-underscore-app-root.md) | Git Bash/MSYS on Windows mangles any `_/`-prefixed path argument (empty diff, misfired `php -l`), and togaview's app root IS `_/`; open before running git/php a |
|
|
11
12
|
| [Route 53 Hosted Zone Migration Between AWS Accounts](workflows/route53-cross-account-zone-migration.md) | Playbook to move a Route 53 hosted zone's records between AWS accounts (export → new zone → import → repoint NS); used for `togaview.com`; open for any cross-ac |
|
|
@@ -6,7 +6,7 @@ project: TOGa View
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["mhammontree", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- index.php
|
|
@@ -20,6 +20,7 @@ related:
|
|
|
20
20
|
- 1.0/apps/togadesk/architecture.md
|
|
21
21
|
- 1.0/apps/togaview/features/login-flows.md
|
|
22
22
|
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
23
|
+
- 1.0/apps/togaview/features/ticket-export.md
|
|
23
24
|
- 1.0/apps/togaview/features/ticket-detail-page.md
|
|
24
25
|
- 1.0/apps/togaview/features/retail-commerce-lifecycle.md
|
|
25
26
|
---
|
|
@@ -59,7 +60,7 @@ Architecturally it is a **multi-tenant, single-codebase MVC portal**: one deploy
|
|
|
59
60
|
- `App_MVC::parseRoute()` (in `library`) turns the request URI into route parts and resolves `mvc/<route…>/<method>.php`, where `<method>` is `get` or `post` (from the HTTP verb, overridable via a `_method` POST param). It matches longest-to-shortest, so overlapping paths can surprise — keep route folders distinct.
|
|
60
61
|
- **GET** (`get.php`) selects a template via the fallback chain above and passes data to it (no central master-layout system — each `common/<…>/<page>.php` is self-contained).
|
|
61
62
|
- **POST** (`post.php`) reads input with `App_Page::getVar()/getVarIfSet()`, does the work, then `header("Location: …"); exit;`. Login/auth, ticket creation, user management live here.
|
|
62
|
-
- AJAX requests dispatch through `App_Framework::ajaxHandler()
|
|
63
|
+
- AJAX requests dispatch through `App_Framework::ajaxHandler()`. **`?fileId` is the "skip the page shell" flag, not a filename** — a GET without it renders the shell, which flushes output (`App_Page::flushCapture`, `library/app/page.php:213`) before the route runs, so any `header()` then fails. Every download/binary route must carry a `fileId` (the value can be a fixed marker); with it, the request goes straight to `App_MVC::loadFile()`. See [MSP ticket export](features/ticket-export.md). `mvc/_TEMPLATE/` is the scaffold for new pages.
|
|
63
64
|
|
|
64
65
|
## Browser subsystem (grids & modals)
|
|
65
66
|
`_/browser/`: `Browser_Datatable_*` classes (`initialize()` registers fields/joins/DB link via `setDatabaseLinkString()` / `setTableJoins()`, `Browser_DataTableField` for cell rendering), `Browser_Modal_*` classes (capture HTML via `App_Page::startCapture()/stopCapture()`, e.g. `Browser_Modal_CreateNewTicket`, `CreatenewUser`), and `Browser_Pagination_TogaView`.
|
|
@@ -94,6 +95,7 @@ Read-only client-facing KB sourced from TOGa Desk's `db_togadesk` KB tables, gat
|
|
|
94
95
|
- New client onboarding touches at minimum: the HTTP_HOST switch in `framework.php`, a login flow (or SAML branch), and possibly the `smbContractSkus` whitelist in `login/post.php`.
|
|
95
96
|
|
|
96
97
|
## Gotchas
|
|
98
|
+
- **Never pass a file between two requests via local disk.** togaview runs on several EB instances behind a shared ALB, so `/var/www/cache` is per-instance — request 2 lands on another instance and 404s. Build and return in one request. See [MSP ticket export](features/ticket-export.md).
|
|
97
99
|
- **Template fallback is per-page, not central.** Each `get.php` re-implements the host→stylePath→togaview chain; it's easy to miss a host-specific branch.
|
|
98
100
|
- **`common/togaview/*` is the blast radius** — editing a generic page changes nearly every client at once.
|
|
99
101
|
- **Divergent session shapes** across login flows (see Session model) — always use the `emailAddress`/`email` fallback and tolerate a missing `togadeskClientId`.
|
|
@@ -105,5 +107,6 @@ Read-only client-facing KB sourced from TOGa Desk's `db_togadesk` KB tables, gat
|
|
|
105
107
|
- [TOGa Desk architecture](../togadesk/architecture.md)
|
|
106
108
|
- [Login flows & session variables](features/login-flows.md)
|
|
107
109
|
- [MSP dashboard & ticket visibility](features/msp-dashboard.md)
|
|
110
|
+
- [MSP ticket export (.xlsx)](features/ticket-export.md)
|
|
108
111
|
- [Ticket detail page security](features/ticket-detail-page.md)
|
|
109
112
|
- [Retail commerce lifecycle](features/retail-commerce-lifecycle.md)
|
|
@@ -6,7 +6,7 @@ project: TOGa View
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- mvc/login/post.php
|
|
@@ -94,9 +94,13 @@ Also note the rumcsi SAML `acs` case (:459-472) sets **no** session variables on
|
|
|
94
94
|
Only togadesk writes it (`desk/includes/functions.php:588/635/672/689`, plus `actions/user/loginAsAnother.php:17-18`). Both togaview login paths set session state only. So **`lastlogin` cannot measure portal usage** — a portal user logging in daily still shows NULL. See [TOGa Desk staff login](../../togadesk/features/staff-login-authentication.md).
|
|
95
95
|
|
|
96
96
|
### Other
|
|
97
|
+
- **towfoundation has a real password login path despite being a SAML host** — `mvc/login/post.php:82-96` matches `people.password = sha1($pass)` + `isActive = 1` + `clientid` = TOW Foundation; `people.type = 'admin'` maps to `USERTYPE_ADMIN` and lands `/msp_dashboard`. Useful for testing a SAML tenant locally with no IdP.
|
|
97
98
|
- **`people.password` is a bare SHA-1** (`mvc/login/post.php:21/96/471`) and the hash is also carried in `$_SESSION['Contact']['password']`. Known and accepted for now — togaview is migrating to 2.0, so no remediation is planned here; do not "fix" it as a side effect of another ticket.
|
|
98
99
|
- Code consuming the session must read `emailAddress` with an `email` fallback (and tolerate missing `togadeskClientId`) or it rejects all staplesprotection users — this broke the ticket.php ownership check until the fallback was added (June 2026).
|
|
99
100
|
|
|
101
|
+
## Change history
|
|
102
|
+
- 2026-09-18 — Recorded towfoundation's password login path (SAML host with a live sha1 password flow) (mhammontree)
|
|
103
|
+
|
|
100
104
|
## Related
|
|
101
105
|
- [MSP dashboard & ticket visibility](msp-dashboard.md)
|
|
102
106
|
- [Ticket detail page](ticket-detail-page.md)
|
|
@@ -6,7 +6,7 @@ project: TOGa View
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- common/togaview/msp_dashboard.php
|
|
@@ -15,6 +15,7 @@ files:
|
|
|
15
15
|
- mvc/support/support.php
|
|
16
16
|
related:
|
|
17
17
|
- 1.0/apps/togaview/features/login-flows.md
|
|
18
|
+
- 1.0/apps/togaview/features/ticket-export.md
|
|
18
19
|
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
19
20
|
- 1.0/apps/togadesk/features/smb-contract-editing.md
|
|
20
21
|
---
|
|
@@ -50,14 +51,18 @@ Different pages scope tickets **differently**, and the MSP pages depend on `tick
|
|
|
50
51
|
- An MSP's "customers" are **TOGA-side `Customers` rows**, NOT togadesk clients. All of an MSP's tickets carry the MSP's own `tickets.clientid`.
|
|
51
52
|
|
|
52
53
|
### Client variations
|
|
53
|
-
- The single-department pattern still exists in the `newcenturyholdingsllc` and `towfoundation` dashboard variants (
|
|
54
|
+
- The single-department pattern still exists in the `newcenturyholdingsllc` and `towfoundation` dashboard variants (`departmentid = $_SESSION['clientDepartmentId']`, towfoundation `:271` + the count queries from `~:287`) and in enterprise_dashboard's `clientDepartmentId` filter for end users. **Those counters are the stale/buggy side — the unfinished half of the June 2026 fix.** The .xlsx export has never filtered by department and deliberately still does not (adding one would spread the bug) — so on those two forks the export legitimately returns more rows than the counters. See [Ticket export](ticket-export.md).
|
|
54
55
|
- Only towfoundation / newcenturyholdingsllc have their own dashboard variants; everyone else runs the generic `common/togaview/` pages.
|
|
55
56
|
|
|
56
57
|
## Gotchas
|
|
57
58
|
- "You have no assigned clients" usually means the MSP's `SMBContracts.clientMspId` was corrupted by the togadesk contract modal (see smb-contract-editing doc) — check data before debugging code.
|
|
58
59
|
- An expired `SMBContracts.dateContractEnd` is display-only; it does not hide tickets.
|
|
59
60
|
|
|
61
|
+
## Change history
|
|
62
|
+
- 2026-09-18 — Noted the towfoundation/newcentury counters still use the single-department filter; export is department-wide by design (mhammontree)
|
|
63
|
+
|
|
60
64
|
## Related
|
|
61
65
|
- [Login flows](login-flows.md)
|
|
66
|
+
- [MSP ticket export (.xlsx)](ticket-export.md)
|
|
62
67
|
- [TOGa Desk ticket lifecycle](../../togadesk/features/ticket-lifecycle.md)
|
|
63
68
|
- [SMB contract editing](../../togadesk/features/smb-contract-editing.md)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MSP Ticket Export (.xlsx)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: togaview
|
|
5
|
+
project: TOGa View
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-18
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- _/app/export/ticketexport.php
|
|
13
|
+
- mvc/msp_dashboard/export/get.php
|
|
14
|
+
- common/togaview/msp_dashboard.php
|
|
15
|
+
- common/towfoundation/msp_dashboard.php
|
|
16
|
+
- common/newcenturyholdingsllc/msp_dashboard.php
|
|
17
|
+
related:
|
|
18
|
+
- 1.0/apps/togaview/features/msp-dashboard.md
|
|
19
|
+
- 1.0/apps/togaview/architecture.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
The MSP dashboard "Export Tickets" .xlsx download — single-request design, why a two-request (build then download) export cannot work behind the shared load balancer, and the identity gate across the three dashboard forks.
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
`App_Export_TicketExport` (`_/app/export/ticketexport.php`) builds the ticket workbook for all three msp_dashboard forks. `mvc/msp_dashboard/export/get.php` builds it **in one request**, reads it fully into memory, then sends download headers. Nothing touches shared disk and the endpoint takes **no** user input.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
1. Export button hits `mvc/msp_dashboard/export/get.php` with a `fileId` parameter (see gotcha — required, value is only a marker).
|
|
29
|
+
2. Route gates on identity: userType in (CUSTOMER, MSP, ADMIN) **and** `$_SESSION['togadeskClientId']`; failure → `App_JavaScript::jsRedirect('/login')`.
|
|
30
|
+
3. `App_Export_TicketExport` builds the workbook, writes it to `tempnam(sys_get_temp_dir(), …)`, `file_get_contents()` reads it back, `unlink()` in a `finally`.
|
|
31
|
+
4. Only then are download headers + the bytes sent. On failure → redirect to `?exportFailed=1`.
|
|
32
|
+
|
|
33
|
+
**Scope:** the export is deliberately **not** filtered by `departmentid` — it returns all of the client's tickets and therefore more rows than the dashboard counters. Verified on production (clientid 168: 693 tickets across departments 307/312/310/0, all owned by TOW Foundation). Not a leak — see [msp-dashboard](msp-dashboard.md) for the stale single-department counters on two forks.
|
|
34
|
+
|
|
35
|
+
**Shared class, three forks.** Before 2026-09-18 each of `common/{togaview,towfoundation,newcenturyholdingsllc}/msp_dashboard.php` carried its own `getExportFileName()` copy (already drifted). All three now call `App_Export_TicketExport`. Autoload: `App_Export_TicketExport` → `__APPROOT__/app/export/ticketexport.php`, i.e. togaview's `_/` folder (same placement as `App_Framework_TogaView`).
|
|
36
|
+
|
|
37
|
+
## Gotchas
|
|
38
|
+
|
|
39
|
+
### Never pass a file between two requests via local disk (EB + shared ALB)
|
|
40
|
+
The original export was two requests: request 1 wrote the .xlsx to `$config['internal']['cache_folder']` = `/var/www/cache` and echoed the name; request 2 downloaded it. togaview runs on several Elastic Beanstalk instances behind a **shared** Application Load Balancer (`.ebextensions/050_register_instance_to_shared_application_load_balancer.config`), and `/var/www/cache` is **per-instance** local disk (`.ebextensions/007_setup_export_cache_folders.config`). Request 2 usually landed on a different instance → "File not found. Please try exporting again." Intermittent, so it looked flaky, not broken. Applies to **any** 1.0 app on EB behind the shared ALB. The s3fs mount exists but `cache_folder` does not use it. *(Open: 007 + `ebs/setup_export_cache_folders.php` now have zero readers — dead infra, separate cleanup.)*
|
|
41
|
+
|
|
42
|
+
### A download route in togaview MUST carry `fileId`
|
|
43
|
+
`App_Framework_TogaView::renderIndex()` (`_/app/framework.php` ~1265): a GET **without** `fileId` renders the page shell, which calls `App_Page::flushCapture()` (`library/app/page.php:213`) and flushes output **before** the route file runs — any `header()` then fails with "Cannot modify header information". A GET **with** `fileId` skips the shell and goes straight to `App_MVC::loadFile()`. The value can be a fixed marker; it need not be a filename.
|
|
44
|
+
|
|
45
|
+
### Build the whole file, then send headers — never stream
|
|
46
|
+
`App_Error::handleError` turns **every** PHP warning into `handleException`, which `exit`s (`library/app/error.php` ~407 and ~671). Bytes already on the wire → HTTP 200 with a truncated .xlsx plus an error page: a silent corrupt file, worse than a visible error. Buffering first keeps a clean redirect possible.
|
|
47
|
+
|
|
48
|
+
### The vendored PhpSpreadsheet Xlsx writer takes a filename, not a stream
|
|
49
|
+
`library/PhpOffice/PhpSpreadsheet/Writer/Xlsx.php::save($pFilename)` (:173) calls `strtolower($pFilename)` (:181) — a stream resource fails with "strtolower() expects parameter 1 to be string, resource given". Stream support landed in a later release. Use `tempnam()` + `file_get_contents()` + `unlink()`; the temp file lives inside one request, so it cannot recreate the load-balancer bug.
|
|
50
|
+
|
|
51
|
+
### Lint and run togaview with PHP 7.2
|
|
52
|
+
Use `xampp_7_2_33`'s `php.exe`. Linting under PHP 8 passes, but a CLI run dies on a vendored PhpSpreadsheet deprecation (`Worksheet\Iterator::current()` return type), which is fatal under `App_Error` — a misleading failure.
|
|
53
|
+
|
|
54
|
+
### Don't assume one userType across the forks
|
|
55
|
+
`common/togaview/msp_dashboard.php:16` requires CUSTOMER or MSP; `common/towfoundation/msp_dashboard.php:234` and `common/newcenturyholdingsllc/msp_dashboard.php:235` require **ADMIN**. All three also require `togadeskClientId`. A shared gate must be the **union**, or every towfoundation/newcentury admin loses the button they can see.
|
|
56
|
+
|
|
57
|
+
### Anti-pattern this replaced (was live in production)
|
|
58
|
+
The old `mvc/msp_dashboard/export/get.php` gated only on `isset($_GET['fileId'])` — no login check — then `readfile()`d **and** `unlink()`d `$cacheDir . '/' . $fileName`, validated only by `substr($fileName, -5) === '.xlsx'` and `file_exists()`. `$_GET` passes through `App_String::xssProtect()` (= `htmlspecialchars()`), which touches neither `.` nor `/`, so `../` traversal worked: unauthenticated arbitrary read **and** arbitrary delete. Filenames were also `Tickets_Export_<unix timestamp>.xlsx` in one shared folder — a guessable cross-tenant read with no traversal at all. **An extension check plus `file_exists()` is not a traversal defense, and `htmlspecialchars()` is not path sanitization.** Same family as the TRUE-78835 / TRUE-80671 togaview IDORs.
|
|
59
|
+
|
|
60
|
+
## Change history
|
|
61
|
+
- 2026-09-18 — Export collapsed to one request (was broken by the shared ALB), extracted to `App_Export_TicketExport`, identity-gated, and the unauthenticated traversal/delete endpoint removed (TRUE-81955) (mhammontree)
|
|
@@ -5,7 +5,7 @@ project: Library
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-09-
|
|
8
|
+
updated: 2026-09-18
|
|
9
9
|
owners: [jcardinal, rgirish, mhammontree, ajean]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
@@ -553,6 +553,18 @@ Note: the 2.0 metadata-driven API features (Record Scripts, API Payload Intercep
|
|
|
553
553
|
- **Remove all `debugVar()` calls before merging.**
|
|
554
554
|
- Don't extend `DOA_`-prefixed code; plan its removal when you touch the area and dependencies allow.
|
|
555
555
|
|
|
556
|
+
## Binary downloads: build the whole file, then send headers
|
|
557
|
+
|
|
558
|
+
Never stream a generated file straight to the browser. `App_Error::handleError` promotes every
|
|
559
|
+
PHP warning to `handleException`, which `exit`s (`library/app/error.php` ~407 and ~671). If bytes
|
|
560
|
+
are already on the wire, the user gets HTTP 200 with a truncated file plus an error page — a
|
|
561
|
+
silent corrupt download, worse than a visible failure. Build it fully in memory (or a per-request
|
|
562
|
+
`tempnam()`), then send headers, then echo. A failure before the headers can still redirect.
|
|
563
|
+
|
|
564
|
+
Never persist a generated file on local disk to hand to a *second* request. Our EB apps run on
|
|
565
|
+
several instances behind a shared load balancer, so per-instance paths like `/var/www/cache`
|
|
566
|
+
are not shared and the follow-up request intermittently 404s. One request, start to finish.
|
|
567
|
+
|
|
556
568
|
## Documentation
|
|
557
569
|
|
|
558
570
|
- Keep code well-documented with DocBlocks and usage examples.
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: [tcox, bala, apeterson, jcardinal, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -364,6 +364,23 @@ successfully.
|
|
|
364
364
|
> `group=_status` is separately known to fail. And `where` still never prunes a nested child array
|
|
365
365
|
> (see below).
|
|
366
366
|
|
|
367
|
+
#### ⚠ `in` instead of an OR-chain of `contains` shortens the SQL - it does NOT make the filter fast
|
|
368
|
+
|
|
369
|
+
A calculated field's expression is inlined per row, so **no operator makes it indexable**. Swapping
|
|
370
|
+
an OR chain of `contains` for one `in` clause only shrinks the query text.
|
|
371
|
+
|
|
372
|
+
Measured on sandbox-client 2026-09-18 (TRUE-81379, Compass approvals list): after the blox `in`
|
|
373
|
+
serializer shipped, the request sends the single clause
|
|
374
|
+
`where=(_status:in:pendingApproval:pendingInitialApproval)` - and the endpoint still takes
|
|
375
|
+
**14.66 s** (`GET /v2/sales-orders?recordsPerPage=15&page=1&sort=-number&...`, plus joins on
|
|
376
|
+
Addresses and ojoins on Locations/Contacts/Users). `_status` on SalesOrders is calculated, so every
|
|
377
|
+
sales order (~11,000 in `pendingApproval` alone) is evaluated before paging - strongly supported,
|
|
378
|
+
but not yet proven against the generated SQL.
|
|
379
|
+
|
|
380
|
+
**Do not read `in` in the network tab as "the slow filter is fixed."** Making it fast means taking
|
|
381
|
+
the calculated field out of the filter path (a stored/indexed status column, or a pre-filtered set),
|
|
382
|
+
not tuning the serializer.
|
|
383
|
+
|
|
367
384
|
## Encoding rules (the query string is never urldecoded at parse time)
|
|
368
385
|
|
|
369
386
|
- Encode a literal `%` in a LIKE pattern as **`%25`**.
|
|
@@ -540,6 +557,9 @@ If you need the behaviour, loop uuids client-side. Point 3 is the durable, non-o
|
|
|
540
557
|
[V2 request logging](request-logging.md).
|
|
541
558
|
|
|
542
559
|
## Change history
|
|
560
|
+
- 2026-09-18 — Recorded that swapping an OR-chain of `contains` for a single `in` on a
|
|
561
|
+
calculated field only shortens the SQL text and does not speed it up: the Compass approvals list
|
|
562
|
+
(`_status:in:...`) still takes 14.66 s with the `in` clause live (TRUE-81379). (apeterson)
|
|
543
563
|
- 2026-09-17 — Documented that **bulk `DELETE` by `where` is implemented but unused and unsafe to
|
|
544
564
|
adopt**: the branch is real (`V2.php:5554` / `:5697` / `:5740`, returns `meta.deletedRecordCount`)
|
|
545
565
|
but has zero callers, is non-atomic with no rollback, and — the decisive part — **zero matching
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: [jcardinal, apeterson, tcox, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/.github/workflows/publish.yml
|
|
@@ -32,6 +32,10 @@ Core model:
|
|
|
32
32
|
- **Version derivation (CI owns the suffix; dev owns only the base).** CI reads `version` from `package.json`, **strips any `-<suffix>`**, does **`patch + 1`**, then appends `-<mode>.<GITHUB_RUN_NUMBER>`. Base `1.0.317` on `_sandbox-client` run 104 publishes `1.0.318-sandbox-client.104`. The developer controls only the BASE version committed in `package.json` on the feature branch. **Never hand-write the `-<mode>.N` suffix** — CI generates it. There is no manual `npm publish`; CI is the only publisher.
|
|
33
33
|
- **A GitHub branch and an npm channel are unrelated.** Creating a `_<mode>` branch in a *consuming app* repo (e.g. `toga2-commerce`) only tells Amplify what to build — it does **not** create a blox channel. A channel exists **only** when this repo (`toga-blox-npm`) publishes with `npm publish --tag <mode>`, which `publish.yml` now does automatically for any `_<mode>` branch.
|
|
34
34
|
|
|
35
|
+
- **The trailing `.N` is the shared `GITHUB_RUN_NUMBER`**, not a per-package counter. It counts runs across **all** `_*` branches, so gaps in a channel's sequence (e.g. `.164`–`.167` missing) are runs that published other branches — never a failed or lost publish.
|
|
36
|
+
- **CI never commits the version back.** Step 9 runs `npm version --no-git-tag-version`, so `package.json` in git stays at the base (e.g. `1.1.9`) forever and never matches what npm shows. That mismatch is by design.
|
|
37
|
+
- **⚠ Never hand-bump `package.json` to a published prerelease value.** Committing `1.1.10-sandbox-client.169` makes CI strip the suffix, read the base as `1.1.10`, and publish `1.1.11-...` next run — silently moving the base a patch. Edit `package.json` only to deliberately move the base (e.g. `1.1.9` → `1.2.0`).
|
|
38
|
+
|
|
35
39
|
**What was removed (and why it's safe):** the old workflow hardcoded a branch list (`_production/_gamma/_beta/_qc-security`) with a production special case: `_production` published dist-tag `latest` with a plain (non-prerelease) version, committed the bump back, and git-tagged. **All gone.** Consequence: the workflow no longer maintains the `@latest` npm tag. Fine because consumers never install bare `npm install @agilant/toga-blox`; they install by explicit `@<mode>` channel (commerce's `amplify.yml`) or by a pinned/ranged version in `package.json`.
|
|
36
40
|
|
|
37
41
|
## Steps
|
|
@@ -74,6 +78,21 @@ Practical consequence: a consumer's `.npmrc` needs the FontAwesome registry line
|
|
|
74
78
|
|
|
75
79
|
**⚠ Never commit a token VALUE in `.npmrc`.** Live FontAwesome tokens have been found **committed** in `toga25-supply/.npmrc` and `info/.npmrc`, and one was pushed in `bdr` commit `88809c3`. Treat every token found this way as compromised and rotate it. The correct shape is an env-var placeholder resolved at build time — exactly what blox's own `publish.yml` does (see the "Secret hygiene done right" gotcha below). This is the same violation recorded in [`../../../standards/frontend.md`](../../../standards/frontend.md); what is **new here is that only the FontAwesome token was ever needed** — the npmjs token in those files bought nothing.
|
|
76
80
|
|
|
81
|
+
### You cannot publish blox from a laptop — and `npm login` makes it worse
|
|
82
|
+
|
|
83
|
+
Publishing **is** the push to a `_*` branch; there is no supported local path.
|
|
84
|
+
|
|
85
|
+
- The publish credential is the GitHub Actions secret **`NPM_TOKEN`** (granular, bypass-2FA), consumed as `NODE_AUTH_TOKEN`. It lives only in GitHub secrets — never on a laptop.
|
|
86
|
+
- npm requires **either** an interactive 2FA code **or** a granular bypass-2FA token to publish. The `agilant-blox` npm account has **no 2FA**, so the bypass token is the only route.
|
|
87
|
+
|
|
88
|
+
| Symptom | What it means |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `403 Forbidden - PUT .../@agilant%2ftoga-blox … Two-factor authentication or granular access token with bypass 2fa enabled is required` | You are on a web-session token — which is what `npm login` writes over the `//registry.npmjs.org/:_authToken=` line in `~/.npmrc`. Not publishable. |
|
|
91
|
+
| `404 Not Found - PUT` | No token at all; npm returns 404, not 401, for an unauthenticated publish. Easy to misread as "bad version". |
|
|
92
|
+
| `npm whoami` prints `agilant-blox` | Proves only that the token can **read**. Says nothing about publish rights or the bypass flag — never treat it as proof publishing will work. |
|
|
93
|
+
|
|
94
|
+
Dates: the **August 2026** npm change removed 2FA-bypass for *account and package management* only (token creation, maintainer/org changes) — publishing was unaffected. Bypass-2FA tokens lose **direct publish around January 2027**; move `publish.yml` to trusted publishing (OIDC) or staged publishing before then. That is a CI change, since CI is already the only publisher.
|
|
95
|
+
|
|
77
96
|
## Gotchas
|
|
78
97
|
|
|
79
98
|
- **⚠ There is a STALE UNDERSCORED dist-tag on the registry — `_sandbox-client` (with the underscore) is NOT the channel.** Re-verified 2026-09-02, the registry still carries **both** `_sandbox-client` = `1.0.316-sandbox-client.1` (vestigial, predates the current pipeline; it has **not** moved in over a month) **and** `sandbox-client` = `1.1.2-sandbox-client.142` (the live channel head). Branch `_<mode>` maps to channel `<mode>` with the underscore **stripped** (same convention as `amplify.yml`), so the **branch** `_sandbox-client` tracks the **tag** `sandbox-client`. Asking npm for "the latest `_sandbox-client` tag" literally installs something **141 publishes behind**. Always resolve the head with `npm view @agilant/toga-blox dist-tags` and install the underscore-less name.
|
|
@@ -88,7 +107,7 @@ Practical consequence: a consumer's `.npmrc` needs the FontAwesome registry line
|
|
|
88
107
|
3. No feature branch pending merge **touches the `version` line** — normally true, because feature branches never edit it.
|
|
89
108
|
If any one fails, bump on the feature branch instead. Note the bump commit lands *only* on `_sandbox-client` and is never merged back, which is precisely what generates the "N behind" release plumbing.
|
|
90
109
|
- **`gh` is not installed on every dev machine.** The skill's `gh run watch` monitoring step simply does not work there. Fall back to polling the registry: `npm view @agilant/toga-blox@sandbox-client version` (or `npm dist-tag ls @agilant/toga-blox`) until the new version appears — the registry is the authoritative confirmation anyway.
|
|
91
|
-
- **
|
|
110
|
+
- **A publish is only half the deploy — and the consumer must depend on the LITERAL dist-tag.** In `toga25-supply/package.json` the dependency is the bare tag string (`"@agilant/toga-blox": "sandbox-client"`), **not** a version range. `npm install @agilant/toga-blox@sandbox-client` rewrites it to a `^` range, which then stops following base bumps — re-set the literal tag, re-run `npm install`, commit `package-lock.json`. Verify what is installed with `node -p "require('./node_modules/@agilant/toga-blox/package.json').version"`. Exact pins drift: branch `TRUE-81379` was pinned to `1.1.3-production.166` while `node_modules` held `1.1.10-sandbox-client.163` — check this first when a fix "works locally but not after deploy". Likewise the feature branch keeps its old base version after a bump lands on `_sandbox-client`; that is expected.
|
|
92
111
|
- **⚠ Credentials have been found in the local git remote URL.** Verified 2026-08-26: the `origin` remote for the `toga-blox-npm` checkout had a **GitHub PAT embedded in plaintext in the URL** (visible to anything that runs `git remote get-url origin`, and to any tool that reads `.git/config`). Check for this and re-point the remote at SSH or a credential helper; treat any token found this way as compromised and rotate it. This is the local-config sibling of the committed-`.npmrc` violation already recorded in `../../../standards/frontend.md`.
|
|
93
112
|
- **FontAwesome v7 `style`-prop TS2322** (fixed 2026-07-21 in `getFontAwesomeIcon.tsx`, and a reusable pattern for any consumer). FA v7 types the `FontAwesomeIcon` `style` prop as `CSSProperties & CSSVariables`, where `CSSVariables` requires a `--fa-*` custom-property index signature that plain `React.CSSProperties` lacks. Cast the passed style to `React.CSSProperties & Record<\`--${string}\`, string>` — the value type must be `string` (**not** `string | number`; `CSSVariables` requires `string`). Runtime and function signature are unchanged, so it is safe for the ~40 consumers.
|
|
94
113
|
- **The `_production`/`_beta` build copies only `.scss` — a plain `.css` component stylesheet is silently dropped from the tarball.** On `_production` and `_beta` the `build` script is `tsc && copyfiles -u 1 "src/**/*.scss" dist`: `tsc` compiles a component's `import "./Foo.css"` into the emitted JS, but `copyfiles` never ships the `.css`, so the published package contains JS importing a file that isn't in the tarball. Consumers then fail their Vite/Rollup build with `Could not resolve "./Foo.css"` (seen with `EnvironmentBadge` in `@agilant/toga-blox@beta` 1.0.316-beta.108, breaking `toga2-commerce`). **Convention on the prod/beta build: author component stylesheets as `.scss`, never `.css`** (a flat `.css` file is valid SCSS as-is, so renaming is the zero-risk fix — no build-script change). Note the build description in `architecture.md` (copies `*.scss/*.css/*.module.css`) reflects the in-progress `feature-new-table` branch, **not** the current `_production`/`_beta` build; that feature branch is not to be merged to prod/beta just to pick up the wider copy glob.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -9,7 +9,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 13 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
12
|
-
- **togaview** (TOGa View) —
|
|
12
|
+
- **togaview** (TOGa View) — 8 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
13
13
|
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
|
14
14
|
- **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
|
|
15
15
|
- **test** (Test) — 16 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
package/package.json
CHANGED