toga-ai 1.0.109 → 1.0.110

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.
@@ -75,7 +75,24 @@ second, independently-gated concern in the same handler.
75
75
  `_Worker::runTask('Netsuite/Opportunity/<verb>', {internalId})`.
76
76
  5. **Handle (worker2):** `_Worker_Netsuite_Opportunity` fetches the record from NetSuite by id
77
77
  and upserts it into `Forecast.Opportunities` (+ `OpportunityItems`), and — only when a
78
- Presales Lead is set — creates the ClickUp opportunity task.
78
+ Presales Lead is set — creates the ClickUp opportunity task. **Both `POST` (create) and `PUT`
79
+ (edit) create the task** (since 2026-06-16); `DELETE` only removes from Forecast.
80
+
81
+ ## ClickUp opportunity task
82
+
83
+ Created by `maybeCreateClickupTask()` → `createTask()` (ported verbatim from the legacy
84
+ `webhook/` handler), gated on a **Presales Lead** (`custbody_ctc_solution_architect` employee →
85
+ email; empty email = skip). Posts to list `901111987449` with `custom_item_id` = `1009`.
86
+
87
+ - **Task name** = `{opportunityNumber} — {customer} — {title}`, e.g.
88
+ `60533 — 560 Endeavor Health — Data Center Facility Consolidation Services` (empty parts dropped).
89
+ - **Custom-field value mapping** (the non-obvious part — these were swapped before 2026-06-16):
90
+ `Opportunity #` (`a5529cdc-…`) ← `tranId`; `Customer #` (`170dc118-…`) ← `entity->refName`
91
+ (the customer **name**, deliberately — not the NetSuite customer number). Plus `Sales Rep`,
92
+ `Presales Lead Email`, and `Presales Lead` (users field, resolved via a cached
93
+ `Client_True.Users.c_clickupUserIdentifier` lookup, falling back to a `GET /team` scan).
94
+ - The ClickUp token comes from `[clickup] token` in the worker2 config (legacy webhook received
95
+ it on the payload; the new contract drops it).
79
96
 
80
97
  ## Data model
81
98
 
@@ -128,7 +145,26 @@ None — platform-wide Forecast sync.
128
145
  - **No WebhookIngestion Lambda locally.** Dev points the enqueuer at an ngrok tunnel to local
129
146
  worker2 via the `DEV_OVERRIDE` block, which also wraps the body in worker2's legacy
130
147
  `{action,parameters}` envelope (since there's no Lambda to convert `/netsuite` → an action).
131
- Set `DEV_OVERRIDE.enabled=false` for prod.
148
+ - **`debugWrap` is the dev/prod body-shape switch — not just the URL.** `debugWrap=true` (dev)
149
+ nests the raw `{recordType,internalId,…}` contract inside worker2's legacy `{action,parameters}`
150
+ envelope (`action='Netsuite/Webhook'`); the local `worker()` legacy path needs this because
151
+ there's no Lambda. `debugWrap=false` (prod) sends the raw contract — the WebhookIngestion Lambda
152
+ derives the `Netsuite/Webhook` action from the `/netsuite` URL path itself. Changing only the
153
+ endpoint to prod while leaving `debugWrap` on double-wraps the body and breaks the handler.
154
+ - **Prod cutover: flip `DEV_OVERRIDE.enabled=false` — do NOT comment out the block.** `getConfig()`
155
+ reads `DEV_OVERRIDE.enabled/.endpoint/.debugWrap`; deleting the `const` throws `ReferenceError`,
156
+ which `afterSubmit`'s catch swallows as `AMQ enqueue FAILED` → **no row enqueued, sync silently
157
+ stops.** Flipping `enabled=false` makes `getConfig()` fall through to `DEFAULTS.endpoint`
158
+ (`https://webhook.togahub.com/netsuite`) with `debugWrap` off. Also flip the enqueuer deployment
159
+ `Testing → Released` (Testing fires only for the deploying user). Full checklist in
160
+ `DEPLOY_RUNBOOK.md` §6.
161
+ - **No ClickUp dedup (deferred).** `maybeCreateClickupTask()` only gates on the Presales Lead — it
162
+ does **not** check whether a task already exists. Since both POST and PUT now create, **every edit
163
+ of an opportunity with a Presales Lead creates a NEW ClickUp task** (and the NetSuite-side
164
+ enqueuer fires on every save). Before this hits production traffic, add dedup — recommended:
165
+ store the created ClickUp task id on the `Forecast.Opportunities` row and skip when present.
166
+ Until 2026-06-16 PUT skipped ClickUp entirely, which masked this (an edit produced no duplicate
167
+ only because the edit path didn't create at all).
132
168
  - **NetSuite doubled-prefix ids.** The queue record and its fields were created with the full
133
169
  custom prefix typed into the ID box, so the real ids are doubled:
134
170
  type `customrecordcustomrecord_api_msg_queue`, fields `custrecordcustrecord_amq_*`. The
@@ -142,6 +178,11 @@ None — platform-wide Forecast sync.
142
178
  (`Created`,`Pending`,`Retry`,`Sending`) — omitting `Created` silently matches nothing (`candidates:0`).
143
179
 
144
180
  ## Change history
181
+ - 2026-06-16 — ClickUp parity hardening (TRUE-79182): verified the `Opportunity #`/`Customer #`
182
+ custom fields exist on list 901111987449 and **fixed their swapped values**; `PUT` now also
183
+ creates the ClickUp task (was POST-only); task name reformatted to
184
+ `{opp#} — {customer} — {title}`; `createTask()` made private + generic `Exception` throws
185
+ narrowed to `RuntimeException`. Dedup still deferred (see gotcha). (dfranks)
145
186
  - 2026-06-16 — Trigger correction: the immediate trigger moved INTO the enqueuer (`task.create`
146
187
  the drainer) after discovering a UE can't trigger a UE — `ue_amq_drain` retired. `task.create`
147
188
  needs a `Not Scheduled` deployment of `ss_amq_drain` (else `NO_DEPLOYMENTS_AVAILABLE`); submit by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.109",
3
+ "version": "1.0.110",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",