toga-ai 1.0.342 → 1.0.344
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/2.0/apps/_underscore/features/cloud-s3-helpers.md +11 -1
- package/knowledge/2.0/apps/api2/features/surface-meta-option.md +22 -2
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/compass-vip-support-importer.md +125 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/profile.md +5 -1
- package/package.json +1 -1
|
@@ -7,11 +7,12 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-07-14
|
|
10
|
-
owners: ["jcardinal"]
|
|
10
|
+
owners: ["jcardinal", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Cloud.php
|
|
13
13
|
related:
|
|
14
14
|
- ../../worker2/features/oneuptime-worker2-monitoring.md
|
|
15
|
+
- ../../worker2/features/compass-vip-support-importer.md
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
## Summary
|
|
@@ -58,8 +59,17 @@ None — shared core helper.
|
|
|
58
59
|
do not add your own `ContinuationToken` loop on top.
|
|
59
60
|
- A bucket outside the config-default us-east-1 (e.g. `agilant-as2` in us-west-2) throws
|
|
60
61
|
`AuthorizationHeaderMalformed` unless you pass `$awsRegion`.
|
|
62
|
+
- **`getFileFromS3` (the GET method) is single-region.** It builds the `S3Client` with region
|
|
63
|
+
`_Config::cloud('aws_region')` (the worker's own region) and only swallows HTTP 404. Reading
|
|
64
|
+
an object from a bucket in a **different** region raises a `301 PermanentRedirect`, which it
|
|
65
|
+
re-throws. To read a cross-region (or public) S3 object, use a direct HTTPS download instead,
|
|
66
|
+
or construct an `S3Client` with the bucket's own region. (Unlike `getS3Objects()`, `getFileFromS3`
|
|
67
|
+
has no region-override parameter.)
|
|
61
68
|
|
|
62
69
|
## Change history
|
|
70
|
+
- 2026-07-15 — Documented that `getFileFromS3` is single-region (client built with the worker's
|
|
71
|
+
own `aws_region`, only swallows 404, re-throws `301 PermanentRedirect` for cross-region reads);
|
|
72
|
+
use a direct HTTPS download for cross-region/public objects. (bala)
|
|
63
73
|
- 2026-07-14 — `getS3Objects()` gained an optional `?string $awsRegion = null` 3rd param
|
|
64
74
|
(falls back to `_Config::cloud('aws_region')`); needed because `agilant-as2` is in
|
|
65
75
|
us-west-2, not the config-default us-east-1. Additive — 2-arg callers unchanged. (jcardinal)
|
|
@@ -6,8 +6,8 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-07-15
|
|
10
|
+
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
- _underscore/Model/Core/Surface.php
|
|
@@ -61,7 +61,27 @@ traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work
|
|
|
61
61
|
`Client.AclRecordScripts` dispatch grant — it 403'd EZ-1 on dev-sandbox until granted, exactly like
|
|
62
62
|
`meta` did. See [surface-resolver](../../_underscore/features/surface-resolver.md).
|
|
63
63
|
|
|
64
|
+
- **The tell that it's the dispatch-grant gate (not record-level READ) failing: the 403 EZ-1
|
|
65
|
+
response carries `identifiers.script` set to the script route** (e.g. `"meta"`). If you see
|
|
66
|
+
`identifiers.script`, chase `AclRecordScripts`, not `AclRecordPermissions`.
|
|
67
|
+
- **Which roles the dispatch check reads depends on the record's `aclDatabase`.** The `surfaces`
|
|
68
|
+
Record has `aclDatabase = CORE`, so `getRecordScriptPhpMethod` matches the caller's **CORE** roles
|
|
69
|
+
(`Core.Roles`: 1=Public, 2=Integrations, 3=Super User, 4=Base) against the `AclRecordScripts` rows —
|
|
70
|
+
even though those rows physically live in each **client** DB. The public login surface authenticates
|
|
71
|
+
as CORE Public(1). (⚠ This corrects the earlier "keyed on client roles" note in
|
|
72
|
+
[surface-resolver](../../_underscore/features/surface-resolver.md) — the two disagree; confirm
|
|
73
|
+
against `V2.php getRecordScriptPhpMethod` before relying on either.)
|
|
74
|
+
- **Seed the dispatch grants in EVERY client DB.** Grant pattern: `meta` + `meta-group` →
|
|
75
|
+
Public(1)/Super User(3)/Base(4); `debug` → Super User(3) only. A client DB missing the grant 403s
|
|
76
|
+
that tenant even though the code and Core metadata are identical — new/pre-existing client DBs
|
|
77
|
+
silently lack it.
|
|
78
|
+
|
|
64
79
|
## Change history
|
|
80
|
+
- 2026-07-15 — Documented the diagnostic tell (`identifiers.script` present on the 403 EZ-1 → it is
|
|
81
|
+
the `AclRecordScripts` dispatch gate, not record READ); clarified the dispatch check reads the
|
|
82
|
+
caller's **CORE** roles because `surfaces.aclDatabase = CORE` (contradicts the earlier "client
|
|
83
|
+
roles" note — flagged for reconciliation); and that the per-role grants (`meta`/`meta-group` →
|
|
84
|
+
1/3/4, `debug` → 3 only) must be seeded in every client DB. (apeterson)
|
|
65
85
|
- 2026-06-30 — Noted the grouped `meta-group` endpoint (batched multi-slug resolve) and that it needs
|
|
66
86
|
its own per-role `AclRecordScripts` dispatch grant (403 EZ-1 until granted). (jcardinal)
|
|
67
87
|
- 2026-06-29 — Documented the second ACL layer: scripted-API dispatch is authorized via
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
| [ClickUp Rich-Text Custom Fields via Quill Delta (API)](features/clickup-richtext-api.md) | ClickUp custom text fields (type `text` and long-text) support rich formatting only through a **Quill Delta** written to the undocumented `value_richtext` key o | test/@dave/clickup_md2delta.js, .claude/skills/plan-ticket/scripts/clickup.js |
|
|
12
12
|
| [ClickUp Subtask Activity → Parent Opportunity/Epic Comments](features/clickup-subtask-activity.md) | Surfaces **subtask** progress, completion, and discussion on the top-level **Opportunity** or **Epic** it rolls up to, so a deal/project owner sees activity whe | worker2/Worker/Clickup/Subtask.php, worker2/Worker/Clickup.php, dbchanges2/Team/2026-07-14a - Add ClickupSubtaskActivity ledger.sql |
|
|
13
13
|
| [ClickUp Work Type Automation (Committed / Conditional / Stretch)](features/clickup-work-type-automation.md) | The ClickUp webhook handler (`_Worker_Clickup`) automatically maintains each task's **Work Type** custom field — `Committed`, `Conditional`, or `Stretch` — base | worker2/Worker/Clickup.php, worker2/Tests/Worker/ClickupWorkTypeTest.php |
|
|
14
|
+
| [Compass VIP Support Importer (worker2)](features/compass-vip-support-importer.md) | A worker2 action that ingests Compass's quarterly VIP spreadsheet and assigns each VIP user's support technician by setting `Users.c_supportedByUserId` in `Clie | worker2/Worker/Client/Compass/VipSupport.php |
|
|
14
15
|
| [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
|
|
15
16
|
| [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
|
|
16
17
|
| [Etilize Catalog Item Import & Refresh](features/etilize-catalog-item-import.md) | Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. | worker2/Worker/Etilize/Items.php |
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass VIP Support Importer (worker2)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-15
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Client/Compass/VipSupport.php
|
|
13
|
+
related:
|
|
14
|
+
- ./creating-worker-actions.md
|
|
15
|
+
- ../../../clients/compass-usa/profile.md
|
|
16
|
+
- ../../_underscore/features/cloud-s3-helpers.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
A worker2 action that ingests Compass's quarterly VIP spreadsheet and assigns each VIP
|
|
22
|
+
user's support technician by setting `Users.c_supportedByUserId` in `Client_Compass`. It is
|
|
23
|
+
invoked **manually via Postman** (no cron). Given a file, it stages one update per matching
|
|
24
|
+
VIP row, applies them in a single transaction, and returns a JSON report of what was updated
|
|
25
|
+
and — with a per-row reason — what was skipped.
|
|
26
|
+
|
|
27
|
+
- **Action:** `Client/Compass/VipSupport/ProcessVipFile`
|
|
28
|
+
- **Params:** `{"filePath": "<local path OR http(s) URL>"}`
|
|
29
|
+
|
|
30
|
+
## Key files / entry points
|
|
31
|
+
|
|
32
|
+
- `worker2/Worker/Client/Compass/VipSupport.php` — `ProcessVipFile(string $filePath): string`.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
1. **Locate the input.** `filePath` may be a local filesystem path **or** an `http(s)` URL.
|
|
37
|
+
A URL (e.g. a public S3 object) is downloaded via cURL to a temp file first (see
|
|
38
|
+
*Remote input* below); a local path is used as-is.
|
|
39
|
+
2. **Find the header row.** Compass now prefixes the sheet with a **title-banner row above
|
|
40
|
+
the header**, so the reader does not assume row 1. `findHeaderRowNumber()` scans for the
|
|
41
|
+
row whose first cell equals `PERS. NO.` and uses that as the header; the banner above it
|
|
42
|
+
is ignored.
|
|
43
|
+
3. **Validate headers.** The header row is checked against the 12 `EXPECTED_HEADERS`
|
|
44
|
+
(`PERS. NO.`, `FIRST NAME`, `LAST NAME`, `USERNAME`, `EMAIL ADDRESS`, `POSITION`,
|
|
45
|
+
`COST CTR`, `SECTOR`, `COUNTRY`, `LOCATION`, `SUPPORT TECH`, `Support Tech Email`).
|
|
46
|
+
A missing/reordered column throws.
|
|
47
|
+
4. **Load users.** `loadUserMaps()` loads **every** user (active + inactive) from
|
|
48
|
+
`Client_Compass.Users` into two maps keyed by lowercased `c_hrEmpUsername` and `email`,
|
|
49
|
+
carrying the `isActive` flag. `ORDER BY isActive DESC` means an active row wins a
|
|
50
|
+
duplicate key, and a skip can distinguish "inactive" from "not in the DB".
|
|
51
|
+
5. **Evaluate each data row.** For every row below the header, `evaluateVipRow()` (guard
|
|
52
|
+
clauses / early return — no `continue`) returns either a staged update
|
|
53
|
+
`{id, c_supportedByUserId}` or a structured skip `{row, user, reason}`.
|
|
54
|
+
6. **Stop at the first fully-empty row.** `isEntirelyEmptyRow()` (all 12 columns empty) marks
|
|
55
|
+
the end of the list; the loop breaks and reports `stoppedAtRow`. Rows below it (including a
|
|
56
|
+
trailing legend/note row) are never processed. A *partial* row (only a support tech, or a
|
|
57
|
+
note in one column) is **not** a stop.
|
|
58
|
+
7. **Apply.** Staged updates are applied in batches inside one transaction
|
|
59
|
+
(`buildBatchUpdateSql` — a `CASE id WHEN … THEN …` update); rollback on any failure.
|
|
60
|
+
8. **Report.** Returns JSON `{updated, skipped, stoppedAtRow, skippedRows[]}`. There is no
|
|
61
|
+
echo logging — the returned JSON is the entire output.
|
|
62
|
+
|
|
63
|
+
### Matching keys
|
|
64
|
+
|
|
65
|
+
- VIP identity → `Users.c_hrEmpUsername` (matched against the file's `USERNAME` column).
|
|
66
|
+
- Support technician → `Users.email` (matched against the file's `Support Tech Email` column).
|
|
67
|
+
|
|
68
|
+
### Skip reasons
|
|
69
|
+
|
|
70
|
+
Each `skippedRows[]` entry carries the **real spreadsheet row number** (`$row->getRowIndex()`,
|
|
71
|
+
so it lines up with what you see in Excel — not an internal counter offset by the banner/header)
|
|
72
|
+
plus a reason: VIP not in the DB · VIP inactive · VIP has no username in the file (with an
|
|
73
|
+
`(and no support tech email)` note when applicable) · row has no VIP identity to match ·
|
|
74
|
+
support tech email not in the DB · support tech inactive.
|
|
75
|
+
|
|
76
|
+
### Remote input
|
|
77
|
+
|
|
78
|
+
`isRemoteUrl()` detects an `http(s)` URL; `downloadToTempFile()` fetches it via cURL
|
|
79
|
+
(`DOWNLOAD_TIMEOUT_SECONDS = 60`, `FAILONERROR`, follow redirects) to a temp file **kept with
|
|
80
|
+
an `.xlsx` suffix** so PhpSpreadsheet's `IOFactory` resolves the reader by extension. The temp
|
|
81
|
+
file is **always removed in a `finally` block**. A local input file is unlinked after a
|
|
82
|
+
successful run (unless `TEST_MODE`).
|
|
83
|
+
|
|
84
|
+
## Client variations
|
|
85
|
+
|
|
86
|
+
Compass-only. Some spreadsheet rows reference users that belong to a **different Compass tenant
|
|
87
|
+
DB** and are inactive in `Client_Compass` — those rows are skipped with the "not active"
|
|
88
|
+
reason, which is expected, not an error.
|
|
89
|
+
|
|
90
|
+
## Gotchas / known issues
|
|
91
|
+
|
|
92
|
+
- **PhpSpreadsheet `RowIterator` rewinds on `foreach`.** `RowIterator::rewind()` resets to
|
|
93
|
+
`startRow`, and `foreach` always rewinds. An earlier version advanced past the header with
|
|
94
|
+
manual `next()`/`current()`, but the subsequent `foreach` rewound the iterator, so the header
|
|
95
|
+
row was silently reprocessed as a (skipped) data row. Fix: use explicit ranges —
|
|
96
|
+
`getRowIterator($headerRowNum, $headerRowNum)` for the header and
|
|
97
|
+
`getRowIterator($headerRowNum + 1)` for data.
|
|
98
|
+
- **The file format changes.** The current Compass standard is **12 columns** with a banner row
|
|
99
|
+
above the header. The previous format was 14 columns (header on row 1) with `Support Tech
|
|
100
|
+
First`/`Support Tech Last` columns; removing those moved `Support Tech Email` from column N
|
|
101
|
+
(index 13) to column L (index 11). If Compass changes the layout again, adjust
|
|
102
|
+
`EXPECTED_COL_COUNT`, `EXPECTED_HEADERS`, `IDX_SUPPORT_EMAIL`, and the `'A'..'L'` cell
|
|
103
|
+
iterators together.
|
|
104
|
+
- **Why a URL is preferred over a `/tmp` path in production.** The prod EB worker tier runs
|
|
105
|
+
**more than one instance** and SQS distributes jobs across them, so a file placed in `/tmp`
|
|
106
|
+
on one instance is invisible to the instance that actually runs the job — a local path fails
|
|
107
|
+
intermittently with "file not found". A URL fetched at runtime works regardless of which
|
|
108
|
+
instance runs the job. (Running-app path is `/var/app/current`, even though an SSH session on
|
|
109
|
+
the AL2023 box shows `/var/www/html`.)
|
|
110
|
+
- **Cross-region / public S3 reads.** `_Cloud::getFileFromS3` is single-region and cannot read
|
|
111
|
+
a bucket in another region — this importer downloads the object over plain HTTPS instead. See
|
|
112
|
+
[_Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md).
|
|
113
|
+
|
|
114
|
+
## Change history
|
|
115
|
+
- 2026-07-15 — Adapted to Compass's new 12-column format (banner row above the header;
|
|
116
|
+
`Support Tech First`/`Support Tech Last` removed, so `Support Tech Email` moved col N→L);
|
|
117
|
+
added `findHeaderRowNumber()` scanning for `PERS. NO.` and switched to explicit
|
|
118
|
+
`getRowIterator` ranges to fix the RowIterator-rewind bug that silently reprocessed the
|
|
119
|
+
header as a data row. (bala)
|
|
120
|
+
- 2026-07-15 — `ProcessVipFile` now accepts an `http(s)` URL (downloaded via cURL to a temp
|
|
121
|
+
`.xlsx`, always removed in `finally`), because the prod EB worker runs multiple instances and
|
|
122
|
+
a `/tmp` file on one is invisible to the instance that runs the job. (bala)
|
|
123
|
+
- 2026-07-15 — Added structured per-row skip reporting (real spreadsheet row numbers; reasons
|
|
124
|
+
distinguishing inactive vs. not-in-DB), all-users-with-`isActive` load, and stop-at-first-
|
|
125
|
+
empty-row; `ProcessVipFile` returns JSON and echo logging was removed. (bala)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -18,7 +18,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
18
18
|
## 2.0 framework
|
|
19
19
|
|
|
20
20
|
- **_underscore** (_Underscore) _(framework core)_ — 32 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
|
-
- **worker2** (Worker) —
|
|
21
|
+
- **worker2** (Worker) — 30 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)
|
|
24
24
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -15,13 +15,14 @@ project: _Underscore
|
|
|
15
15
|
client: compass-usa
|
|
16
16
|
type: profile
|
|
17
17
|
status: active
|
|
18
|
-
updated: 2026-07-
|
|
18
|
+
updated: 2026-07-15
|
|
19
19
|
owners: [jcardinal, bala, tcox, apeterson]
|
|
20
20
|
files: []
|
|
21
21
|
related:
|
|
22
22
|
- features/asn-to-item-fulfillment.md
|
|
23
23
|
- features/cost-centers.md
|
|
24
24
|
- workflows/cross-kit-bundle-corruption.md
|
|
25
|
+
- ../../2.0/apps/worker2/features/compass-vip-support-importer.md
|
|
25
26
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
26
27
|
- ../../2.0/apps/toga2-commerce/features/cart-bundle-submission-and-identity.md
|
|
27
28
|
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
@@ -71,6 +72,9 @@ separate, related client (see its own profile).
|
|
|
71
72
|
- [Cost Centers (Unit Locations, numeric-only)](features/cost-centers.md) — what a Compass
|
|
72
73
|
"cost center" actually is (a Unit Location keyed by `c_erpSystemEntityId`), and the
|
|
73
74
|
numeric-only selection/import/data policy (2026-07-06).
|
|
75
|
+
- [VIP Support Importer (worker2)](../../2.0/apps/worker2/features/compass-vip-support-importer.md)
|
|
76
|
+
— manual (Postman) worker2 action that reads Compass's quarterly VIP spreadsheet and sets
|
|
77
|
+
`Users.c_supportedByUserId` (assigned support tech) per VIP.
|
|
74
78
|
|
|
75
79
|
## Notes
|
|
76
80
|
- **Order status is shipped-only (2026-06-30).** Compass imports all IF stages
|
package/package.json
CHANGED