toga-ai 1.0.398 → 1.0.399

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,8 +6,8 @@ project: Worker
6
6
  client: nycdoe
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-07-07
10
- owners: [mhammontree]
9
+ updated: 2026-07-20
10
+ owners: [mhammontree, sking]
11
11
  files:
12
12
  - worker/crons/sync/nycdoe/send_ticket_updates.php
13
13
  - worker/crons/sync/nycdoe/process_tickets.php
@@ -76,29 +76,55 @@ revert or never reach ServiceNow. It is the status-sync companion to the broader
76
76
  - `Hold` has **statusRank 6**, beating `Work in Progress` (rank 5) in the downgrade-guard
77
77
  added by TRUE-76812 — so a downstream sync cannot silently demote a held order.
78
78
 
79
- ### Status field (ServiceNow side)
80
- - The SNOW **native `state` field** (human-readable: `On Hold`, `In Progress`, `Assigned`,
81
- `Resolved`, …) is the **SLA-bearing, authoritative status**. Read/write this for hold.
82
- - The custom **`u_status_task`** field is a *separate concept* with a different vocabulary;
83
- it can lag/disagree with `state` (observed: native `state="In Progress"` while
84
- `u_status_task="Open"`). **Never use `u_status_task` for hold detection.**
85
- - Numeric `state` codes seen on the wire: INC `On Hold` = **3**, `In Progress` = **2**;
86
- RITM `On Hold` = **8**, the scheduled-update state = **"-14"**.
87
-
88
- ### Outbound (TOGaDesk → ServiceNow)
89
- - `send_ticket_updates.php` (INC) pushes the hold as `state:3`. The "Assigned → In Progress"
90
- auto-start block is **guarded** so it never pushes `state:2` when the local repair order is
91
- in any `HOLD_*` status (this is what caused the self-clobber — see Change history).
92
- - `send_request_item_updates.php` (RITM) pushes `state:"8"` (On Hold) and carries a
93
- defensive `HOLD_*` guard on the scheduled-update path so it can't push `state:"-14"` while
94
- held.
79
+ ### Status fields (ServiceNow side) — CORRECTED 2026-07-20
80
+ > **The earlier "native `state` is authoritative; never use `u_status_task`" model was WRONG**
81
+ > (see Change history 2026-07-20). Both fields exist and both matter, with distinct roles:
82
+ - **`u_status_task` (custom) is the CLIENT-FACING, authoritative incident status** — the field
83
+ DOE's own ServiceNow views/reporting read (values `Open`, `In Progress`, `On Hold`, …). **A hold
84
+ MUST be written here or the client never sees it.** It is **writable from any active state**; a
85
+ controlled test PATCH of `{u_status_task:'On Hold'}` returned success and PERSISTED for days
86
+ (INC2192842 held 3+ days). **The TOGaDesk inbound sync keys off `u_status_task`**
87
+ (`process_tickets.php:206` reads `$data->u_status_task`), and the RITM cron checks
88
+ `u_status_task == 'On Hold'` (`send_request_item_updates.php:224`) — so `u_status_task` **is**
89
+ the correct field for hold detection.
90
+ - The native **`state`** field is ServiceNow's INTERNAL, SLA-bearing state machine. Setting
91
+ `state=3` (On Hold) pauses the SNOW SLA clock but is **invisible to the DOE client**. ServiceNow
92
+ only permits the native On Hold transition **FROM In Progress (`state=2`)** — pushing `state=3`
93
+ from any other state (Assigned, or already Resolved/Closed/Canceled) is silently rejected (HTTP
94
+ 200, empty body — see the empty-body gotcha). `hold_reason=10` is not a valid choice on this
95
+ instance and is stored empty/dropped, but native On Hold still sticks without it.
96
+ - Numeric `state` codes seen on the wire: INC `On Hold`=**3**, `In Progress`=**2**;
97
+ RITM `On Hold`=**8**, the scheduled-update state=**"-14"**.
98
+
99
+ ### Outbound (TOGaDesk → ServiceNow) — INC hold push (worker #1676 + #1677)
100
+ When a DOE repair order is on hold, `send_ticket_updates.php` pushes:
101
+ - **Always set `u_status_task='On Hold'`** — the client-facing field, writable from any active
102
+ state; on release set it back to `'In Progress'`. This is what actually surfaces the hold to the
103
+ client. Until the local status stops reverting (the library `qqStatus()` fix below) the outbound
104
+ never sees `orderIsOnHold=true` and never pushes `u_status_task` at all — hence the deploy
105
+ dependency.
106
+ - **Set native `state=3` (On Hold) only when the incident is currently `In Progress`** — an SLA
107
+ pause. Because ServiceNow rejects the transition from any other state (empty-body 200), the cron
108
+ **defers without advancing `dtSynced`** when the incident is neither On Hold nor In Progress, and
109
+ re-pushes only once it reaches In Progress.
110
+ - **Closed-incident reconcile:** if the incident is already `Resolved`/`Closed`/`Canceled` on the
111
+ SNOW side it can no longer be held — the cron **advances `dtSynced`** to accept the remote
112
+ closure instead of retrying forever. (A canceled incident, INC2190846, had generated **2,392**
113
+ failed empty-body PATCHes before this.)
114
+ - The "Assigned → In Progress" auto-start block is still guarded so it never pushes `state:2` over
115
+ a held order (TRUE-79922). Named constants: `SN_STATE_IN_PROGRESS` / `SN_STATE_ON_HOLD` /
116
+ `SN_HOLD_REASON_DEFAULT` in `send_ticket_updates.php`, plus `SN_TASK_STATUS_ON_HOLD` /
117
+ `SN_TASK_STATUS_IN_PROGRESS` for the `u_status_task` writes (#1677).
118
+ - `send_request_item_updates.php` (RITM) pushes `state:"8"` (On Hold) and carries a defensive
119
+ `HOLD_*` guard on the scheduled-update path so it can't push `state:"-14"` while held; its
120
+ reverse-hold logic keys off `u_status_task == 'On Hold'`.
95
121
 
96
122
  ### Inbound (ServiceNow → TOGaDesk)
97
- - `process_tickets.php` determines local status from the native **`$data->state`** field
98
- (NOT `u_status_task`): after the `u_status_task` switch — before the completion-preserve
99
- step and the `statusRank` downgrade-guard — if native `state` is `On Hold`
100
- (case-insensitive) it sets `$newStatus = STATUS_HOLD`. Both inbound "Assigned-force"
101
- `patchINC(state:2)` blocks are guarded against a local hold.
123
+ - `process_tickets.php` reads the client-facing **`u_status_task`** field
124
+ (`$status = $data->u_status_task`, L206) — **NOT** native `state` — and maps `On Hold` to the
125
+ bare **`STATUS_HOLD`** on the local repair order (this is exactly why the library `qqStatus()`
126
+ fix below had to recognize bare `HOLD`). Both inbound "Assigned-force" `patchINC(state:2)` blocks
127
+ are guarded against a local hold.
102
128
 
103
129
  ### RITM "ETA-in-hold"
104
130
  - `send_request_item_updates.php` syncs `scheduled_date` (ETA) to ServiceNow **inside the
@@ -182,9 +208,19 @@ The SNOW round-trip note reconciliation (delete-and-reinsert in `process_tickets
182
208
  auto-start fought the hold push — `state:3` then `state:2` pushed seconds apart, every run,
183
209
  so SNOW never stayed On Hold. Any future auto-start/auto-status block on these crons MUST
184
210
  short-circuit when the local RO is in a `HOLD_*` status.
185
- - **The wrong-field read (root cause #2 of TRUE-79922):** reading `u_status_task` instead of
186
- native `state` meant a hold set on the SNOW side never reached TOGaDesk. Hold detection is
187
- on native `state` only.
211
+ - **⚠ SUPERSEDED (see Change history 2026-07-20):** TRUE-79922 originally concluded that reading
212
+ `u_status_task` (instead of native `state`) was a bug and that hold detection should be on native
213
+ `state` only. That was **backwards.** `u_status_task` is the client-facing authoritative field
214
+ and **is** the correct hold-detection field — the live inbound cron reads it at
215
+ `process_tickets.php:206`. Native `state` is the internal SLA state, pushed only as a secondary
216
+ pause when the incident is In Progress.
217
+ - **The empty-body 200 (nycd3 `data/tasks` PATCH):** `App_Api_NYCDOEV2::patchINC`/`patchRITM`
218
+ (both hit `/api/nycd3/data/tasks/{number}`) return **HTTP 200 with an EMPTY body (no `result`
219
+ object)** when a requested native `state` transition is illegal — e.g. pushing `state=3` from a
220
+ non-In-Progress state. This is silent: no exception, no error field. A canceled incident
221
+ (INC2190846) generated **2,392** such failed PATCHes before the closed-incident reconcile was
222
+ added. Gate native `state=3` on `snState == 'In Progress'`, and reconcile
223
+ Resolved/Closed/Canceled by advancing `dtSynced`.
188
224
  - Holds are **never auto-released** — if a SNOW user manually changes status, the outbound
189
225
  cron correctly re-asserts On Hold; that is intended, not a bug. Clear the hold in TOGaDesk.
190
226
  - **Diagnosing which writer dropped a hold:** `repair_order_history`
@@ -217,6 +253,25 @@ The SNOW round-trip note reconciliation (delete-and-reinsert in `process_tickets
217
253
  `repair_orders.status` directly cannot distinguish the six hold variants after a recompute.
218
254
 
219
255
  ## Change history
256
+ - 2026-07-20 — **Corrected the authoritative-field model and shipped the INC hold push.**
257
+ Controlled test PATCHes proved the earlier "native `state` is authoritative; never use
258
+ `u_status_task`" conclusion (TRUE-79922) **backwards**: the custom **`u_status_task`** is the
259
+ client-facing authoritative incident status (a `{u_status_task:'On Hold'}` PATCH persisted 3+
260
+ days, INC2192842), the inbound cron reads it (`process_tickets.php:206`), and the RITM cron keys
261
+ off it (L224). Native `state` is the internal SLA state and only accepts On Hold **from In
262
+ Progress** — any other transition returns a silent HTTP-200 empty body (a canceled incident,
263
+ INC2190846, had racked up 2,392 failed PATCHes). Fix: outbound `send_ticket_updates.php` now
264
+ always writes `u_status_task='On Hold'` (client-facing; `'In Progress'` on release), sets native
265
+ `state=3` only when In Progress (SLA pause), and reconciles already-closed incidents by advancing
266
+ `dtSynced` instead of retrying forever; added `SN_STATE_*` / `SN_HOLD_REASON_DEFAULT` /
267
+ `SN_TASK_STATUS_*` constants. worker PRs **#1676** (merged — retry-storm/closed reconcile + native
268
+ state gate) and **#1677** (open — `u_status_task` write). (sking)
269
+ - 2026-07-20 — library **#845**: `App_Model_TogaDesk_RepairOrder::qqStatus()` now recognizes all
270
+ six `HOLD_*` constants in both "on hold" IN-clauses. The inbound sync writes SNOW On Hold as the
271
+ bare `STATUS_HOLD` ('HOLD'), which the prior 2-of-6 match dropped, silently recomputing the order
272
+ to `ORDER_ASSIGNED_AWAITING_SCHEDULING` (the local "hold revert"). Additive only. Must ship with/
273
+ before worker #1677 — until the local status stops reverting, the outbound never sees
274
+ `orderIsOnHold=true`. (sking)
220
275
  - 2026-07-07 — TRUE-80060 (togadesk portion): closed the **third** DOE hold-failure path — a
221
276
  hold set via a note/comment (`Repair::addNotes`) never reaching SNOW. `addNotes` recorded
222
277
  status only on `repair_order_notes.newstatus` (never the `repair_orders.status` cache the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.398",
3
+ "version": "1.0.399",
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",