toga-ai 1.0.788 → 1.0.790
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/worker/features/netsuite-togasupply-per-client-sync.md +77 -1
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -1
- package/knowledge/2.0/apps/worker2/features/qa-qc-branch-automation.md +31 -5
- package/knowledge/2.0/apps/worker2/features/qa-qc-review-pipeline.md +94 -11
- package/knowledge/clients/nychh/profile.md +22 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-09
|
|
10
10
|
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -257,6 +257,45 @@ again, the sections still could **not advance** — the value was in the **wrong
|
|
|
257
257
|
This is the same SuiteQL-vs-REST clock trap as the date-only-format bug above — see the
|
|
258
258
|
[SuiteQL/REST shim doc](../../library/features/netsuite-suiteql-rest-shim.md).
|
|
259
259
|
|
|
260
|
+
### ⚠ …and the cursor was READ on the wrong clock too — `strtotime()` (Chicago) shifted the fetch window +1h → SILENT record skips across ALL 6 sections + ALL clients (found + fixed 2026-09-09, NYCHH prod)
|
|
261
|
+
|
|
262
|
+
The deepest of the cursor-clock bugs, and the one that actually **dropped records that never came
|
|
263
|
+
back**. The two clock bugs above are on the **write** side of the cursor; this one is on the
|
|
264
|
+
**read** side of the same value, and it was silent — no crash, no alert, no frozen section. It
|
|
265
|
+
hit **every section and every client**, not just the by-reference ones.
|
|
266
|
+
|
|
267
|
+
- **Root cause — read clock ≠ fetch clock.** The cursor value `"<lastmoddate>|<netsuiteId>"` stores
|
|
268
|
+
its datetime in the NetSuite account timezone = **America/New_York (Eastern)** — the same clock the
|
|
269
|
+
list-fetch `WHERE` bounds render in (`App_Api_Netsuite_Rest::listItemFulfillments` et al. call
|
|
270
|
+
`->setTimezone('America/New_York')`). But `startModeIteration()`
|
|
271
|
+
(`common_sync_togasupply.php` ~L622) read it back with **`strtotime()`**, which parses in the
|
|
272
|
+
**script timezone America/Chicago** (set in library `_.php`). Chicago is 1h behind Eastern, so the
|
|
273
|
+
same digits produced an epoch **1 hour later**; the list fetch then re-rendered that bound in
|
|
274
|
+
Eastern, shifting the window's **lower bound +1 hour**.
|
|
275
|
+
- **Effect — a silent fetch-level skip.** Any record whose Eastern lastmod fell in that **1-hour
|
|
276
|
+
shadow** right after the previous checkpoint was **excluded from the fetch**; the window read empty,
|
|
277
|
+
and the `|0` empty-window jump advanced the cursor **past** it. Nothing threw — a throw still
|
|
278
|
+
freezes the section loudly; this is the quiet path that loses data. Combined with the giant-record
|
|
279
|
+
900s timeouts (below) that historically froze sections and were then manually stepped over, the
|
|
280
|
+
gaps compounded over time.
|
|
281
|
+
- **Proven on NYCHH prod** (item fulfillment `6138135`, lastmod **2025-09-04 17:26:58 Eastern**): the
|
|
282
|
+
+1h bound (`> 17:29:03`) returned **0** records; the correct bound (`> 16:29:03`) returned **1**
|
|
283
|
+
(that record). The cursor trace showed the `|0` empty-window jump right over its time slot.
|
|
284
|
+
- **Fix (worker only, deployed + verified):** parse the cursor as Eastern in `startModeIteration` —
|
|
285
|
+
`$epochLastIntegration = (new \DateTime($dtLastIntegration, new \DateTimeZone('America/New_York')))->getTimestamp();`
|
|
286
|
+
— and change all **6** empty-window `|0` jump writes (~L831/938/1024/1193/1281/1369) from
|
|
287
|
+
`App_Date::convertToSQLDate(...)` (renders Chicago) to
|
|
288
|
+
`App_Date::convertToTimezone($epoch, 'Y-m-d H:i:s', 'America/New_York')`, so the **whole** cursor
|
|
289
|
+
pipeline — read + per-record write + empty-window write — runs on **one** clock (Eastern). DST-safe
|
|
290
|
+
(named IANA zone). php-reviewer + cto both **AGREE**; cto flagged (and we applied) the
|
|
291
|
+
empty-window-write hardening to stop a permanent 1h backward bias building up on idle runs. Verified
|
|
292
|
+
after setting NYCHH's fulfillment cursor to `2025-09-04 00:00:00|0`: `6138135` imported.
|
|
293
|
+
- **The rule (now proven THREE times — this bug + the two write-clock bugs above):** every touch of
|
|
294
|
+
the cursor value — the **read**, the per-record **write**, and the empty-window **write** — must use
|
|
295
|
+
the NetSuite account timezone (**Eastern**), never the script's Chicago clock and never the REST
|
|
296
|
+
GET's UTC. `strtotime()` and `App_Date::convertToSQLDate()` both use the script tz (Chicago) and are
|
|
297
|
+
the trap. Any manual cursor rewind value you set by hand must also be **Eastern**.
|
|
298
|
+
|
|
260
299
|
### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
|
|
261
300
|
|
|
262
301
|
Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
|
|
@@ -454,6 +493,19 @@ See the [tracking-number bridge doc](../../../2.0/apps/_underscore/features/trac
|
|
|
454
493
|
0 of their fulfillments resolve an upstream sales order). `/units` writes and all three
|
|
455
494
|
tracking-bridge models have **no** interceptor, and **DELETE fires none**.
|
|
456
495
|
|
|
496
|
+
- **The two `GET /sales-orders` calls use a narrow `fields` projection, NOT `depth` (2026-09-09).**
|
|
497
|
+
Both the sales-order lookup and the missing-SO refetch in `syncItemFulfillmentFromNetsuite`
|
|
498
|
+
(`toga2.php` ~L4644-4706) request only `uuid`,
|
|
499
|
+
`salesOrderItems.{uuid,lineNumber,quantity,c_netsuiteLineUniqueKey}`, and
|
|
500
|
+
`salesOrderItems.item.{uuid,partNumber}` — the only fields the line-matcher reads. They previously
|
|
501
|
+
sent `depth=5`, whose huge nested read (units, tracking, PO links) made api2 exceed the worker's
|
|
502
|
+
**900s cURL** limit and threw **before** the `ItemFulfillments` row was written, freezing the
|
|
503
|
+
section (`Logs.Issue` 666, `apitransaction.php:418`). SO GETs dropped 100s+ → ~1s. Same class of fix
|
|
504
|
+
as the 2026-09-08 inventory-adjustments `depth=5→4` change; a `fields` projection is the proven
|
|
505
|
+
pattern here (already used by the `/item-fulfillments` GET in this same method) and dodges the api2
|
|
506
|
+
max-depth FK omission. Keep new fields explicit in the projection — a needed field left out of the
|
|
507
|
+
list silently reads `null`, not an error.
|
|
508
|
+
|
|
457
509
|
### Single-tracking-number fan-out to items (318) and units (319) — corrected 2026-08-26
|
|
458
510
|
|
|
459
511
|
The earlier "maintains all three bridges" claim was **only true for 317 (header)**: in prod
|
|
@@ -1387,6 +1439,30 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
1387
1439
|
|
|
1388
1440
|
## Change history
|
|
1389
1441
|
|
|
1442
|
+
- 2026-09-09 — **The resume cursor was READ on the wrong clock — a silent +1h window skip dropped
|
|
1443
|
+
records across ALL 6 sections and ALL clients (worker, deployed + verified).** `startModeIteration`
|
|
1444
|
+
(`common_sync_togasupply.php` ~L622) parsed the Eastern-stored cursor with `strtotime()` (script tz
|
|
1445
|
+
America/Chicago), so the fetch's lower bound rendered **1 hour high** and any record in that 1-hour
|
|
1446
|
+
shadow was excluded; the empty window then `|0`-jumped past it — no crash, no alert. Fixed by parsing
|
|
1447
|
+
the cursor as `America/New_York` and rewriting the 6 empty-window `|0` jump writes with
|
|
1448
|
+
`App_Date::convertToTimezone(..., 'America/New_York')`, so read + per-record write + empty-window
|
|
1449
|
+
write all run on one clock (Eastern). Proven on NYCHH IF `6138135` (Eastern 2025-09-04 17:26:58): the
|
|
1450
|
+
+1h bound returned 0 records, the correct bound returned 1. php-reviewer + cto AGREE; cto's
|
|
1451
|
+
empty-window-write hardening applied. Now the THIRD cursor-clock bug — the standing rule is every
|
|
1452
|
+
touch of the cursor value uses Eastern, never Chicago (`strtotime`/`convertToSQLDate`) and never the
|
|
1453
|
+
REST GET's UTC. See the read-clock gotcha in *How it works*. (jcardinal)
|
|
1454
|
+
- 2026-09-09 — **Fulfillment sales-order GET at `depth=5` hit the worker's 900s cURL timeout and froze
|
|
1455
|
+
the section (library, deployed + verified).** Both `GET /sales-orders` calls in
|
|
1456
|
+
`App_Api_Toga2::syncItemFulfillmentFromNetsuite` (`toga2.php` ~L4644-4706 — the lookup + the
|
|
1457
|
+
missing-SO refetch) changed from `depth=5` to a narrow `fields` projection (uuid;
|
|
1458
|
+
`salesOrderItems.{uuid,lineNumber,quantity,c_netsuiteLineUniqueKey}`;
|
|
1459
|
+
`salesOrderItems.item.{uuid,partNumber}` — the only fields the matcher reads), dropping the huge
|
|
1460
|
+
nested read (units, tracking, PO links) that pushed api2 past 900s and threw before the
|
|
1461
|
+
`ItemFulfillments` row was written (`Logs.Issue` 666, `apitransaction.php:418`). Same class as the
|
|
1462
|
+
2026-09-08 inventory-adjustments depth fix; the `fields` projection is the proven pattern (already
|
|
1463
|
+
used by the `/item-fulfillments` GET in this method). Fulfillment SO GETs dropped 100s+ → ~1s. Both
|
|
1464
|
+
fixes address the NYCHH missing-fulfillment investigation; library + worker still deploy together for
|
|
1465
|
+
this sync path. (jcardinal)
|
|
1390
1466
|
- 2026-09-08 - **A `Closed` NetSuite order never reaches the transfer-order sync** - the
|
|
1391
1467
|
`status === 'Closed'` `continue` at `common_sync_togasupply.php` ~L746 sits **before** the
|
|
1392
1468
|
`isTransferOrder()` branch, so `syncTransferOrderFromNetsuite`'s `case 'Closed'` was dead code and
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
| [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. |
|
|
46
46
|
| [Platform Cache Cleanup (_Worker_Platform_Cache — Clean + Truncate)](features/platform-cache-cleanup.md) | `_Worker_Platform_Cache` owns maintenance of the shared **Cache** cluster that backs api2's [multi-client data retrieval](../../api2/features/cross-client-data- |
|
|
47
47
|
| [QA/QC Branch Automation (auto-mirror + rebuild-from-ledger revert)](features/qa-qc-branch-automation.md) | Two CTO-approved (AGREE) pieces of GitHub branch automation, centralized in **worker2** rather than per-repo GitHub Actions. |
|
|
48
|
-
| [QA/QC Review Pipeline (ClickUp review chain, QC batch + defect/client-review)](features/qa-qc-review-pipeline.md) | The ClickUp-driven
|
|
48
|
+
| [QA/QC Review Pipeline (ClickUp review chain, QC batch + defect/client-review)](features/qa-qc-review-pipeline.md) | The ClickUp-driven review + promotion chain for the platform-wide QA/QC dev process (per the approved `QA-QC Plan.txt`), moving a task through **Development → Q |
|
|
49
49
|
| [Service Request → Sales Order → Purchase Order generation (Sync/ServiceRequest)](features/service-request-sales-order-generation.md) | `_Worker_Sync_ServiceRequest` turns a **Service Request into a Sales Order, and then into one Purchase Order per vendor**, for **any** tenant. |
|
|
50
50
|
| [SSO Stability Monitor (Monitor/Operations/SsoStability)](features/sso-stability-monitor.md) | `_Worker_Monitor_Operations::SsoStability(): string` is the **reporter half** of the SAML uptime monitor. |
|
|
51
51
|
| [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: draft
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-09
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Team/Github.php
|
|
@@ -25,9 +25,12 @@ than per-repo GitHub Actions. **Not deployed yet** — on the `feature-qa-qc` br
|
|
|
25
25
|
1. **Branch auto-mirror** — keep certain branches a byte-for-byte copy of a source branch.
|
|
26
26
|
2. **Rebuild-from-ledger revert** — undo one task's changes on a QA/QC branch by rebuilding the
|
|
27
27
|
branch from its merge ledger, not by `git revert`.
|
|
28
|
+
3. **The single guarded door to `_production`** — `MergeTaskToProduction`, the only path a merge
|
|
29
|
+
can reach `_production`, plus `FanProductionOut` and a hardened public `Merge()`.
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
All three feed the QA/QC review flow: a denied review routes a task to Rework and reverts its code;
|
|
32
|
+
an approved Task/Hotfix/Feature deploys through `MergeTaskToProduction` — see
|
|
33
|
+
[QA/QC review pipeline](./qa-qc-review-pipeline.md).
|
|
31
34
|
|
|
32
35
|
## How it works
|
|
33
36
|
|
|
@@ -54,6 +57,22 @@ see [QA/QC review pipeline](./qa-qc-review-pipeline.md).
|
|
|
54
57
|
merge-commit revert (and revert-of-revert) break there — rebuild-from-ledger is the only
|
|
55
58
|
correct undo.
|
|
56
59
|
|
|
60
|
+
### The single guarded door to `_production` (`MergeTaskToProduction`)
|
|
61
|
+
|
|
62
|
+
- **`_production` is reachable ONLY through `_Worker_Team_GitHub::MergeTaskToProduction`.** It
|
|
63
|
+
groups a task's PRs by repo and merges each — used by the Hotfix flow and the Stage cron.
|
|
64
|
+
- Two layers stop a double-deploy when a webhook re-fires:
|
|
65
|
+
1. a **per-repo `GET_LOCK`**, and
|
|
66
|
+
2. a **persisted deploy-claim** — a row in `EnvironmentBranchMerges` with `branch='_production'`.
|
|
67
|
+
A re-fired webhook or a cron re-run sees the claim and resumes instead of re-merging.
|
|
68
|
+
- **Public `Merge()` is split** so no queue message can reach prod by accident:
|
|
69
|
+
- the public `Merge()` is a **thin wrapper that refuses any non-mirror merge to `_production`**;
|
|
70
|
+
- a private `performMerge()` is the actual engine.
|
|
71
|
+
- **`FanProductionOut`** merges `_production` **into the rebuildable branches** after a deploy, to
|
|
72
|
+
keep them current.
|
|
73
|
+
- **Reverts are rebuild-from-ledger, never a merge-commit revert** (see below) — this holds for
|
|
74
|
+
`_production`-carrying branches too.
|
|
75
|
+
|
|
57
76
|
### Safety rules (load-bearing)
|
|
58
77
|
|
|
59
78
|
- **Force-push is gated to `REBUILDABLE_BRANCHES` only.** Mirror targets and `_production` are
|
|
@@ -70,13 +89,20 @@ see [QA/QC review pipeline](./qa-qc-review-pipeline.md).
|
|
|
70
89
|
- **Never `git revert` a QA/QC env-branch merge.** Env branches carry `_production` merges too,
|
|
71
90
|
so merge-commit revert / revert-of-revert break. Use `RebuildBranch` (reset + re-merge from
|
|
72
91
|
the ledger).
|
|
92
|
+
- **Nothing merges to `_production` except `MergeTaskToProduction`.** Public `Merge()` refuses a
|
|
93
|
+
non-mirror `_production` merge, so a stray queue message can't deploy. Re-fires are stopped by
|
|
94
|
+
the per-repo `GET_LOCK` + the persisted deploy-claim (`EnvironmentBranchMerges` `branch='_production'`).
|
|
73
95
|
|
|
74
96
|
## Change history
|
|
97
|
+
- 2026-09-09 — Added the single guarded door to `_production` (draft, `feature-qa-qc`, not
|
|
98
|
+
deployed): `MergeTaskToProduction` (per-repo `GET_LOCK` + persisted deploy-claim in
|
|
99
|
+
`EnvironmentBranchMerges` `branch='_production'`, groups a task's PRs by repo); public `Merge()`
|
|
100
|
+
split into a wrapper that **refuses** any non-mirror `_production` merge + a private
|
|
101
|
+
`performMerge()` engine; `FanProductionOut` merges `_production` into the rebuildable branches.
|
|
102
|
+
(jcardinal)
|
|
75
103
|
- 2026-09-08 — Initial (draft, on `feature-qa-qc`, not deployed): centralized GitHub branch
|
|
76
104
|
automation in worker2. Auto-mirror via `MIRROR_MAP` (hard-reset + force-with-lease, push
|
|
77
105
|
webhook-driven); rebuild-from-ledger revert (`RevertTask`/`RebuildBranch`/`recordMerge`,
|
|
78
106
|
`REBUILDABLE_BRANCHES`, `Team.EnvironmentBranchMerges`) replacing `git revert -m 1`. Force-push
|
|
79
107
|
gated to rebuildable branches; `GET_LOCK` serializes merge vs rebuild; conflicts skip+email.
|
|
80
108
|
(jcardinal)
|
|
81
|
-
</content>
|
|
82
|
-
</invoke>
|
|
@@ -6,15 +6,20 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: draft
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-09
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Clickup/QaReview.php
|
|
13
13
|
- worker2/Worker/Clickup/QcReview.php
|
|
14
14
|
- worker2/Worker/Clickup/QcBatch.php
|
|
15
|
+
- worker2/Worker/Clickup/StageBatch.php
|
|
16
|
+
- worker2/Worker/Clickup/FeatureReview.php
|
|
17
|
+
- worker2/Worker/Clickup/BeReview.php
|
|
15
18
|
- worker2/Worker/Clickup.php
|
|
19
|
+
- worker2/Worker/Github.php
|
|
16
20
|
- _underscore/Component/Api/Clickup/Clickup.php
|
|
17
|
-
- dbchanges2/Core/2026-09-
|
|
21
|
+
- dbchanges2/Core/2026-09-09a - Insert StageBatch CronJob.sql
|
|
22
|
+
- dbchanges2/Team/2026-09-09a - Add isApproved to PullRequests.sql
|
|
18
23
|
related:
|
|
19
24
|
- ./clickup-general-automation.md
|
|
20
25
|
- ./qa-qc-branch-automation.md
|
|
@@ -23,17 +28,31 @@ related:
|
|
|
23
28
|
|
|
24
29
|
## Summary
|
|
25
30
|
|
|
26
|
-
The ClickUp-driven
|
|
27
|
-
approved `QA-QC Plan.txt`)
|
|
28
|
-
|
|
31
|
+
The ClickUp-driven review + promotion chain for the platform-wide QA/QC dev process (per the
|
|
32
|
+
approved `QA-QC Plan.txt`), moving a task through **Development → QA → QC → (Client Review) →
|
|
33
|
+
Stage → Production**. Several **new** workers plus entry points in the shared ClickUp webhook
|
|
34
|
+
handler. **Not deployed yet** — on the `feature-qa-qc` branch working tree.
|
|
29
35
|
|
|
30
|
-
- `_Worker_Clickup_QaReview` — the UI → FE → BE review chain
|
|
31
|
-
-
|
|
36
|
+
- `_Worker_Clickup_QaReview` — the UI → FE → BE review chain (branches by Task Type; Hotfix
|
|
37
|
+
BE-approve jumps straight to QC-Hotfix).
|
|
38
|
+
- `_Worker_Clickup_QcReview` — QC defect review + client review; also handles the **Hotfix**
|
|
39
|
+
defect gate and its `DeployHotfix` entry.
|
|
32
40
|
- `_Worker_Clickup_QcBatch` — a weekday 4:30pm Central CRON that batches QA-Approved tasks into QC.
|
|
41
|
+
- `_Worker_Clickup_StageBatch` — the **Wed 4:30pm Central** CRON that ships "To Deploy" tasks to
|
|
42
|
+
production (the Task-flow prod-deploy piece; replaces the retired legacy status-driven merge).
|
|
43
|
+
- `_Worker_Clickup_FeatureReview` — the **Feature** flow: rolls a multi-subtask feature through the
|
|
44
|
+
same gates as a unit, advancing only when **all** subtasks clear a gate.
|
|
45
|
+
- `_Worker_Clickup_BeReview` — **auto** BE-approve driven by GitHub PR reviews (removes the manual
|
|
46
|
+
BE-review step).
|
|
47
|
+
|
|
48
|
+
**Scope gate — only fires in one ClickUp folder.** The whole automation is gated to the dedicated
|
|
49
|
+
**"(TEST) True Dev Sprints"** folder (id `90116585845`, list `901111612373`) via
|
|
50
|
+
`_Worker_Clickup::QA_QC_FOLDER_ID` + `isTaskInQaQcScope`, so it never touches live sprints.
|
|
33
51
|
|
|
34
52
|
When a review is **denied**, the task is routed to **Rework** and its code changes are **reverted**
|
|
35
53
|
from the QA/QC branch — see [QA/QC branch automation](./qa-qc-branch-automation.md) for how the
|
|
36
|
-
revert (rebuild-from-ledger) works
|
|
54
|
+
revert (rebuild-from-ledger) works, and for the single guarded door to `_production`
|
|
55
|
+
(`MergeTaskToProduction`) that the Stage cron and Hotfix flow deploy through.
|
|
37
56
|
|
|
38
57
|
## How it works
|
|
39
58
|
|
|
@@ -63,6 +82,52 @@ code triggered when a task *left* a status; the QA/QC entry must be on entering
|
|
|
63
82
|
- **any Show Stopper** → **Rework** + revert;
|
|
64
83
|
- otherwise route to **Stage (To Deploy)** or **Client-<letter> (Client Review)**.
|
|
65
84
|
- **Client review webhook:** **Approved** → Stage / To Deploy; **Denied** → **Rework** + revert.
|
|
85
|
+
- QC entry **sets Environment to `QC-<letter>`** (the plan's "clear" wording was a slip).
|
|
86
|
+
|
|
87
|
+
### BE auto-approve from a GitHub PR (`_Worker_Clickup_BeReview::FromPr`)
|
|
88
|
+
|
|
89
|
+
- Removes the manual BE-review step. `worker2/Worker/Github.php` gains a `pull_request_review`
|
|
90
|
+
case: it records each PR's approval in `PullRequests.isApproved`, parses the task custom id from
|
|
91
|
+
the PR title (`prTitleToCustomId` — first token, colon stripped), and dispatches `BeReview/FromPr`.
|
|
92
|
+
- `FromPr` sets the task's **BE Review** field **Approved only once ALL of the task's `_production`
|
|
93
|
+
PRs are approved**; a changes-requested on any PR → **Denied**. Multi-repo task = one decision.
|
|
94
|
+
- **PR → task lookup:** `PullRequests.title` (custom id) maps to a ClickUp task via
|
|
95
|
+
`Team.Tasks.customClickupIdentifier ↔ clickupIdentifier`.
|
|
96
|
+
- Setting the BE Review field then drives `QaReview` (Task / Hotfix) or `FeatureReview` (Feature).
|
|
97
|
+
|
|
98
|
+
### Hotfix flow (`QaReview` + `QcReview`)
|
|
99
|
+
|
|
100
|
+
- Expedites a **single** task straight to production, **skipping Stage**.
|
|
101
|
+
- `QaReview::handleBeReview` branches by Task Type; a **Hotfix** BE-approve → **QC-Hotfix**.
|
|
102
|
+
- `QcReview` handles the Hotfix defect gate and exposes a `DeployHotfix` entry.
|
|
103
|
+
- The merge to `_production` runs from the worker with **no human at the merge moment** — the
|
|
104
|
+
guard chain (task type + status + all-defects-reviewed + no-show-stopper + deploy-claim + lock)
|
|
105
|
+
makes that safe. The actual prod merge goes through `MergeTaskToProduction` — see
|
|
106
|
+
[QA/QC branch automation](./qa-qc-branch-automation.md).
|
|
107
|
+
|
|
108
|
+
### Stage cron (`_Worker_Clickup_StageBatch`)
|
|
109
|
+
|
|
110
|
+
- The **Wed 4:30pm Central** batch (`'30 16 * * 3'`, seeded by
|
|
111
|
+
`Core/2026-09-09a - Insert StageBatch CronJob.sql`) that ships **"To Deploy"** tasks to production.
|
|
112
|
+
- `Process` loops `Team.Tasks` "to deploy" rows and dispatches **one `DeployTask` per task**, so
|
|
113
|
+
each stays under the 300s worker cap and a re-run **resumes via the deploy-claim** (no double
|
|
114
|
+
deploy). `DeployTask` ships via `MergeTaskToProduction` then the shared finalize
|
|
115
|
+
(`markProductionDeployed` + `releaseSubEnvironment`).
|
|
116
|
+
- Replaces the **retired** legacy status-driven prod-merge (the old `to deploy` / `hotfix review`
|
|
117
|
+
switch cases in `Worker/Clickup.php`).
|
|
118
|
+
|
|
119
|
+
### Feature flow (`_Worker_Clickup_FeatureReview`)
|
|
120
|
+
|
|
121
|
+
- A large **Feature** is reviewed and deployed **as one unit**: it is **one feature branch**, with
|
|
122
|
+
PRs keyed by the **feature** custom_id; **subtasks are review-only (no code)**.
|
|
123
|
+
- 5 phases: DevFinished QA-entry; UI + FE roll-ups; BE roll-up → QC; QC-defect + Client-review
|
|
124
|
+
roll-ups. Each phase advances the **feature** only when **all subtasks** clear that gate.
|
|
125
|
+
- Runs under a **per-feature `GET_LOCK`** on the feature custom_id with a **fresh-in-lock status
|
|
126
|
+
re-fetch**, so two sibling subtask reviews can't double-advance.
|
|
127
|
+
- `QaReview` / `QcReview` **skip** a Feature task or a Feature subtask (via a `parentIsFeature`
|
|
128
|
+
check) so `FeatureReview` owns them; `StageBatch` ships a Feature as one feature branch and
|
|
129
|
+
finalizes its subtasks (and skips subtasks in the batch scan).
|
|
130
|
+
- The feature's own **BE Review is a MANUAL field for now** (jcardinal + CTO decision).
|
|
66
131
|
|
|
67
132
|
## Gotchas
|
|
68
133
|
|
|
@@ -71,12 +136,20 @@ code triggered when a task *left* a status; the QA/QC entry must be on entering
|
|
|
71
136
|
- **The unit-test gate is a stub (`runUnitTests()` returns true).** It does not block anything yet.
|
|
72
137
|
- **Custom-field option VALUES are resolved at runtime, IDs are constants** — see
|
|
73
138
|
[ClickUp custom-field conventions](./clickup-custom-field-conventions.md).
|
|
139
|
+
- **The automation only fires inside the "(TEST) True Dev Sprints" folder.** `isTaskInQaQcScope`
|
|
140
|
+
gates the fan-out, `handleDevelopmentFinished`, and `QcBatch`. A task outside that folder is
|
|
141
|
+
ignored — verify the folder before expecting the flow to run.
|
|
142
|
+
- **A Feature advances only when ALL subtasks clear a gate** — a single lagging subtask holds the
|
|
143
|
+
whole feature. Subtasks carry no code; only the feature branch does.
|
|
144
|
+
- **`_underscore CLICK_UP_CUSTOM_FIELD_ID__CLIENT_REVIEW` was repointed** to the "(TEST) True Dev
|
|
145
|
+
Sprints" folder's field (`2c4ab51a-…`); all field ids were verified against that folder.
|
|
74
146
|
|
|
75
147
|
## Known security gaps (discovered, not yet fixed — separate tickets)
|
|
76
148
|
|
|
77
|
-
- **ClickUp and GitHub webhooks are NOT signature-verified today.** Verification
|
|
78
|
-
**ingestion Lambda over the raw request body** — the worker only
|
|
79
|
-
payload and cannot re-verify a signature. Do not try to add
|
|
149
|
+
- **ClickUp and GitHub webhooks are NOT signature-verified today.** Verification (incl. the GitHub
|
|
150
|
+
HMAC check) must live at the **ingestion Lambda over the raw request body** — the worker only
|
|
151
|
+
ever sees a **transformed** payload and cannot re-verify a signature. Do not try to add
|
|
152
|
+
verification in the worker.
|
|
80
153
|
- **`_Component_Api_Clickup::ACCESS_TOKEN` is a hardcoded, committed ClickUp token.** It must be
|
|
81
154
|
**rotated and moved to an environment variable**. (Only the location is recorded here — never the
|
|
82
155
|
value.) See also [ClickUp credential rotation](../workflows/clickup-credential-rotation.md).
|
|
@@ -84,6 +157,16 @@ code triggered when a task *left* a status; the QA/QC entry must be on entering
|
|
|
84
157
|
separate ticket to escape/cast them.
|
|
85
158
|
|
|
86
159
|
## Change history
|
|
160
|
+
- 2026-09-09 — Added the remaining flows (draft, on `feature-qa-qc`, not deployed): **Hotfix**
|
|
161
|
+
(QaReview branches by Task Type → QC-Hotfix; QcReview `DeployHotfix`), **Stage cron**
|
|
162
|
+
(`_Worker_Clickup_StageBatch`, Wed 4:30pm Central, one `DeployTask` per task, resumes via
|
|
163
|
+
deploy-claim; retired the legacy `to deploy`/`hotfix review` switch cases), **Feature** roll-up
|
|
164
|
+
(`_Worker_Clickup_FeatureReview`, one feature branch keyed by feature custom_id, subtasks
|
|
165
|
+
review-only, per-feature GET_LOCK + fresh-in-lock re-fetch), and **BE auto-approve from PR**
|
|
166
|
+
(`Github.php` `pull_request_review` → `BeReview::FromPr`; all `_production` PRs approved →
|
|
167
|
+
Approved; `PullRequests.isApproved`). Scoped the whole automation to the "(TEST) True Dev
|
|
168
|
+
Sprints" folder (`isTaskInQaQcScope`); repointed CLIENT_REVIEW field; QC entry sets
|
|
169
|
+
Environment `QC-<letter>`; UI Review N/A advances. (jcardinal)
|
|
87
170
|
- 2026-09-08 — Initial (draft, on `feature-qa-qc`, not deployed): new QA chain (`_Worker_Clickup_QaReview`,
|
|
88
171
|
UI→FE→BE, approve advances / deny → Rework+revert), QC batch (`_Worker_Clickup_QcBatch`, weekday
|
|
89
172
|
4:30pm Central, QA-Approved → QC-Task, skips while QC busy) and QC defect/client review
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: nychh
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-09-
|
|
21
|
+
updated: 2026-09-09
|
|
22
22
|
owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
|
|
23
23
|
files:
|
|
24
24
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
@@ -85,6 +85,27 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
|
|
|
85
85
|
`NETSUITE_LAST_SYNC_CURSOR_ITEM_RECEIPTS` is stale at 2024-08-06**, so re-enabling that feed
|
|
86
86
|
replays ~2 years. See
|
|
87
87
|
[NYCHH NetSuite → TransferOrders import](./features/netsuite-transfer-order-import.md).
|
|
88
|
+
- **Missing NetSuite records (item fulfillments + others) root-caused and fixed (2026-09-09).** Two
|
|
89
|
+
separate bugs in the 1.0 NetSuite→TOGa Supply sync starved NYCHH imports, both deployed and verified
|
|
90
|
+
in prod: (1) the resume cursor was **read on the wrong clock** (`strtotime()` in Chicago vs the
|
|
91
|
+
Eastern-stored value), shifting the fetch window +1h and **silently skipping** any record in that
|
|
92
|
+
1-hour shadow — this hit all 6 sections and all clients; and (2) the fulfillment `GET /sales-orders`
|
|
93
|
+
ran at `depth=5` and **timed out at 900s**, freezing the section before the fulfillment row was
|
|
94
|
+
written. Proven on IF `6138135` (Eastern 2025-09-04 17:26:58). Fixes and the standing "cursor is
|
|
95
|
+
always Eastern" rule live in the
|
|
96
|
+
[per-client sync feature doc](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
97
|
+
- **NYCHH sync scope + data-gap scale + backfill approach (2026-09-09).**
|
|
98
|
+
- **Scope = 22 NetSuite customers:** parent **28908** "NYC Health + Hospitals" + 21 hospital child
|
|
99
|
+
customers (`24145, 29273, 29276, 31584, 31910-31916, 31918-31925, 32229, 33674`).
|
|
100
|
+
- **Gap report (NetSuite scope vs `Client_Nychh` imported):** Item Fulfillments **2986 vs 2472**
|
|
101
|
+
(~514 missing, 17%); Sales+Transfer Orders 2121 NS vs 2098 TOGa (159 SalesOrders + 1939
|
|
102
|
+
TransferOrders — roughly complete); Invoices **2128 vs 213** (~1915 missing, 90% — but that is
|
|
103
|
+
mostly because the **invoices feed was toggled OFF on 2026-09-03**, not the timezone bug).
|
|
104
|
+
POs/Receipts/Adjustments are vendor/location-scoped (not measured) and several are toggled off.
|
|
105
|
+
- **Backfill approach:** prefer a **targeted re-import of the specific missing NetSuite ids** (diff
|
|
106
|
+
NetSuite scope vs TOGa) over a **blind cursor rewind** — a full rescan re-pulls thousands of heavy
|
|
107
|
+
serial records and stresses the shared NetSuite auth quota. Re-import is safe (matches on the
|
|
108
|
+
NetSuite internal id). Any manual cursor rewind value must be written in **Eastern**.
|
|
88
109
|
- 🚩 **OPEN, not root-caused (2026-09-08): a duplicate item INSERT aborts the whole NYCHH sales-order
|
|
89
110
|
sync run.** `Items` id 5429 (658-BFZH) was created and then re-inserted in the same run —
|
|
90
111
|
`Duplicate entry '26-658-BFZH-1' for key 'Items.manufacturerId_partNumber_catalogId'` (EV-10). The
|
package/package.json
CHANGED