toga-ai 1.0.134 → 1.0.135

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.
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-17
9
+ updated: 2026-06-18
10
10
  owners: ["dfranks"]
11
11
  files:
12
12
  - worker2/Worker/Netsuite.php
@@ -90,7 +90,15 @@ second, independently-gated concern in the same handler.
90
90
 
91
91
  1. `findClickupTaskByOpportunityNumber($tranId)` — GET the list filtered by the `Opportunity #`
92
92
  custom field (`include_closed=true&include_archived=true&custom_fields=[{field_id,operator:'=',value}]`).
93
- 2. **Match → `updateTask()`**: **diffs first, writes only what changed** (change-detection backstop,
93
+ 2. **Match → `updateTask()` — but ONLY when `UPDATE_CLICKUP === true`.** `const UPDATE_CLICKUP = false`
94
+ ([Opportunity.php](worker2/Worker/Netsuite/Opportunity.php)) is a **deliberate one-way guard, NOT a
95
+ temporary backstop to flip**: once a ClickUp opportunity task exists, ClickUp is the source of truth
96
+ for it, and a NetSuite edit must **not** clobber edits made on the ClickUp side. With the flag false
97
+ (its production value), a match returns `skipped update (update disabled)` and ClickUp is left
98
+ untouched — so the *designed, correct* behavior of a NetSuite edit to an opportunity that already has
99
+ a task is: **Forecast upserts, ClickUp unchanged.** Do not "enable updates" by flipping it. The
100
+ change-detection logic below only runs in the (currently-off) update path. When enabled, it:
101
+ **diffs first, writes only what changed** (change-detection backstop,
94
102
  since 2026-06-17). PUT name/description **only if** one differs; POST **only** the custom fields
95
103
  that differ (each to `/task/{id}/field/{fieldId}` — ClickUp has no bulk custom-field set). When
96
104
  nothing differs it writes nothing and returns `task unchanged`. This kills the no-op-write echo: a
@@ -204,6 +212,13 @@ None — platform-wide Forecast sync.
204
212
  (`https://webhook.togahub.com/netsuite`) with `debugWrap` off. Also flip the enqueuer deployment
205
213
  `Testing → Released` (Testing fires only for the deploying user). Full checklist in
206
214
  `DEPLOY_RUNBOOK.md` §6.
215
+ - **ClickUp's `=` custom-field filter is substring/prefix, NOT strict equality.** `findClickupTaskByOpportunityNumber($tranId)`
216
+ filters the `Opportunity #` field with `operator:'='`, but for a short-text custom field ClickUp matches
217
+ loosely: a lookup for `74266` still returns a task whose value is `74266--` (confirmed live 2026-06-18).
218
+ So **appending** to the dedup value does NOT defeat the match — to force a dedup *miss* (e.g. to exercise
219
+ the create branch) you must change the **leading** content or clear the field entirely. Verified both ways
220
+ on opp 74266/internalId 7161054: `74266--` → matched (update branch, skipped); field removed → missed
221
+ (create branch, new task `868k2rfj8`).
207
222
  - **ClickUp dedup keys on the `Opportunity #` field, not a DB column.** We deliberately did NOT add
208
223
  a `clickupTaskId` column — the dedup lookup queries ClickUp itself by `Opportunity #` (= tranId).
209
224
  Consequences to know:
@@ -262,6 +277,16 @@ same **skip-if-unchanged** compare on the extracted values, and **actor-identity
262
277
  trigger a CU→NS write. The NS→CU change-detection above is the complementary backstop, not a substitute.
263
278
 
264
279
  ## Change history
280
+ - 2026-06-18 — **Verified live in production** (no code change): real NetSuite SuiteScript webhooks
281
+ (`user-agent: NetSuite/2026.1`) arrive at `webhook.togahub.com/netsuite` and process successfully
282
+ (e.g. opp 6926224 PUT). Clarified that **`UPDATE_CLICKUP = false` is an intentional one-way guard**
283
+ (NS edits must not overwrite ClickUp-side edits), not a flag to flip — so "NS edit → Forecast upserts,
284
+ existing ClickUp task untouched" is the *designed* behavior. Documented that ClickUp's `=` custom-field
285
+ filter is **substring/prefix**, not exact (a `74266` lookup matched `74266--`); confirmed both dedup
286
+ branches on opp 74266/internalId 7161054. Testing note: the webhook needs the **internalId**, not the
287
+ opportunity number — resolve it via `Forecast.Opportunities.netsuiteOpportunityInternalId` for a given
288
+ `opportunityNumber` (tranId). Fire a manual test with
289
+ `curl -X POST https://webhook.togahub.com/netsuite -d '{"recordType":"opportunity","eventType":"edit","internalId":<id>}'`. (dfranks)
265
290
  - 2026-06-17 — ClickUp **change-detection backstop** added to `updateTask()`: diff before write, skip
266
291
  no-op syncs (`task unchanged`), PUT name/description only when changed, POST only changed custom
267
292
  fields. `findClickupTaskByOpportunityNumber()` now returns the full task object; added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.134",
3
+ "version": "1.0.135",
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",