toga-ai 1.0.85 → 1.0.87

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.
@@ -44,12 +44,24 @@ relays the payload to the ClickUp API verbatim.
44
44
  `description => $body->description`, and custom fields for sales rep / presales email / presales
45
45
  user id / opportunity number / customer number.
46
46
 
47
- There are (at least) two NetSuite-side triggers feeding this one webhook:
48
- - **Real-time** UserEventScript on the Opportunity "Presales Lead" field
49
- (`custbody_ctc_solution_architect`, employee ref) fires on empty→set and posts a fully-populated
50
- payload. Produces correctly-named tasks. Verified 2026-06-15: touching one opp via REST PATCH
51
- (an `xedit`) produced the correct `60533 — 560 Endeavor Health — …` task.
52
- - **A scheduled/batch process** (runs ~:01 past the hour) source of the degenerate tasks below.
47
+ The single payload-builder is NetSuite **UserEvent script 2493 "Create Clickup Opportunity"**
48
+ (`customscript2493`, deployment `customdeploy1`), which fires `afterSubmit` when the Opportunity
49
+ "Presales Lead" field (`custbody_ctc_solution_architect`, employee ref) transitions **empty→set**.
50
+ Its output depends entirely on the trigger **context** (because it reads field values off
51
+ `context.newRecord`):
52
+ - **Full-record contexts** form save (`create`/`edit`), and REST/SOAP API edits (NetSuite context
53
+ `RWS`/`WS`, e.g. the `touch_opportunity_presales_lead.php` tool) — carry the whole record →
54
+ `tranid`/entity/title are present → **correct** name. Verified 2026-06-15: touching opp 5483750 via
55
+ REST produced `60533 — 560 Endeavor Health — Data Center Facility Consolidation Services`.
56
+ - **Partial-record `xedit`** — `submitFields()`, inline-grid edits, mass updates — put only the
57
+ changed field on `context.newRecord` → `tranid`/entity/title read empty → 2493 falls back to
58
+ `name = "OPP " + record.id` with empty descriptive/custom fields → the degenerate stub.
59
+
60
+ The degenerate trigger was **scheduled script 2412 "Update Opp Status / % to Close / Presales Lead"**
61
+ (deployment `customdeploy_update_opp_status_morning`, status SCHEDULED). It auto-assigns the Presales
62
+ Lead (NetSuite context `SCH`, user `-System-`) — originally via `submitFields`, which fired 2493 in
63
+ `xedit` → stub. **Fixed 2026-06-15:** 2412 now does a full `record.load()`/`setValue()`/`save()`, so
64
+ the assignment fires 2493 in `edit` (full-record) context → proper name (see Gotchas → Fix).
53
65
 
54
66
  ## Data model
55
67
  - ClickUp Opportunities list `901111987449`; custom-item id `1009`.
@@ -62,18 +74,38 @@ There are (at least) two NetSuite-side triggers feeding this one webhook:
62
74
  None — single internal Agilant/True integration.
63
75
 
64
76
  ## Gotchas / known issues
