toga-ai 1.0.927 → 1.0.929
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/library/features/netsuite-suiteql-api-reference.md +25 -2
- package/knowledge/1.0/apps/tools/INDEX.md +1 -0
- package/knowledge/1.0/apps/tools/features/force-logout.md +46 -0
- package/knowledge/1.0/apps/tools/features/worker2-job-bridge.md +13 -2
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +28 -1
- package/knowledge/2.0/apps/dbchanges2/architecture.md +2 -2
- package/knowledge/2.0/apps/toga2-commerce/features/force-logout-on-deployment.md +5 -0
- package/knowledge/2.0/apps/toga25-supply/features/force-logout-on-deployment.md +5 -1
- package/knowledge/2.0/apps/worker2/INDEX.md +3 -3
- package/knowledge/2.0/apps/worker2/features/clickup-design-sprint-automation.md +9 -4
- package/knowledge/2.0/apps/worker2/features/clickup-general-automation.md +47 -28
- package/knowledge/2.0/apps/worker2/features/clickup-work-type-automation.md +32 -8
- package/knowledge/2.0/apps/worker2/features/committed-secrets-and-config-debt.md +14 -3
- package/knowledge/2.0/apps/worker2/features/netsuite-opportunity-sync.md +7 -1
- package/knowledge/2.0/apps/worker2/features/qa-qc-review-pipeline.md +52 -16
- package/knowledge/2.0/apps/worker2/features/team-sprint-management.md +92 -69
- package/knowledge/2.0/apps/worker2/workflows/sprint-clickup-folder-repoint.md +26 -15
- package/knowledge/INDEX.md +2 -2
- package/knowledge/clients/nychh/INDEX.md +2 -2
- package/knowledge/clients/nychh/features/transfer-order-notification-emails.md +80 -1
- package/knowledge/clients/nychh/profile.md +2 -2
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean", "rgirish"]
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean", "rgirish", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
13
13
|
- library/ssl/netsuite_ec_key.pem
|
|
@@ -106,6 +106,7 @@ Type codes (`transaction.type`):
|
|
|
106
106
|
|
|
107
107
|
Status codes (`transaction.status` letter):
|
|
108
108
|
**Sales Orders:** `A` Pending Approval · `B` Pending Fulfillment · `C` Cancelled · `D` Partially Fulfilled · `E` Pending Billing/Partially Fulfilled · `F` Pending Billing · `G` Billed · `H` Closed. **"Open" sales orders = `B`, `D`, `E`, `F`.** Note `listSalesOrders()` returns **all** statuses for a window (no server-side status filter) — callers must filter.
|
|
109
|
+
**Item Fulfillments (`ItemShip`):** `A` Picked · `B` Packed · `C` Shipped.
|
|
109
110
|
**Invoices:** `A` Open · `B` Paid In Full. **Credit Memos:** `A` Open · `B` Fully Applied. **Cash Sales:** `A` Unapproved Payment · `C` Deposited. **Cash Refunds:** `Y` (the only status; empty string in SOAP).
|
|
110
111
|
|
|
111
112
|
Header columns present (✓ confirmed in SuiteQL): `id`, `tranid`, `trandate`, `lastmodifieddate`, `type`, `status`, `entity`, `employee`, `leadsource`, `terms`, `memo`, `otherrefnum`, `foreigntotal`/`total` (both return the same value), `foreignamountunpaid` (AR balance — invoices only; NULL for SO/credit memo/cash sale), `duedate`, `shipmethod`, `opportunity`, `shippingaddress` (numeric id → join `transactionShippingAddress`), and custom fields (`CUSTBODY_END_CUSTOMER`, `CUSTBODY_STOCKING_ORDER`, …).
|
|
@@ -114,6 +115,25 @@ Header fields NOT in SuiteQL (✗ — require a per-id REST GET): `shippingCost`
|
|
|
114
115
|
|
|
115
116
|
Tracking numbers in SuiteQL (verified 2026-09-28): `transaction.trackingnumberlist` (on the IF) / `linkedtrackingnumberlist` (on the SO) return **`trackingnumber` record ids**, not the numbers — resolve with `SELECT id, trackingnumber FROM trackingnumber WHERE id IN (...)`. `trackingnumbermap` returned nothing. The REST per-id GET below also has them.
|
|
116
117
|
|
|
118
|
+
### Order → the transactions created FROM it: `nexttransactionlink`
|
|
119
|
+
|
|
120
|
+
**`createdfrom` is a `transactionline` column — SuiteQL rejects it on `transaction`.** There is no
|
|
121
|
+
header-level createdfrom to filter on. To find an order's fulfillments (or any child document), join
|
|
122
|
+
`nexttransactionlink`:
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
SELECT ntl.previousdoc, ntl.nextdoc, t.tranid, t.type, t.status
|
|
126
|
+
FROM nexttransactionlink ntl
|
|
127
|
+
JOIN transaction t ON t.id = ntl.nextdoc
|
|
128
|
+
WHERE ntl.previousdoc IN (<order internal ids>)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pair it with the `ItemShip` status letters above to see how far each fulfillment got. This is how a
|
|
132
|
+
**NYCHH transfer order** is traced: `Client_Nychh.TransferOrders.c_netsuiteInternalSalesOrderId`
|
|
133
|
+
holds a NetSuite **sales order** internal id (a $0 SO *is* the transfer order — see
|
|
134
|
+
[the outbound push](../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md)), so
|
|
135
|
+
`previousdoc` is that id and the `ItemShip` rows that come back are its fulfillments.
|
|
136
|
+
|
|
117
137
|
### `transactionline` table
|
|
118
138
|
|
|
119
139
|
Key columns (confirmed present): `transaction`, `linesequencenumber`, `uniquekey`, `id`, `item`, `itemtype`, `mainline`, `quantity`, `foreignamount`, `rate`, `costestimate`, `class`, `memo`, `quantitybilled`, `quantitycommitted`, `quantitypicked`, `quantityshiprecv`, `createdfrom`, `entity` (line-level customer), `location`, `subsidiary`.
|
|
@@ -271,4 +291,7 @@ SalesOrd per-id GET counts by `lastmodifieddate` window: ~16,600 (90d) / ~61,900
|
|
|
271
291
|
- `../../worker/features/forecast2-netsuite-reconciliation.md` — the audit/trueup tooling that consumes this API.
|
|
272
292
|
|
|
273
293
|
## Change history
|
|
294
|
+
- 2026-10-01 — Added `nexttransactionlink` as the order → child-document join (`createdfrom` is a
|
|
295
|
+
`transactionline` column and is rejected on `transaction`), and the `ItemShip` status letters
|
|
296
|
+
(`A` picked / `B` packed / `C` shipped). Found tracing NYCHH transfer orders. (bala)
|
|
274
297
|
- 2026-09-30 — Gotcha: `inventorybalance.quantityavailable` ignores SO commitments; use `aggregateitemlocation`. (rgirish)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
| [Tools — Developers Folder (UUID & Password Generators)](features/developer-tools.md) | Tools "Developers" folder — the Generate UUID and Generate Password tools (reference example of the one-tool-per-`mvc/<route>/get.php` pattern); open for the ge |
|
|
13
13
|
| [/emails Email Queue Review (Tools → 2.0 Logs_<Client>.Email)](features/email-queue-review.md) | Staff page to browse any 2.0 client's `Logs_<Client>.Email` queue and release/reject `HELD` review-hold emails. |
|
|
14
14
|
| [/errors Curation Console (Tools → shared Core Logs DB)](features/errors-curation-console.md) | Internal-only 1.0 Tools triage/curation screen (`/errors`) for the 2.0 Issue/Event error pipeline, reading the shared Core Logs DB via `db_toga2logs`; curates, |
|
|
15
|
+
| [Force Logout (tools /developers/force-logout → worker2 sets META_LAST_REFRESH_DATETIME)](features/force-logout.md) | Tools page that signs every 2.0 front-end user out after a big deploy by changing Core parameter `META_LAST_REFRESH_DATETIME` through a worker2 job; open to tri |
|
|
15
16
|
| [GitHub Audit (monthly code-churn report)](features/github-audit.md) | Internal monthly code-churn report at `/developers/github-audit` (lines added/removed across every `agilantsolutions` repo for one month); open for the two-phas |
|
|
16
17
|
| [Legacy Email Notifier (tools /email-migration/notify)](features/legacy-email-notifier.md) | SSO-gated admin tool at `/email-migration/notify` that emails employees a digest of mail still hitting their legacy domains (asks senders to switch to `@togatec |
|
|
17
18
|
| [Tools — Multi-Client SQL (per-client-DB SQL fan-out)](features/multi-client-sql.md) | Tools `/developers/multi-client-sql` — fans one SQL change-set across a live list of 2.0 client/log/archive/module DBs into a copy/download script (never execut |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Force Logout (tools /developers/force-logout → worker2 sets META_LAST_REFRESH_DATETIME)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: tools
|
|
5
|
+
project: Tools
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- tools/_/app/developers/forcelogout.php
|
|
13
|
+
- tools/mvc/developers/force-logout/get.php
|
|
14
|
+
- tools/mvc/developers/force-logout/post.php
|
|
15
|
+
- tools/_/app/nav.php
|
|
16
|
+
- worker2/Worker/Platform/ForceLogout.php
|
|
17
|
+
related:
|
|
18
|
+
- ./worker2-job-bridge.md
|
|
19
|
+
- ./developer-tools.md
|
|
20
|
+
- ../../../2.0/apps/toga2-commerce/features/force-logout-on-deployment.md
|
|
21
|
+
- ../../../2.0/apps/toga25-supply/features/force-logout-on-deployment.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Tools page that signs every 2.0 front-end user out after a big deploy by changing Core parameter `META_LAST_REFRESH_DATETIME` through a worker2 job; open to trigger a force logout, or before touching `Core.Parameters` from code.
|
|
25
|
+
|
|
26
|
+
## Summary
|
|
27
|
+
|
|
28
|
+
Page `/developers/force-logout` (nav: Developers → Force Logout, persona **Development Team** only). Dev picks a date/time, presses Update. Replaces the old hand-written INSERT/UPDATE on `Core.Parameters` in prod. toga2-commerce and toga25-supply poll that key and log the user out on **any** change; the value itself does not matter (see the two FE docs in `related`).
|
|
29
|
+
|
|
30
|
+
## How it works
|
|
31
|
+
|
|
32
|
+
1. `post.php` actions: `load` (current value), `queue-update`, `job-status`.
|
|
33
|
+
2. `App_Developers_ForceLogout::queueLogoutTimeUpdate()` validates strict `Y-m-d H:i:s` and **refuses a value equal to the stored one** (no change = no logout). Logs via `error_log` with the user uuid (audit).
|
|
34
|
+
3. Queues `Platform/ForceLogout/SetLogoutTime` with `App_Worker::enqueue` (see [worker2-job-bridge](./worker2-job-bridge.md)).
|
|
35
|
+
4. Page polls `job-status` every 2s: reads `WorkerJobs.isSuccess/dtStarted/output` scoped to this action only; shows only the first line of a failure, file paths stripped.
|
|
36
|
+
5. worker2 `_Worker_Platform_ForceLogout::SetLogoutTime(value, requestedByUserUuid = '')` loads `_Model_Core_Parameter` by the hardcoded key, sets `value`, `save()`. Turns the query cache off first, then calls `_Database::transactionCommit(database: _underscore::DB_CORE)` explicitly.
|
|
37
|
+
|
|
38
|
+
## Gotchas
|
|
39
|
+
|
|
40
|
+
- **⚠ Never take the parameter key from input.** `Core.Parameters` also holds api2 secrets (`API_SECRET_ACCESS_TOKEN` etc). Key is a constant in both tools and worker2.
|
|
41
|
+
- **`createFromFormat` alone accepts `2026-02-31`** (rolls over). Validate with a round trip: `format()` of the parsed date must equal the input.
|
|
42
|
+
- **Works on prod tools only today.** Only `config.production.ini` has both `[worker]` (queue_url/queue_region) and `[database_toga2core]`. alpha has Core but no `[worker]`; no beta tools config has a worker queue.
|
|
43
|
+
- **Deploy worker2 before tools**, or the first job fails with an unknown class.
|
|
44
|
+
|
|
45
|
+
## Change history
|
|
46
|
+
- 2026-10-01 — Built the page + worker2 action, replacing hand-run SQL on Core.Parameters. TRUE-82414. (bala)
|
|
@@ -6,11 +6,12 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [mhammontree]
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: [mhammontree, bala]
|
|
11
11
|
files:
|
|
12
12
|
- tools/_/app/worker.php
|
|
13
13
|
related:
|
|
14
|
+
- ./force-logout.md
|
|
14
15
|
- ../../../clients/compass-usa/features/people-file-import-admin-page.md
|
|
15
16
|
- ./mvc-data-access-patterns.md
|
|
16
17
|
- ../../../2.0/apps/worker2/architecture.md
|
|
@@ -63,6 +64,16 @@ empty result — because the wrapper format is worker2's, not a contract.
|
|
|
63
64
|
`maxExecutionTime`. Never make a page wait for long work — poll, don't block.
|
|
64
65
|
- **Deploy worker2 first.** A Tools page that calls a new action is broken until the action
|
|
65
66
|
exists.
|
|
67
|
+
- **Most tools configs cannot enqueue.** Only `config.production.ini` has a `[worker]` section
|
|
68
|
+
(queue_url/queue_region); alpha and beta tools have none.
|
|
69
|
+
|
|
70
|
+
## Local testing (tools → worker2)
|
|
71
|
+
|
|
72
|
+
1. Tools picks `config.<ENVIRONMENT>.ini` from the `ENVIRONMENT` env var; `config.dev-local.ini` is git-ignored and needs `[worker]` + the DB sections.
|
|
73
|
+
2. Tools `composer install` fails on local PHP 8.1 (`symfony/filesystem` needs 8.2). Workaround: a git-ignored `tools/vendor/autoload.php` that requires worker2's vendor autoload.
|
|
74
|
+
3. The AWS SDK ignores the host in `QueueUrl`. Set `AWS_ENDPOINT_URL_SQS` to a local fake SQS that POSTs the message body to `http://worker2/` (Laragon vhost).
|
|
75
|
+
4. Dev-bypass login must use a `Client_True` email (e.g. `@togatech.com`, not `@goagilant.com`).
|
|
66
76
|
|
|
67
77
|
## Change history
|
|
78
|
+
- 2026-10-01 — Added the local tools→worker2 test setup and the prod-only `[worker]` config fact (TRUE-82414). (bala)
|
|
68
79
|
- 2026-09-23 — Created from TRUE-82250: documented the three `enqueue()` outcomes, the log-line-wrapped `output`, and the CTO decision that tools never speaks SFTP (phpseclib 1.x sha1-only KEX + credential duplication). (mhammontree)
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-01
|
|
10
10
|
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -106,6 +106,10 @@ The value is `<seconds>-<STATE>`. Three readings, and only the first is a real f
|
|
|
106
106
|
|
|
107
107
|
- **`1-RUNNING`** (window collapsed ÷3 to 1s) = throwing on **every** run = real freeze. Go read the
|
|
108
108
|
error (`Logs.Event` by clientId → file:line → `Logs_<Client>.Api` on the transactionId).
|
|
109
|
+
**⚠ Nothing alerts on the way down.** `common_sync_togasupply.php:675-681` shrinks the window
|
|
110
|
+
÷3 and the paired *"Interval is already at minimum but previous execution still failed to
|
|
111
|
+
finish"* throw is **commented out** (“so the script can continue”), so a phase degrades
|
|
112
|
+
864000 → … → **1 second** silently and never climbs back. Only the `Parameters` value shows it.
|
|
109
113
|
- **A large-window `*-RUNNING`** (e.g. `864000-RUNNING`, `288000-RUNNING`) = **usually** healthy
|
|
110
114
|
mid-run or a deliberate pause/backfill. **⚠ CORRECTED 2026-09-18 — this is NOT sufficient on its
|
|
111
115
|
own, and the old "NOT stuck, do not fix it" wording caused a real miss.** NYCHH showed
|
|
@@ -577,6 +581,23 @@ purchase orders, invoices and item receipts were disabled again on 2026-09-03**
|
|
|
577
581
|
is exactly why every NYCHH cursor stops at **2026-09-03**. Before concluding "the sync is broken",
|
|
578
582
|
check the launcher's toggles at the relevant commit.
|
|
579
583
|
|
|
584
|
+
### ⚠ NYCHH syncs ONCE A DAY — and its live state is in the CLIENT db, not Core
|
|
585
|
+
|
|
586
|
+
Two traps that cost a diagnosis, both NYCHH-specific:
|
|
587
|
+
|
|
588
|
+
- **The `*/5` schedule is not the real cadence.** `cron.worker.sync.json` registers
|
|
589
|
+
`sync_togasupply_hh.php` every 5 minutes like everyone else, but the wrapper calls
|
|
590
|
+
**`App_Framework::cronInitialization(86400)`** (“THIS CRON JOB IS INTENDED TO BE RUN EVERY DAY”),
|
|
591
|
+
so 287 of every 288 invocations exit immediately. Anything NetSuite ships after the morning run
|
|
592
|
+
waits until the next day. Account for that before calling a late import “broken” — and when
|
|
593
|
+
rewinding a cursor, run the wrapper by hand rather than waiting (the 10-day ceiling above means a
|
|
594
|
+
deep rewind would otherwise take weeks).
|
|
595
|
+
- **Read `Client_Nychh.Parameters`, never the `NETSUITE_HH_*` keys in `Core`.** Core still carries
|
|
596
|
+
legacy `NETSUITE_HH_*` rows whose last-sync dates sit in **May 2026**; they are **stale leftovers
|
|
597
|
+
that nothing writes**. The live cursors and execution modes are
|
|
598
|
+
`NETSUITE_LAST_SYNC_CURSOR_*` / `NETSUITE_EXECUTION_MODE_*` in the client DB (see *Data model*).
|
|
599
|
+
Reading the Core copies leads straight to a wrong diagnosis.
|
|
600
|
+
|
|
580
601
|
### Replaying one order means rewinding the cursor by hand
|
|
581
602
|
|
|
582
603
|
The cursor is a **seek** position (`<lastmodifieddate>|<internalId>`, see above), so an order whose
|
|
@@ -2044,6 +2065,12 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
2044
2065
|
[NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
|
|
2045
2066
|
|
|
2046
2067
|
## Change history
|
|
2068
|
+
- 2026-10-01 — NYCHH diagnosis traps recorded: the wrapper is gated to **once a day** by
|
|
2069
|
+
`cronInitialization(86400)` despite its `*/5` schedule entry, and the live cursors/modes live in
|
|
2070
|
+
`Client_Nychh.Parameters` — the `NETSUITE_HH_*` keys in `Core` are stale (May 2026) leftovers.
|
|
2071
|
+
Also noted that the “interval already at minimum” throw is **commented out**
|
|
2072
|
+
(`common_sync_togasupply.php:675-681`), so a phase can decay to a 1-second window with no alert.
|
|
2073
|
+
(bala)
|
|
2047
2074
|
- 2026-09-30 — Closed-order rule v3: drop only when every line is closed and nothing was created from it ([doc](../../library/features/netsuite-closed-order-rule.md)). Sync never auto-creates warehouse Locations (`throwIfUnknownWarehouse`); unit location from the line, not `custitemnumber_location`. Added `process_netsuite_records.php` and the stuck-sync diagnosis path. (rgirish)
|
|
2048
2075
|
- 2026-09-24 — TO↔PO links: two-direction reconcile (vendor POs no longer upstream; retireStale moves downstream SO links to 326/330); fixed SO downstream line-link deletes on PO re-sync; new `process_netsuite_order.php` + `NETSUITE_SYNC_LOOKUPS_ONLY`. (jcardinal)
|
|
2049
2076
|
- 2026-09-22 — **The `Logs.Issue` 589 doom-loop's framework root cause is now fully explained and has
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-01
|
|
10
10
|
owners: [jcardinal, mhammontree, bala, ajean]
|
|
11
11
|
files:
|
|
12
12
|
- Core/
|
|
@@ -238,7 +238,7 @@ This lifecycle is automated by a local browser tool — see the [TOGa 2.0 Client
|
|
|
238
238
|
|
|
239
239
|
**Every UUID must be freshly, fully random — never derive one from another.** Do **not** take a known uuid and bump a digit (`…-0001`, `…-0002`, …), copy-and-tweak, or otherwise pattern them. When a file needs several literals, generate each one independently at random. (Patterned uuids are guessable and defeat the uniqueness guarantee.) The one intended exception is a fan-out `Client/` grant, which **reuses the same** literal per row across tenants on purpose for safe rollback — deliberate reuse, not incrementing (see the feature doc).
|
|
240
240
|
|
|
241
|
-
One earlier migration (`Core/2026-05-08 … CronJobs_WorkerCleanup_insert`) used `UUID()` — that is a known violation, not a precedent to copy. First applied correctly in `Core/2026-
|
|
241
|
+
One earlier migration (`Core/2026-05-08 … CronJobs_WorkerCleanup_insert`) used `UUID()` — that is a known violation, not a precedent to copy. First applied correctly in `Core/2026-09-29a - Record sprint and design CronJobs.sql` (new rows 83–86 carry pre-generated v4 literals; the recorded prod rows keep their existing prod uuids, which predate this rule).
|
|
242
242
|
7. **Guard every registration/seed insert with `NOT EXISTS` (or `INSERT IGNORE`).** Rows that register platform behavior — `ApiPayloadInterceptors`, `Core.RecordFields`/`CustomRecordFields`, cron jobs, ACL rows — must be inserted only if not already present. An **unguarded** registration is not merely non-idempotent: on a fresh environment it can run **alongside a second migration that registers the same behavior**, producing a **double registration** (e.g. two interceptor rows for the same record → the interceptor fires twice). A real instance: the Rate WH `prePost` interceptor is registered unguarded in `Client_Rate/2026-07-15 - PreInterceptor.sql` (record 191, `minDepth NULL`); a second guarded registration would have double-fired it. See `clients/rate/features/whole-home-warranty-purchase-guard.md`.
|
|
243
243
|
8. **Never reference a database other than the folder's own target.** No `Core.Table` in a `Client_<Name>/` file, no `Client_X.Table` in a `Core/` file, and **no database qualifier at all** in the fan-out folders (`Client/`, `Logs_Client/`, `_modules/`). These databases are on **separate production clusters**; the query works locally and dies in production. Resolve foreign ids with a hardcoded v4 UUID literal or an in-database slug/natural key, or split the work into one file per database folder. See *Database isolation* above — enforced by the `dbchanges2-cluster-isolation` hook.
|
|
244
244
|
9. **Inserting into `Core.Records` or `Core.RecordFields`? ASK the developer for the next `id`** (the developer takes it in Tools → Developers → Central IDs — the source of truth; never guess from MAX(id), branches hold unreleased ids). These are the **only two tables on the platform with team-maintained primary keys**. Write the assigned `id` as an explicit literal — **never** let `AUTO_INCREMENT` assign it. Conversely, **always hardcode** these `id`s where they are referenced, including in foreign keys (`recordId`, `recordFieldId`) from other databases. See *`Core.Records` / `Core.RecordFields`* above.
|
|
@@ -8,6 +8,7 @@ type: feature
|
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-10-01
|
|
10
10
|
owners: [apeterson]
|
|
11
|
+
owners: [apeterson, bala]
|
|
11
12
|
files:
|
|
12
13
|
- toga2-commerce/src/hooks/useDeploymentGuard.tsx
|
|
13
14
|
- toga2-commerce/src/lib/deploymentVersion.ts
|
|
@@ -15,6 +16,7 @@ files:
|
|
|
15
16
|
- toga2-commerce/src/components/AuthLayout/AuthLayout.tsx
|
|
16
17
|
- toga2-commerce/src/lib/authStorage.ts
|
|
17
18
|
related:
|
|
19
|
+
- ../../../../1.0/apps/tools/features/force-logout.md
|
|
18
20
|
- ./sso-redirect-and-session-gating.md
|
|
19
21
|
- ./catalog-cache-freshness.md
|
|
20
22
|
- ../../toga25-supply/features/force-logout-on-deployment.md
|
|
@@ -32,6 +34,8 @@ Ported from toga25-supply (`force-logout-on-deployment`). Two commerce-only diff
|
|
|
32
34
|
|
|
33
35
|
Reached commerce `_production` only in merge `26e2fae7` (2026-09-30, TRUE-81991); supply had it earlier. That gap is why one parameter bump logged supply users out and commerce users not at all.
|
|
34
36
|
|
|
37
|
+
**Trigger:** change the value with tools `/developers/force-logout` ([force-logout](../../../../1.0/apps/tools/features/force-logout.md)), not hand SQL.
|
|
38
|
+
|
|
35
39
|
## How it works
|
|
36
40
|
|
|
37
41
|
1. One query on `META_LAST_REFRESH_DATETIME`, `enabled` only while authenticated, `staleTime: 0`, `refetchOnWindowFocus: true`, `refetchInterval` **5 minutes**.
|
|
@@ -76,4 +80,5 @@ To also fire on an in-app navigation: a `useLocation()` effect calling `refetch(
|
|
|
76
80
|
|
|
77
81
|
## Change history
|
|
78
82
|
- 2026-10-01 — Documented the exact refetch triggers (mount / tab-visibility / 5-min poll; **not** route change), the background-pause behavior, the first-bump no-op, the ignored `key=` param, and the commerce-vs-supply query config difference. Route-change refetch deferred. (apeterson)
|
|
83
|
+
- 2026-10-01 — Trigger is now the tools Force Logout page (TRUE-82414). (bala)
|
|
79
84
|
- 2026-09-25 — Built for commerce: moved out of `AuthContext` into `<DeploymentGuard />` so a toaster can be shown; added 5-min poll + focus refetch; baseline key added to `AUTH_STORAGE_KEYS`. TRUE-81991. (apeterson)
|
|
@@ -7,12 +7,13 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-10-01
|
|
10
|
-
owners: [apeterson]
|
|
10
|
+
owners: [apeterson, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/hooks/useDeploymentGuard.tsx
|
|
13
13
|
- toga25-supply/src/App.tsx
|
|
14
14
|
related:
|
|
15
15
|
- ../../toga2-commerce/features/force-logout-on-deployment.md
|
|
16
|
+
- ../../../../1.0/apps/tools/features/force-logout.md
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
Force-signs-out every user when a deploy bumps a Core parameter, so session + caches reset to the new build; open when debugging surprise logouts or `useDeploymentGuard`.
|
|
@@ -21,6 +22,8 @@ Force-signs-out every user when a deploy bumps a Core parameter, so session + ca
|
|
|
21
22
|
|
|
22
23
|
On large deploys the backend bumps Core parameter `META_LAST_REFRESH_DATETIME`. The FE watches it and, when it changes mid-session, force-signs-out every logged-in user so session, `localStorage`, and React Query cache (including the persisted `supply-chain-query-cache`) reset cleanly. Implemented as a headless `<DeploymentGuard />` driven by `useDeploymentGuard`, mounted inside the `AuthProvider` / `ToasterProvider` tree in `App.tsx` — **above** `<AppRouter />`, so the hook cannot use `useLocation()`.
|
|
23
24
|
|
|
25
|
+
**Trigger:** change the value with tools `/developers/force-logout` ([force-logout](../../../../1.0/apps/tools/features/force-logout.md)), not hand SQL.
|
|
26
|
+
|
|
24
27
|
## How it works
|
|
25
28
|
|
|
26
29
|
1. **Poll the Core parameter.** TanStack `useQuery` reads `META_LAST_REFRESH_DATETIME` via `apiGet("/core-parameters", { fields: ["value"], key: "META_LAST_REFRESH_DATETIME" })`, reading `response.data.coreParameters` (tolerant of an array or a single object). Query: `enabled` only while authenticated, `staleTime: 0`, `refetchOnWindowFocus: true`, `refetchInterval` 5 minutes.
|
|
@@ -32,5 +35,6 @@ On large deploys the backend bumps Core parameter `META_LAST_REFRESH_DATETIME`.
|
|
|
32
35
|
**When it actually fires, the first-bump no-op, and the ignored `key=` param are identical here** — documented once in [commerce's force-logout doc](../../toga2-commerce/features/force-logout-on-deployment.md) (sections *What actually triggers a refetch* and *Gotchas*). Supply-only difference: `App.tsx` leaves `refetchOnWindowFocus` at React Query's default `true` app-wide, so every on-screen query refetches on tab visibility — not just this one. Sign-out timing is the same in both apps.
|
|
33
36
|
|
|
34
37
|
## Change history
|
|
38
|
+
- 2026-10-01 — Trigger is now the tools Force Logout page (TRUE-82414). (bala)
|
|
35
39
|
- 2026-10-01 — Recorded that the guard sits above the router (blocks `useLocation()`); moved the shared trigger/gotcha detail to the commerce doc rather than duplicating it. (apeterson)
|
|
36
40
|
- 2026-09-16 — Initial implementation. (apeterson)
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
| [ClickUp Connectivity Watchdog](features/clickup-connectivity-watchdog.md) | Cron watchdog that emails when the ClickUp integration goes silent in business hours; open when touching ClickUp webhook health or `_Worker_Clickup_Health`. |
|
|
12
12
|
| [ClickUp Custom-Field Conventions (constant IDs, runtime option values, Task Type migration)](features/clickup-custom-field-conventions.md) | How worker2 code must reference ClickUp custom fields; open before reading/writing any ClickUp custom field or touching the Task Type migration. |
|
|
13
13
|
| [ClickUp Design Sprint Automation (Final Design Outcome)](features/clickup-design-sprint-automation.md) | `_Worker_Clickup_Design` drives the design-sprint workflow in ClickUp (FDO default on create, spawn iteration / complete task on Final Design Outcome, daily rev |
|
|
14
|
-
| [ClickUp General Automation (Sub-Environment reservation, Has UI / Has Code
|
|
14
|
+
| [ClickUp General Automation (Sub-Environment reservation, Has UI / Has Code, review-field reset)](features/clickup-general-automation.md) | `_Worker_Clickup_GeneralAutomation` keeps QA/QC fields consistent on ClickUp field/status changes (sub-environment reservation, Has UI / Has Code, review-field |
|
|
15
15
|
| [ClickUp GitHub-tab Auto-linking & Ticket-id Branch Naming](features/clickup-github-autolink.md) | How ClickUp surfaces branches/PRs/commits in a ticket's GitHub tab and the branch/PR-title naming that triggers it; open when naming branches for ticket work or |
|
|
16
16
|
| [ClickUp Project & Opportunity Multi-List Routing](features/clickup-project-routing.md) | Routes ClickUp tasks into secondary multi-list memberships by custom-field values; open for opportunity/epic routing, the stage-is-a-list constraint, or webhook |
|
|
17
17
|
| [ClickUp Rich-Text Custom Fields via Quill Delta (API)](features/clickup-richtext-api.md) | How to write formatted text into a ClickUp text/long-text custom field via the undocumented `value_richtext` Quill Delta key; open when a richtext ClickUp field |
|
|
18
18
|
| [ClickUp Subtask Activity → Parent Opportunity/Epic Comments](features/clickup-subtask-activity.md) | Mirrors subtask status/comment activity up to its top-level Opportunity/Epic as one comment; open for the subtask fan-out, its dedup ledger, or structural ances |
|
|
19
19
|
| [ClickUp Task Description Fluffer (Talos KB Investigation)](features/clickup-task-fluffer-talos.md) | Async worker that "fluffs" a new ClickUp task by running its name+description through the Talos `dev-core` KB agent and writing the report back; open for the fl |
|
|
20
20
|
| [ClickUp Webhook Health Monitor → OneUptime (Monitor/Clickup/CheckWebhook)](features/clickup-webhook-health-monitor.md) | worker2 cron that checks the team's ClickUp webhook subscriptions and pages via a OneUptime Incoming-Request monitor; open when tuning the ClickUp webhook alarm |
|
|
21
|
-
| [ClickUp Work Type Automation (Committed / Conditional / Stretch)](features/clickup-work-type-automation.md) | Auto-maintains each ClickUp task's Work Type (Committed/Conditional/Stretch) from its dependencies, status, and due date, and sets
|
|
21
|
+
| [ClickUp Work Type Automation (Committed / Conditional / Stretch)](features/clickup-work-type-automation.md) | Auto-maintains each ClickUp task's Work Type (Committed/Conditional/Stretch) from its dependencies, status, and due date, and sets sprint-list defaults (Work Ty |
|
|
22
22
|
| [worker2 Codeception integration tests (2.0 test-framework pilot)](features/codeception-integration-tests.md) | First 2.0 app-level automated test suite: Codeception 5 / PHPUnit 11 in worker2 (pilot, Jeff's direction), real-DB integration tests with hard local-only safety |
|
|
23
23
|
| [worker2 — Committed Secrets & Config Exposure (debt register)](features/committed-secrets-and-config-debt.md) | Standing list of credentials committed to the worker2 repo (locations only); open before adding a credential to worker2 or assuming a config value is private. |
|
|
24
24
|
| [Compass VIP Support Importer (worker2)](features/compass-vip-support-importer.md) | worker2 action that ingests Compass's quarterly VIP spreadsheet and sets each VIP's support tech (`Users.c_supportedByUserId` in `Client_Compass`); open when wo |
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
| [Talos Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | Consumer side of Talos: the endpoints, DevCore assistant, and JWT auto-refresh a worker2 dev tool needs to query the meeting-notes corpus; open when a tool must |
|
|
61
61
|
| [Talos Pricing Automation (worker2 Cron — Usage Import, AWS Actuals, Margins, Profiles)](features/talos-pricing-automation.md) | Monthly worker2 crons that turn raw usage + AWS cost into a measured platform unit price, per-client cost, and margins; open for the pricing pipeline, its AWS c |
|
|
62
62
|
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` cron pipeline ingesting Teams VTT transcripts into Talos Bedrock KBs + emailing organizer recaps; open for Export/Process/Retry/SyncK |
|
|
63
|
-
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` engine for TOGA's biweekly dev-sprint process + reporting (warnings, lock, daily dashboard, sprint-end scoring, AI release notes
|
|
63
|
+
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` engine for TOGA's biweekly dev-sprint process + reporting (warnings, lock, daily dashboard, sprint-end scoring, AI release notes, 7 PM In |
|
|
64
64
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | How `Team/Transcripts/Export` polls Microsoft Graph for Teams transcripts; open for the Graph organizer-resolution, GUID-vs-UPN, and Entra-permission details (t |
|
|
65
65
|
| [Centralized Tracking-Status Refresh (worker2 Sync cron, all clients)](features/tracking-status-refresh.md) | Platform-wide worker2 cron polling FedEx/USPS/UPS to keep `TrackingNumbers.status` current for every provisioned client via api2 PUT; open for the dispatcher/cu |
|
|
66
66
|
| [VAPI Webhook Handler (worker2 — AI-BDR end-of-call processing)](features/vapi-webhook-handler.md) | worker2 handler for VAPI's end-of-call report (writes results onto the `ContactAttempt`, dispatches the post-call action) + the outbound dialer eligibility rule |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-01
|
|
10
10
|
owners: ["ajean", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Clickup/Design.php
|
|
@@ -24,7 +24,7 @@ related:
|
|
|
24
24
|
|
|
25
25
|
`_Worker_Clickup_Design` replaces a set of native ClickUp automations. When a task's **Final Design Outcome** (FDO) drop-down is set to an actionable value, the worker spawns an iteration task or completes the task; a daily cron reminds the Design Approver of pending reviews.
|
|
26
26
|
|
|
27
|
-
**Current state (2026-
|
|
27
|
+
**Current state (2026-10-01):** live on Design Sprints `90116585900` (`DESIGN_SPRINTS_FOLDER_IDS`) + Backlog Design lists. The old True Design Sprints folder `90115939842` was **removed** from the gate (old folders get no automation; open tasks there are closed by hand). FDO-on-create, approve→complete and iterate→spawn verified working since Jeff's 9/25 deploy; failures reported after that predated it. History of the 2026-07 no-op / no-spawn fixes (worker2 PR #98, #113 + _underscore PR #669) is in Gotchas. The native ClickUp automations masking the original no-op must stay disabled.
|
|
28
28
|
|
|
29
29
|
## How it works
|
|
30
30
|
|
|
@@ -47,7 +47,7 @@ related:
|
|
|
47
47
|
- `Worker/Clickup.php` — routes ClickUp webhooks.
|
|
48
48
|
- `shouldEmailDesignApprover($payload, $taskDetails)` — the single predicate gating the "Action Needed: Design Review" email (requires history_items[0] is a Design Approver custom_field change, task status still `iterated`, and user-driven: `user.id != -1`).
|
|
49
49
|
- `taskUpdated` path (~822) and `taskStatusUpdated` path (~230) both gate the email on this predicate.
|
|
50
|
-
- `Controller/ClickupDesignTest.php` — regression tests reached via the `?controller=ClickupDesignTest` route (worker2 has **no PHPUnit harness**; mirrors the `CompassPeopleTest` convention). Uses `ReflectionMethod` to reach the private methods.
|
|
50
|
+
- `Controller/ClickupDesignTest.php` — regression tests (updated 2026-10-01: the new-task case now expects `in progress`) reached via the `?controller=ClickupDesignTest` route (worker2 has **no PHPUnit harness**; mirrors the `CompassPeopleTest` convention). Uses `ReflectionMethod` to reach the private methods.
|
|
51
51
|
|
|
52
52
|
### Flow
|
|
53
53
|
|
|
@@ -80,7 +80,7 @@ Configuration lives as ClickUp identifiers (folder IDs, custom-field IDs, drop-d
|
|
|
80
80
|
- **Never use strict `===` when reading a ClickUp `drop_down` value/orderindex/id (ROOT CAUSE of the silent no-op).** ClickUp returns `value`/`orderindex` with **inconsistent int-vs-string typing** (`"2" !== 2`), so `resolveFinalDesignOutcomeOptionId` matched nothing, returned `null`, and `handleFinalDesignOutcome` hit its early return (job still `isSuccess=1`). Fixed by comparing **as strings**: `(string)$option->orderindex === (string)$field->value` (and the same for `$option->id`). **Every other** drop_down reader in `Worker/Clickup.php` relies on PHP **array-key coercion** (`$optionsByIndex[$option->orderindex]` then indexed by `$customField->value`) precisely because of this typing inconsistency — strict `===` anywhere reading a ClickUp drop_down value is a bug. The native automation `c25fd345` had been masking this no-op in prod.
|
|
81
81
|
- **`taskStatusUpdated` switches on `before->status`, NOT `after->status`** (Clickup.php ~71). A `case '<status>'` in that switch fires when a task **LEAVES** that status — the in-code comment claiming "just changed TO iterated" is misleading. This is the trap behind the second spurious-email vector: the `iterated` case fired on `completeTask()`'s `iterated → complete` transition during `spawnIterationTask`. Anyone adding status-transition logic must account for this direction. The "entering iterated" trigger added this session is correctly placed under `switch ($historyItem->after->status)` (`case ...::STATUS_ITERATED` → `handleTaskEnteredIterated()`) — distinct from the `before->status` switch.
|
|
82
82
|
- **Spurious "Design Review" email — fixed on a single predicate.** Both vectors (the `taskUpdated` Design-Approver-field path ~822 and the `taskStatusUpdated` `iterated` path ~230) now gate on `shouldEmailDesignApprover($payload, $taskDetails)`, which requires: `history_items[0]` is a Design Approver custom_field change, task status still `iterated`, and the change is **user-driven** (`user.id != -1`; ClickBot/automation writes carry `user.id == -1`). The dead `$isDesignApproverSet` block was removed. Consequence: a pure status change **into** iterated (approver already set, no field change) no longer emails from the status path — the legitimate review email flows from the `taskUpdated` field handler.
|
|
83
|
-
- **Native automations must stay disabled once the worker is verified.** The worker replaces: `c25fd345`, `272f743e`, `a64c863a`, `7739cc41`, `08570f31`, `70b09962`. Keep sprint-readiness automations only: `10df1786`, `f6214678
|
|
83
|
+
- **Native automations must stay disabled once the worker is verified.** The worker replaces: `c25fd345`, `272f743e`, `a64c863a`, `7739cc41`, `08570f31`, `70b09962`. Keep sprint-readiness automations only: `10df1786`, `f6214678` — these existed only on the old folder and **must be rebuilt by hand on Design Sprints** (open as of 2026-10-01). Re-enabling any of the replaced set reintroduces the duplicate+close and spurious-email behavior.
|
|
84
84
|
- **The spawn gate must admit Backlog Design lists — folder + name, not just the sprint folder (ROOT CAUSE of the 2026-07-15 intermittent no-spawn).** `isDesignSprintTask()` originally accepted only the True Design Sprints folder or two hardcoded `DESIGN_LIST_IDS`, so a design task in a **Backlog Design list** was silently skipped (no iteration spawned). The gate now also admits `BACKLOG_FOLDER_ID = '90116192732'` when the list name matches `DESIGN_LIST_NAME_PATTERN`. **Regex trap:** the pattern is `/(?<!non-)\bdesign\b/i` — a bare `\bdesign\b` wrongly matched **"Non-Design Overflow"** because the hyphen is itself a word boundary, so the negative-lookbehind `(?<!non-)` is required; the `\b` boundary also correctly excludes **"Redesign"**. `SendReviewReminders()` applies the same folder+pattern so new backlog Design lists get reminders without a code change.
|
|
85
85
|
- **Resolve the FDO from the webhook `after` value, NOT the per-process snapshot (read-after-write race — second root cause of the 2026-07-15 failure).** `handleFinalDesignOutcome` used to resolve the outcome from the cached `getTaskDetails` snapshot fetched early in the request, which can **predate** ClickUp reflecting the drop-down write → it read the OLD value → silent skip. The webhook's authoritative new value (`history_items[].after`) is now threaded through `handleFinalDesignOutcome(..., ?string $webhookFdoValue)` → `resolveFinalDesignOutcomeOptionId(..., ?string $overrideValue)` (override wins over the snapshot value; the option *list* is still read from the snapshot). `_Worker_Clickup::extractCustomFieldAfterValue()` normalizes the `after` shape and — critically — distinguishes a **missing** `after` (→ `null` → fall back to snapshot) from an **explicit null** `after` (→ `''` sentinel → field was cleared → resolve to no outcome; do NOT act on the stale previous option). The previously-silent resolver-null return now logs a `[ClickupDesign]` breadcrumb.
|
|
86
86
|
- **`_Component_Api_Clickup::send()` throws on transient failures — it no longer silently returns NULL (shared-core, `_underscore` PR #669).** The old retry loop never actually retried and returned NULL on HTTP `>= 502`, **silently dropping** the API call. Now transient failures — HTTP 502/503/504, 429, and transport-level errors (`responseCode === 0` / cURL failure) — **throw** so the worker job fails loudly and the **queue** retries; other non-2xx still throw. Retry is intentionally left to the queue (no in-worker sleep-and-retry; CTO Option C). **It throws the base `Exception`** — the same type every other `_Component_Api` client throws and the framework error handler / Sentry already track — NOT a bespoke exception class. (A `_Exception_Api_Transient` subclass was prototyped and dropped precisely because a new type sits outside the existing Sentry/error path.) Exception messages never embed the full `$response` body (task/user PII); the private `summarizeApiError()` surfaces only ClickUp `err`/`ECODE` with a bounded/truncated fallback. Affects **every** caller of `send()` across the 2.0 apps — grep `_Component_Api_Clickup::send(` before assuming a caller tolerated the old null-on-failure return.
|
|
@@ -90,3 +90,8 @@ Configuration lives as ClickUp identifiers (folder IDs, custom-field IDs, drop-d
|
|
|
90
90
|
|
|
91
91
|
- [ClickUp Work-Type Automation](./clickup-work-type-automation.md)
|
|
92
92
|
- [team-sprint-management](./team-sprint-management.md)
|
|
93
|
+
|
|
94
|
+
## Change history
|
|
95
|
+
- 2026-10-01 — Old True Design Sprints folder `90115939842` dropped from `DESIGN_SPRINTS_FOLDER_IDS`; natives 10df1786/f6214678 to rebuild on Design Sprints (ajean; worker2 PR #178)
|
|
96
|
+
- 2026-09-21 — Multi-folder gate (commit `6dcebe0`) (jcardinal)
|
|
97
|
+
- 2026-07-15 — Backlog Design lists + webhook `after` value fix (worker2 #113, _underscore #669) (ajean)
|
|
@@ -1,59 +1,78 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: ClickUp General Automation (Sub-Environment reservation, Has UI / Has Code
|
|
2
|
+
title: ClickUp General Automation (Sub-Environment reservation, Has UI / Has Code, review-field reset)
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
repo: worker2
|
|
5
5
|
project: Worker
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
|
-
status:
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: ["jcardinal", "ajean"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Clickup/GeneralAutomation.php
|
|
13
13
|
- worker2/Worker/Clickup.php
|
|
14
|
+
- worker2/Worker/Github.php
|
|
14
15
|
- _underscore/Component/Api/Clickup/Clickup.php
|
|
15
16
|
related:
|
|
16
17
|
- ./qa-qc-review-pipeline.md
|
|
17
18
|
- ./clickup-work-type-automation.md
|
|
18
19
|
- ./clickup-custom-field-conventions.md
|
|
20
|
+
- ./team-sprint-management.md
|
|
19
21
|
---
|
|
20
22
|
|
|
21
|
-
`_Worker_Clickup_GeneralAutomation`
|
|
23
|
+
`_Worker_Clickup_GeneralAutomation` keeps QA/QC fields consistent on ClickUp field/status changes (sub-environment reservation, Has UI / Has Code, review-field reset to Needs Review); open when one of those fields is wrong or you add a QA/QC field rule.
|
|
22
24
|
|
|
23
25
|
## Summary
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
1. **Sub-Environment auto-reservation** — when a Task needs a reserved environment.
|
|
29
|
-
2. **Has UI / Has Code roll-up** to the parent Feature.
|
|
30
|
-
3. **Has Code backfill** on "Development Finished" when a PR exists.
|
|
27
|
+
Action `Clickup/GeneralAutomation/Process`, fanned out from `_Worker_Clickup::Webhook` on
|
|
28
|
+
`taskUpdated` / `taskStatusUpdated`. Implements the "General Automation" section of the team QA/QC
|
|
29
|
+
plan as seven numbered rules (listed in the class docblock). Live in prod; Dev Sprints folder scope.
|
|
31
30
|
|
|
32
31
|
## How it works
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
### The rules (`GeneralAutomation.php`)
|
|
34
|
+
|
|
35
|
+
1. **Client Approval Required** checked → check **Reserve Sub-Environment**.
|
|
36
|
+
2. **Task Type = Feature** → check Reserve Sub-Environment.
|
|
37
|
+
3. Reserve Sub-Environment checked → claim the next free Greek letter from `SubEnvironments` with a
|
|
38
|
+
**guarded `UPDATE` + retry** (never select-then-update) and set **Sub-Environment**.
|
|
39
|
+
4. Reserve Sub-Environment unchecked → clear Sub-Environment, release the row.
|
|
40
|
+
5. A subtask's **Has UI / Has Code Changes** → roll up onto the parent Feature (any child = parent).
|
|
41
|
+
6. Status → **Development Finished**, a PR exists, Has Code Changes unchecked → check it.
|
|
42
|
+
7. Status **enters** `ui review` / `front-end review` / `back-end review` → reset the matching field
|
|
43
|
+
**UI Review** (`c08e3ca2…`) / **Front-end Review** (`e21c888a…`) / **Back-End Review**
|
|
44
|
+
(`227480b6…`) to **Needs Review**. Dev Sprints only, not Feature tasks (`FeatureReview` owns
|
|
45
|
+
those); skipped if already Needs Review. `QaReview` / `FeatureReview` treat Needs Review as a
|
|
46
|
+
no-op, so the reset does not loop.
|
|
47
|
+
|
|
48
|
+
### PR → "Has Code Changes"
|
|
49
|
+
|
|
50
|
+
- `hasPullRequest(string $customTaskId)` (public) — **exact** match on `PullRequests.title`, which
|
|
51
|
+
stores only the task custom id (`_Worker_Github::prTitleToCustomId`). A `LIKE` prefix match was a
|
|
52
|
+
false-positive risk (`TRUE-123` matched `TRUE-1234`). Shared with
|
|
53
|
+
`_Worker_Clickup::handleDevelopmentFinished`, which checks for a PR itself so it does not race rule 6.
|
|
54
|
+
- `Worker/Github.php` `setHasCodeChangesFromPr()` — checks Has Code Changes when a PR row is **first
|
|
55
|
+
recorded** (task resolved via `Team.Tasks` customClickupIdentifier → clickupIdentifier). Best-effort,
|
|
56
|
+
never throws: the GitHub webhook must keep returning 200.
|
|
57
|
+
|
|
58
|
+
Sprint-list defaults set on create / move (Condition, Has UI Changes, Work Type) live in
|
|
59
|
+
[ClickUp work-type automation](./clickup-work-type-automation.md#sprint-list-defaults-taskcreated--taskmoved).
|
|
49
60
|
|
|
50
61
|
## Gotchas
|
|
51
62
|
|
|
52
|
-
- **Reserve the sub-environment with a guarded `UPDATE` + retry, never select-then-update.**
|
|
53
|
-
|
|
63
|
+
- **Reserve the sub-environment with a guarded `UPDATE` + retry, never select-then-update.**
|
|
64
|
+
Concurrent webhooks would hand the same letter to two tasks.
|
|
65
|
+
- **Field IDs are constants, option VALUES are resolved at runtime** — see
|
|
66
|
+
[ClickUp custom-field conventions](./clickup-custom-field-conventions.md).
|
|
67
|
+
- **Review fields always reset on entry (owner decision 2026-10-01).** Do not "keep" an old
|
|
68
|
+
Approved/Denied when a task re-enters a review status.
|
|
54
69
|
|
|
55
70
|
## Related
|
|
56
71
|
|
|
57
72
|
- [QA/QC review pipeline](./qa-qc-review-pipeline.md)
|
|
58
73
|
- [ClickUp Work-Type Automation](./clickup-work-type-automation.md)
|
|
59
74
|
- [ClickUp custom-field conventions](./clickup-custom-field-conventions.md)
|
|
75
|
+
|
|
76
|
+
## Change history
|
|
77
|
+
- 2026-10-01 — Rule 7 review-field reset; Has Code Changes on first PR record; `hasPullRequest()` exact match (ajean; worker2 PR #178)
|
|
78
|
+
- 2026-09-16 — Initial rules 1–6 (jcardinal)
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["jcardinal"]
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: ["jcardinal", "ajean"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Clickup.php
|
|
13
13
|
- worker2/Tests/Worker/ClickupWorkTypeTest.php
|
|
@@ -16,7 +16,7 @@ related:
|
|
|
16
16
|
- ../workflows/clickup-credential-rotation.md
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
Auto-maintains each ClickUp task's Work Type (Committed/Conditional/Stretch) from its dependencies, status, and due date, and sets
|
|
19
|
+
Auto-maintains each ClickUp task's Work Type (Committed/Conditional/Stretch) from its dependencies, status, and due date, and sets sprint-list defaults (Work Type, Condition, Has UI Changes) on create/move; open when work-type promotion/demotion or those default writes misbehave.
|
|
20
20
|
|
|
21
21
|
## Summary
|
|
22
22
|
|
|
@@ -42,12 +42,29 @@ Flow:
|
|
|
42
42
|
- `Stretch` with a due date inside the current sprint → set Committed/Conditional as above.
|
|
43
43
|
6. When a task is depended on by another (`$dependency->depends_on == $taskId`), it recurses into the blocking direction so completing a task re-evaluates the tasks it was blocking. The `$dependsOnTaskId` argument breaks two-node cycles.
|
|
44
44
|
|
|
45
|
-
###
|
|
45
|
+
### Sprint-list defaults (`taskCreated` + `taskMoved`)
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
Applied by `applySprintEntryDefaults()` when a task is **created in**, or **moved into**, a list whose
|
|
48
|
+
name starts with `Sprint` inside the Dev Sprints or Design Sprints folder
|
|
49
|
+
(`isInDevOrDesignSprintsFolder()`). Backlog and other lists get nothing.
|
|
50
|
+
|
|
51
|
+
- **Condition** = On Track if empty (`defaultConditionOnTrack()`), then **Previous Sprint Condition**
|
|
52
|
+
= Condition (`CLICK_UP_CUSTOM_FIELD_ID__PREVIOUS_CONDITION`).
|
|
53
|
+
- **Has UI Changes** checked (Dev Sprints only).
|
|
54
|
+
- **Work Type** = Unplanned if empty (Dev Sprints only; `defaultWorkTypeUnplanned()`, option resolved
|
|
55
|
+
by name).
|
|
56
|
+
- **Previous Sprint Work Type** = the Work Type name (or Unplanned) — **only on sprint lists** (it used
|
|
57
|
+
to fire on Backlog too).
|
|
58
|
+
- Every optional write goes through private `setFieldSafely($taskId, $fieldLabel, callable)`:
|
|
59
|
+
try/catch → `error_log` + Sentry, so one refused field never aborts the job (the later
|
|
60
|
+
Clickup/Fluffer/Process dispatch still runs). Dropdowns are set by option name
|
|
61
|
+
(`setDropdownByName()`).
|
|
62
|
+
- Design FDO default is dispatched from `taskCreated` too — see
|
|
63
|
+
[design sprint automation](./clickup-design-sprint-automation.md).
|
|
64
|
+
|
|
65
|
+
**Dev sprint data ingestion** (sprint tag, `ensureTaskExistsInCurrentSprint`, dependencies) requires
|
|
66
|
+
the task's **HOME** folder = Dev Sprints (`isHomeFolderInQaQcScope()`), not just any list membership —
|
|
67
|
+
so Design tasks multi-listed into a Dev list stay out of Dev reports (owner decision).
|
|
51
68
|
|
|
52
69
|
Data model: reads/writes the `Team` database (`Tasks`, `Developers`, `Tasks_Developers`, `Sprints`) and the ClickUp REST API. The Work Type itself lives in ClickUp as a custom field (`CLICK_UP_CUSTOM_FIELD_ID__WORKTYPE`), not in the DB.
|
|
53
70
|
|
|
@@ -58,8 +75,15 @@ Data model: reads/writes the `Team` database (`Tasks`, `Developers`, `Tasks_Deve
|
|
|
58
75
|
- **No PHPUnit harness** in worker2. The regression test `Tests/Worker/ClickupWorkTypeTest.php` is a plain-PHP script (reflection on `isStatusComplete`) run with `php Tests/Worker/ClickupWorkTypeTest.php`.
|
|
59
76
|
- **ClickUp dropdown custom-field `value` is the option ORDERINDEX, not its name.** The webhook payload for a dropdown/labels custom field delivers the selected option's integer `orderindex`, *not* the option's display name. Any name-keyed lookup (e.g. `$lookupPreviousSprintWorkTypeByName`) will silently never match if fed the raw `value`. Resolve the name first via the field's own `type_config->options` — build a map keyed by `orderindex` (`$workTypesByIndex[$opt->orderindex] = $opt`) and read `->name` from it. This is the proven pattern in `_Worker_Team_Sprint::SprintLock` (`Worker/Team/Sprint.php`, the Work Type resolve ~L3894 and the POST ~L3971). Guard the lookup with `isset($workTypesByIndex[$value])` so a stale/deleted option orderindex doesn't warn.
|
|
60
77
|
|
|
78
|
+
- **Home folder vs. any list.** `isTaskInQaQcScope()` matches any list the task is in (good for QA/QC
|
|
79
|
+
gates); sprint **data ingestion** must use `isHomeFolderInQaQcScope()`. Mixing them up leaks Design
|
|
80
|
+
tasks into Dev sprint reports.
|
|
61
81
|
- **2026-09-21..25 outage: every taskCreated failed (~113 jobs, 0 Fluffer jobs).** The automation user lacked edit rights on Previous Sprint Work Type → HTTP 401 `ACCESS_606`; the throw killed the rest of the job. Fixed by field permissions + `setFieldSafely`. Any new optional write must use `setFieldSafely`. Account-change check: [credential rotation](../workflows/clickup-credential-rotation.md).
|
|
62
82
|
|
|
63
83
|
## Related
|
|
64
84
|
|
|
65
85
|
- [ClickUp Project & Opportunity Multi-List Routing](./clickup-project-routing.md)
|
|
86
|
+
|
|
87
|
+
## Change history
|
|
88
|
+
- 2026-10-01 — Defaults also on taskMoved into a `Sprint*` list; Condition / Previous Sprint Condition / Has UI Changes added; Previous Sprint Work Type limited to sprint lists; ingestion gated on HOME folder (ajean; worker2 PR #178)
|
|
89
|
+
- 2026-09-25 — `setFieldSafely` after the ACCESS_606 taskCreated outage (jcardinal)
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["kyalamarthi"]
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: ["kyalamarthi", "ajean"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/.gitignore
|
|
13
13
|
- worker2/Worker/Team/Github.php
|
|
@@ -43,7 +43,8 @@ the publish on a credential literal.
|
|
|
43
43
|
|---|---|---|
|
|
44
44
|
| GitHub personal access token, `agilantgit` account | `worker2/Worker/Team/Github.php:9` **and** `worker2/.ebextensions/git.json:4` (same token, two copies) | Committed since 2025-01-17 and present in **166 of 167 branches**, so scrubbing history is not a fix — **rotation is the only fix**. Org-wide reach. It is also interpolated into a `shell_exec` clone URL, so it shows up in the instance process list. |
|
|
45
45
|
| NetSuite EC private key | `worker2/Config/netsuite_ec_key.pem` | Tracked in git as a file. |
|
|
46
|
-
| TOGa Talos (`api.togaiq.com`) API key | `worker2/Worker/Team/Sprint.php
|
|
46
|
+
| TOGa Talos (`api.togaiq.com`) API key — **old** release-notes key | git history only: `worker2/Worker/Team/Sprint.php` before PR #179 (still visible at commit `8613ace`) | Code no longer uses it, but it was committed, so it **must be revoked** in TOGa Talos. Open until revoked. |
|
|
47
|
+
| TOGa Talos API key — **current** (`[talos] api_key`) | `worker2/Config/production.ini` `[talos]` group (~L230) | Shared by release notes and the transcript pipeline. Committed like every `Config/*.ini` (see Summary). |
|
|
47
48
|
| PayPal webhook signature verification **off** in production | `worker2/Config/production.ini:182` | Impact already written up — a caller who knows the URL can create a paid SalesOrder. See [Rate PayPal subscription webhook](../../../../clients/rate/features/paypal-subscription-purchase-webhook.md). |
|
|
48
49
|
|
|
49
50
|
### Fixed / no longer true
|
|
@@ -54,6 +55,12 @@ the publish on a credential literal.
|
|
|
54
55
|
`Authorization` header written into `Logs.Api`, are separate open items — see
|
|
55
56
|
[_ApiRequest gotchas](../../_underscore/features/apirequest-json-content-type.md#gotchas).
|
|
56
57
|
|
|
58
|
+
- **TOGa Talos key in `Sprint.php`** (2026-10-01, worker2 PR #179) — the `AI_API_KEY` constant is
|
|
59
|
+
**gone**. `aiApiKey()` reads `[talos] api_key`, the same `/api/ai/generate` key the transcript
|
|
60
|
+
pipeline already uses (no new `[togaiq]` group). This removed a **second** copy of a TOGa Talos key
|
|
61
|
+
from PHP — but `[talos] api_key` is itself committed in `production.ini` (table above), so treat it
|
|
62
|
+
as a refactor, not a fix.
|
|
63
|
+
|
|
57
64
|
## Gotchas
|
|
58
65
|
|
|
59
66
|
- **Config is not private here.** Moving a secret from a PHP constant into
|
|
@@ -75,3 +82,7 @@ the publish on a credential literal.
|
|
|
75
82
|
- [Team Sprint Management & Reporting](./team-sprint-management.md)
|
|
76
83
|
- [_ApiRequest — encode/decode & api-logging behavior](../../_underscore/features/apirequest-json-content-type.md)
|
|
77
84
|
- [Rate PayPal Subscription Purchase & Webhook Pipeline](../../../../clients/rate/features/paypal-subscription-purchase-webhook.md)
|
|
85
|
+
|
|
86
|
+
## Change history
|
|
87
|
+
- 2026-10-01 — TOGa Talos release-notes key moved to `[talos] api_key`; old key listed for revocation (ajean)
|
|
88
|
+
- 2026-09-21 — Register created from audit (kyalamarthi)
|