65
- - **`OPP <internalId>` degenerate tasks (root cause).** Occasionally a task is named just
66
- `OPP 7147699` with no description/fields. The name is built entirely by NetSuite; TOGA relays it.
67
- The opportunities involved are **fully populated in NetSuite** (verified via SuiteQL `OPP 7147699`
68
- is internal id 7147699 = tranid 74197, "7904 Froedtert South", "Seal Shield Opportunity Assessment"),
69
- so this is **not** missing data and **not** a TOGA bug a NetSuite-side process sent a fallback
70
- payload (`name = "OPP " + record.id`, descriptive fields empty) without loading `tranid`/`entity`/
71
- `title`. The four observed (Jun 2026) all created at ~:01 past the hour by the dfranks token →
72
- a NetSuite **scheduled** script, distinct from the working real-time presales-lead trigger
73
- (~3% failure: 4 degenerate vs 122 correct from the same token). Fix is NetSuite-side
74
- (load the record / saved-search columns before building the name); the offending script lives in
75
- the NetSuite File Cabinet, not this repo. Optional TOGA backstop: in `createTask()`, if `name` is
76
- empty or matches `^OPP \d+$`, look the opp up by internal id and rebuild the name before POSTing.
77
+ - **`OPP <internalId>` degenerate tasks (root-caused & confirmed 2026-06-15).** A task named just
78
+ `OPP 7147699` with no description/fields. The name is built by NetSuite and relayed verbatim;
79
+ the opps are **fully populated** in NetSuite (so it's not missing data, not a TOGA bug). Cause:
80
+ scheduled script **2412** sets the Presales Lead via `submitFields` (empty→set) fires UserEvent
81
+ **2493** in `xedit` context 2493 reads `tranid`/entity/title off the partial `newRecord` (empty)
82
+ `name = "OPP " + id`. Confirmed via `systemnote`: each of the 4 stubs (Jun 12–13) was preceded
83
+ ~1–2s by a `context=SCH`, user `-System-`, `CUSTBODY_CTC_SOLUTION_ARCHITECT` set (to Cory Martin,
84
+ employee 38886). **Regression date:** script 2412's source file
85
+ (`Update_Opportunity_Status_Percent_to_Close.js`, file id 5182695) was last modified **2026-06-01**,
86
+ and **zero** stubs existed in the prior ~14 months (full ClickUp sweep, Apr 2025→Jun 12 2026) the
87
+ June 1 edit added the Presales-Lead auto-assignment. **Precise failure condition:** only the
88
+ *empty→set* transition fires it a scheduled set to an already-present lead is a no-op (confirmed
89
+ on opp 5483750, where a 07:02 `SCH` set produced no stub because the lead was already present).
90
+ - **Fix — applied in NetSuite prod 2026-06-15 (deployed; pending verification on next scheduled
91
+ run).** In scheduled script **2412**, the presales-lead assignment was changed from
92
+ `record.submitFields({type, id, values:{custbody_ctc_solution_architect: employeeId}, ...})` to a
93
+ full `record.load()` → `setValue('custbody_ctc_solution_architect', employeeId)` →
94
+ `record.save({ignoreMandatoryFields:true})`. A full `save()` fires 2493 with `context.type='edit'`
95
+ and the **complete** `newRecord`, so it reads `tranid`/entity/title and builds a proper name; the
96
+ empty→set guard still works (`oldRecord` reflects the pre-save state). **Verify** by watching the
97
+ next `SCH` empty→set run for a properly-named task (not `OPP <id>`) — not yet observed against a
98
+ live scheduled run as of the fix.
99
+ - **Residual exposure:** 2493 still reads off `context.newRecord`, so *other* `xedit`-context
100
+ presales-lead sets (inline-grid edit, Mass Update, any other script's `submitFields`) would still
101
+ stub. The 2412 fix closes the actual regression, not the latent 2493 hole. Belt-and-suspenders =
102
+ add a `record.load()`/`lookupFields` fallback **inside 2493** too (not done).
103
+ - **Watch-outs from the full save:** governance per opp rose ~2.5× (`load` ~5 + `save` ~20 units vs
104
+ `submitFields` ~10) — guard batch runs against the governance ceiling (yield/reschedule); and a
105
+ full `save()` re-fires **all** `edit`-context UserEvents/workflows on the opp (Quosal, LSA, etc.),
106
+ not just 2493.
107
+ - **TOGA backstop (optional, not done):** in `createTask()`, if `name` is empty or matches
108
+ `^OPP \d+$`, look the opp up by internal id and rebuild the name before POSTing.
77
109
  - **No inbound logging.** `Webhook_NetSuite::post()` has a `// Log payload to file` comment but does
78
110
  **nothing** — the raw NetSuite payload is persisted nowhere, which is why this was hard to trace.
79
111
  Adding real request logging here would make recurrences diagnosable immediately.
@@ -83,7 +115,30 @@ None — single internal Agilant/True integration.
83
115
  - Handler is triplicated; a fix must be applied to the live `webhook/` copy (and ideally the others
84
116
  to avoid drift).
85
117
 
118
+ ## Remediation (re-create a proper task for an existing stub)
119
+ Because the integration is create-only, an `OPP <id>` stub can't be "fixed" in place — you
120
+ re-fire 2493 from a full-record context, which creates a correctly-named task alongside the stub
121
+ (then delete the stub). Run the touch tool with the opp internal id(s):
122
+
123
+ ```
124
+ php test/@dave/touch_opportunity_presales_lead.php <id1>,<id2>,...
125
+ ```
126
+
127
+ It blanks then restores `custbody_ctc_solution_architect` via REST (`RWS`/full-edit), firing 2493
128
+ with the full record → proper `<tranid> — <custNum> <customer> — <title>` task ~1–2s later.
129
+ **Leaves the original stub** — delete the `OPP <id>` task in ClickUp afterward.
130
+ Done 2026-06-15 for opps 7147699/7147698/7147472/6382879 → 4 proper tasks created; the 4 stubs
131
+ (task ids `868k0tr41`, `868k0tr16`, `868k0rwtp`, `868k0h44m`) were archived in ClickUp afterward.
132
+
86
133
  ## Change history
134
+ - 2026-06-15 — **Fix deployed in NetSuite prod:** scheduled script 2412's presales-lead assignment
135
+ switched from `submitFields` to full `load`/`setValue`/`save` → fires 2493 in `edit` (full-record)
136
+ context → proper names. Residual: 2493 still latently `xedit`-vulnerable to other triggers; watch
137
+ governance (~2.5×) and other UserEvents re-firing on the full save. Pending live-run verification. (dfranks)
138
+ - 2026-06-15 — Identified the exact NetSuite culprits (scheduled **2412** `submitFields`→`xedit`→
139
+ UserEvent **2493**), regression date (script 2412 modified 2026-06-01; no stubs before Jun 12),
140
+ and `systemnote`/`SCH` confirmation; added remediation-via-touch procedure and fix-option
141
+ trade-offs (`record.load` vs skip-`xedit`). (dfranks)
87
142
  - 2026-06-15 — Documented integration + root-caused `OPP <internalId>` degenerate tasks to a NetSuite
88
143
  scheduled (~:01 hourly) fallback payload; confirmed live receiver is `webhook/`, not api2/worker2;
89
144
  noted no-op inbound logging and the Opp#/Cust# swap. (dfranks)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.85",
3
+ "version": "1.0.87",
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",