toga-ai 1.0.915 → 1.0.917

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: nycdoe
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-25
9
+ updated: 2026-09-29
10
10
  owners: [mhammontree]
11
11
  files:
12
12
  - worker2/Worker/Sftp/Import.php
@@ -17,6 +17,7 @@ files:
17
17
  - dbchanges2/Client_Nycdoe/2026-09-22b - Add ASN parsing fields to ServiceRequests.sql
18
18
  - dbchanges2/Client_Nycdoe/2026-09-22c - Add customerPurchaseOrder field to Cases.sql
19
19
  - worker2/tests/Integration/ParseCest.php
20
+ - dbchanges2/Core/2026-09-14a - Add SFTP Import cron - NYCDOE Lenovo.sql
20
21
  related:
21
22
  - servicenow-integration.md
22
23
  - ../profile.md
@@ -46,7 +47,21 @@ The 2.0 replacement for 1.0's `legacy_import_asn.php` + `asnprocessor/*` (see [S
46
47
  | Acer | CDW 47-col, 3 names differ from Lexmark | `ACER_HEADER` | `YYYY/MM/DD` |
47
48
  | Apple | own | `APPLE_HEADER` | `order_date` `M/D/YY` or `M/D/YYYY` |
48
49
 
49
- Schema (`dbchanges2/Client_Nycdoe/`): `2026-09-14a` Files `c_status`/`c_manufacturer`/`c_dtProcessed`; `2026-09-22a` ServiceRequestStages seed (New/Scheduled/On Hold/Delivered/Cancelled); `2026-09-22b` ServiceRequests fields; `2026-09-22c` Cases `c_customerPurchaseOrder`; `2026-09-25a` Cases `longDescription`.
50
+ ### Activation / cutover (NOT built — design under review)
51
+ - **`Sftp/Import/Run` is read-only on the SFTP** (`nlist`/`stat`/`get` only). 1.0 still deletes each file within minutes and runs `*/5` vs 2.0 `*/15`, so a naive 2.0 shadow run sees a random subset. See 1.0 Stage 1b in [ServiceNow / ASN Integration](servicenow-integration.md).
52
+ - **DECIDED (Mark, 2026-09-25/29): 1.0 must not delete a file until 2.0 has imported and parsed it.** Replaces the 2026-08-28 "copy to temp subfolder" bridge idea.
53
+ - **The 4 `dbchanges2/Core/2026-09-14a - Add SFTP Import cron - NYCDOE <Mfr>` rows ship `isActive=0`** (ordered rollout); they stay off through shadow mode.
54
+ - **PROPOSED design (2026-09-29, for review with Jeff; needs a fresh `cto` pass — the 2026-09-25 CTO agreement was for an earlier polling variant):**
55
+ 1. 1.0 replaces its `DeleteSftpFile` runTask with `Sftp/Import/RunForFile(clientId, username, filePath)` — no password in SQS.
56
+ 2. `RunForFile` loads **all** `Sftp/Import/Run` CronJobs rows for clientId+username (the CDW username returns both Lexmark and Acer rows — never trust a manufacturer from 1.0), matches `fnmatch` patterns (no match → delete directly, old behavior), fetches, saves `Files`, queues Parse with `deleteSourceOnSuccess=true`.
57
+ 3. Parse on success queues new `Sftp/Import/DeleteSource(clientId, filesUuid)`, which re-checks `c_status` (`succeeded`/`released`) **and** the remote file's sha256 vs `Files.checksum` before deleting.
58
+ 4. Checksum already in `Files`: `succeeded` → delete; stale `pending` → re-queue Parse; `failed` → keep the file.
59
+ 5. 1.0 skips files already in `Logs.FileLog` (job+fileName+fileTimestamp) but still re-queues `RunForFile`.
60
+ 6. Kill switch `[nycdoe] asn_delete_mode` = `gated` | `immediate`; missing = `immediate`.
61
+ 7. Cutover: turn the CronJobs rows on with `deleteSourceOnSuccess`, remove the 1.0 processors.
62
+ 8. Add `released` to `Files.c_status` for manual release of failed files.
63
+
64
+ Schema (`dbchanges2/Client_Nycdoe/`): `2026-09-14a` Files `c_status` (ENUM `pending`/`processing`/`succeeded`/`failed`, not shipped)/`c_manufacturer`/`c_dtProcessed`; `2026-09-22a` ServiceRequestStages seed (New/Scheduled/On Hold/Delivered/Cancelled); `2026-09-22b` ServiceRequests fields; `2026-09-22c` Cases `c_customerPurchaseOrder`; `2026-09-25a` Cases `longDescription`.
50
65
 
51
66
  Tests: `worker2/tests/Integration/ParseCest.php` (all 4 mfrs + header rejection + comma repair) — see [worker2 Codeception integration tests](../../../2.0/apps/worker2/features/codeception-integration-tests.md).
52
67
 
@@ -60,4 +75,5 @@ Tests: `worker2/tests/Integration/ParseCest.php` (all 4 mfrs + header rejection
60
75
  - **Schema drift: `_Model_Client_Case` declares `longDescription` but `Client_Nycdoe.Cases` lacked the column** → any direct-model Case load/save 1054s. Fixed for DOE (`2026-09-25a`). Other tenants' `Cases` may have the same gap — unchecked (ask Rohan).
61
76
 
62
77
  ## Change history
78
+ - 2026-09-29 — Activation plan: gated-delete decision, proposed RunForFile/DeleteSource design, CronJobs rows set isActive=0 (mhammontree)
63
79
  - 2026-09-25 — Phase 2 parsing built for all 4 mfrs; header validation, comma repair, real-sample header/date corrections; integration tests added (mhammontree)
@@ -38,6 +38,7 @@ files:
38
38
  - dbchanges/Core/SK/
39
39
  - dbchanges/TOGaDeskSupport/SK/
40
40
  - worker2/Worker/Monitor/Nycdoe.php
41
+ - worker2/Worker/Utilities/Sftp/DeleteFile.php
41
42
  related:
42
43
  - ../profile.md
43
44
  - edi-850-receive-and-997-ack.md
@@ -110,6 +111,10 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
110
111
 
111
112
  - **Stage 1a (API):** vendor detected by string match on `u_asset_mfg` (apple=1, acer=2, lenovo=3, lexmark=4; unknown → Lexmark). Serial 'S'-prefix stripped for Lexmark/Lenovo. Ship-to resolved via `App_Api_NYCDOEV2::resolveShippingAddress()`; on failure a tracking `NYCDOETicket` is recorded but **ASN creation is skipped**. `dedupeKey = vendorId|PO|DOE-part|serialNumber`. Dedup is SELECT-before-INSERT with per-row try/catch (unique index is the race backstop).
112
113
  - **Stage 1b (SFTP):** the only source of **non-serialized** items (in practice the Lexmark feed). Serial-less lines of the same PO+part are summed per file into one queue row with `dedupeKey = vendorId|PO|DOE-part|<file token>`. Dedup is INSERT + catch "Duplicate entry" (race-safe).
114
+ - **Per file, in order** (`App_ASNProcessor_{Lenovo,Apple,Lexmark}::processFiles()`): read over plain FTP (`App_FTP`: `ftp_connect`/`ftp_rawlist`/`ftp_get`) → save legacy `Logs.FileLog` (jobs `ASN_LENOVO` / `ASN_APPLE` / `ASN_LEXMARK`) → parse → cache to disk → email → queue `App_Worker::runTask('Utilities/Sftp/DeleteFile/DeleteSftpFile')` (`lenovo.php:176-184`, `apple.php:105-113`, `lexmark.php:102-110`). worker2 `Worker/Utilities/Sftp/DeleteFile.php` then deletes the file over SFTP (phpseclib3). ⚠ The SFTP password travels **inside the SQS message** (source: hardcoded constants in `asnprocessor/*.php`) — see the credentials gotcha.
115
+ - **Delete is gated only on the disk-cache write** (`$filePutContentsResults !== false`), **not on a good parse** — a bad file is still deleted within minutes, and `Logs.FileLog` is the only copy left.
116
+ - **No Acer processor runs.** `asnprocessor/acer.php` is dead code (not called from `legacy_import_asn.php`, never appends to `$output`, never deletes). Acer files do arrive — via CDW on the shared CDW/Lexmark SFTP account — and Lexmark `processFiles()` has **no file-name filter**, so 1.0 processes every Acer file **as Lexmark** (Lexmark vendorId). Proof: 458 `ASN_LEXMARK` FileLog rows with `acer` in `fileName` (2025-08-18 → 2026-09-25), 0 `ASN_ACER`. Any 1.0-vs-2.0 count must compare 1.0 Lexmark against 2.0 Lexmark + Acer.
117
+ - **Vendors upload and forget** — they never re-send or notice if a file sits on the SFTP (Mark, 2026-09-25). A deleted-but-unparsed file is gone unless recovered from `Logs.FileLog`.
113
118
  - **Stage 2:** queue → ASN/Items/Units/TrackingNumbers in a transaction. Serialized: one Unit per serial. Non-serialized: the file's quantity is a **cumulative** total — `unsent item qtyOrder = cumulative − SUM(committed qtyOrder)`; post-freeze growth becomes a **fresh item row**, a decrease fires a discrepancy email and changes nothing. Exactly one Unit per serial-less item row regardless of quantity. Failed rows retry 5× then alert `devteam@goagilant.com`.
114
119
  - **Stage 3 (THE FREEZE):** creates the NetSuite SO and stamps `AdvanceShippingNoticeItems.netSuiteInternalSalesOrderId`. **Once stamped, that item's `qtyOrder` is immutable** — there is no update path to a sent SO line; growth must be a new item row → new SO line.
115
120
  - **Stage 4:** reconciles the NetSuite **PO** (`netSuiteInternalPurchaseOrderId` — not the SO id) and sends serial inventory assignments. Already multi-PO aware (`listPurchaseOrdersCreatedFromSalesOrder`, groups items by PO, stamps each item's `netSuiteInternalPurchaseOrderId`) **but only stamps items where the PO is NULL and never re-stamps** — so an item stamped to PO-A but later *received* on PO-B is never corrected. **TOGA does NOT create NetSuite POs anywhere in the DOE flow.** Stage 3 creates only the SO (and, when needed, a special-order inventory Item); POs are generated inside NetSuite by procurement/special-order. A single SO can therefore be fulfilled across **multiple POs** (back-orders, multi-vendor, re-cut lines) — a legitimate NetSuite outcome, so the durable fix lives downstream (resolve receipts SO-wide), not in preventing PO splits.
@@ -138,6 +143,7 @@ The 1.0 worker cannot run on a dev/Windows box; use the toga DB MCP + `Logs.API`
138
143
  - **NetSuite SOAP request/response payloads** are logged in legacy `Logs.API` — filter `sourceJob LIKE '%3_create_installation_ticket%'`; `requestPayload` contains the PO internalId. (The multi-PO case was verified this way: the receipt search for PO 6971859 returned `totalRecords=1`, only `38S0500`.)
139
144
  - **`Bridge_NetSuite`** (legacy) has `SalesOrders` / `SalesOrderItems` / `SerialNumbers` but **no `PurchaseOrders` table** — POs are pulled live from the NetSuite API.
140
145
  - **Stuck-vs-received check, per ASN item:** compare `listItemReceiptsCreatedFromSalesOrder($stampedSO)` against `listItemReceiptsCreateFromPurchaseOrder($stampedPO)`, dumping each receipt line's `itemName` plus its `inventoryAssignment` serials. If the SO-wide call returns 0 but the stamped PO returns the receipt with the matching serials, it is the cross-wired-stamp regression below.
146
+ - **Legacy `Logs.FileLog` (~47k rows) has no `fileName` index** — only `PRIMARY(id)`, `FileLog_job(job)`, `dtStamp`. Filter by `job` first (uses `FileLog_job`; `ASN_LENOVO` 1,899 / `ASN_LEXMARK` 954 / `ASN_APPLE` 113 rows as of 2026-09-25), then `fileName`. Select `id` only — never `fileData` (longblob) in a scan.
141
147
  - **`App_Model_Core_AdvanceShippingNotice` does NOT map `dtCreatedInstallationTicket`** — its `__get` throws "No field or quick query defined for key …". Read that column via **raw SQL**, not the model.
142
148
 
143
149
  ### Monitoring the ServiceNow endpoint (worker2 liveness probe — TRUE-80587)
@@ -157,7 +163,7 @@ All **live** DOE business logic is 1.0. The only other 2.0 code is the in-progre
157
163
 
158
164
  ## Gotchas
159
165
 
160
- - **⚠ SECURITY (cso review 2026-09-18 — NOT fixed, needs its own ticket): live production ServiceNow credentials are hardcoded in committed PHP.** `library/app/api/nycdoev2.php` and `library/app/api/nycdoe.php` hold the ServiceNow **`clientSecret` and `refreshToken` as literals** (stage values too). A test-repo diagnostic script (committed 2026-06-18) embeds the same prod credential — one rotation covers both. Treat as compromised: move them to config/env and **rotate with DOE**. Separately, `Vision_Log.ErrorLog` rows dump `$_SERVER` including live AWS access key id / secret in plain text — its own ticket, same rotation treatment. **No credential values are recorded here or anywhere in the KB.**
166
+ - **⚠ SECURITY (cso review 2026-09-18 — NOT fixed, needs its own ticket): live production ServiceNow credentials are hardcoded in committed PHP.** `library/app/api/nycdoev2.php` and `library/app/api/nycdoe.php` hold the ServiceNow **`clientSecret` and `refreshToken` as literals** (stage values too). A test-repo diagnostic script (committed 2026-06-18) embeds the same prod credential — one rotation covers both. Treat as compromised: move them to config/env and **rotate with DOE**. Separately, `Vision_Log.ErrorLog` rows dump `$_SERVER` including live AWS access key id / secret in plain text — its own ticket, same rotation treatment. Vendor FTP/SFTP credentials are also hardcoded constants in `library/app/asnprocessor/*.php`, and Stage 1b sends the SFTP password in plain SQS message bodies to worker2's delete task. **No credential values are recorded here or anywhere in the KB.**
161
167
 
162
168
  - **⚠ OPEN BUG / NAMED PATTERN — "Lenovo Off-Layout ASN File" (silent column shift + unit collapse).** Investigated 2026-08-11 (read-only; **no code fix exists**). Lenovo sends NYCDOE ASNs in **two different column layouts**, and `library/app/asnprocessor/lenovo.php` picks its column mapping by **column COUNT with no header-name validation** (`:110`, `if ($colCount >= 30`), so the off-layout file is parsed with the wrong mapping and silently mis-read.
163
169
  - **The two layouts.** The normal daily feed `DOE-Lenovo-ASN-MM-DD-YYYY.CSV` (~2 MB) has **30** columns and matches the code's mapping. A second file, `DOE-Open-Lenovo-MM-DD-YYYY.csv` (**lowercase** `.csv`, ~45 KB), has **31** columns in a **different order**: `Contact Email` moves from column 29 to column **6**, and a trailing `Full Shipment` column is appended. Everything from `Street Address` onward is therefore read **one column too early**. The "extra comma" repair heuristic at `:119-127` does not catch this (unchanged since March 2026 — commits `3442ddcf`, `6956dfbf`, `864cf073`, so not a regression).
@@ -262,6 +268,7 @@ All **live** DOE business logic is 1.0. The only other 2.0 code is the in-progre
262
268
  - Local deep-dive (full WR260236464 case study with record IDs): `doe_service_now_integration.md` in Mark's WWW workspace root; also the `service-now-integration` skill (`asn-import-flow.md`)
263
269
 
264
270
  ## Change history
271
+ - 2026-09-29 — Stage 1b file-delete path (delete not gated on parse; SFTP password rides in SQS), Acer-as-Lexmark, FileLog index facts (mhammontree)
265
272
  - 2026-09-29 — Assignment-group scoping corrected (inverted `isValidDepartmentName`, three competing group lists) with detail in hold-status-sync; `Logs.API` 30-day retention shipped. (mhammontree)
266
273
  - 2026-09-25 — TRUE-82332: cross-link to the close-clobber fix; test-repo copy of the ServiceNow credential noted (mhammontree)
267
274
  - 2026-09-25 — Corrected "no 2.0 tenant" (TRUE-80519 2.0 ASN rewrite now exists); DOE-Open-Lenovo marked retired (mhammontree)
@@ -0,0 +1,75 @@
1
+ ---
2
+ type: session
3
+ slug: TRUE_80519
4
+ title: NYCDOE 2.0 ASN activation — gate the 1.0 SFTP delete on 2.0 (design)
5
+ author: mhammontree
6
+ repos: [worker2, dbchanges2, _underscore, worker, library]
7
+ framework: "both"
8
+ client: nycdoe
9
+ status: active
10
+ created: 2026-09-29
11
+ updated: 2026-09-29
12
+ ---
13
+
14
+ # Session: TRUE_80519
15
+ **Date:** 2026-09-29 (work 2026-09-25 → 2026-09-29)
16
+ **Project/Repo:** worker2 / dbchanges2 / _underscore (2.0) + worker / library (1.0, read-only)
17
+ **Task:** Plan how to turn on the already-built NYCDOE 2.0 Phase 1 + 2 (SFTP import + parse) in production next to the live 1.0 pipeline, so 1.0 no longer deletes a vendor ASN file until 2.0 has imported and parsed it. Design only — for Mark to review with Jeff.
18
+
19
+ ---
20
+
21
+ ## What WORKED
22
+ - **Traced how 1.0 deletes vendor files (code read):** `worker/crons/sync/nycdoe/legacy_import_asn.php` (*/5) → `App_ASNProcessor_{Apple,Lexmark,Lenovo}::processFiles()` read over plain FTP (`App_FTP`), write `Logs.FileLog`, parse, cache to disk, email, then queue `App_Worker::runTask('Utilities/Sftp/DeleteFile/DeleteSftpFile', …)` — `lenovo.php:176-184`, `apple.php:105-113`, `lexmark.php:102-110`. Gated only on `$filePutContentsResults !== false` (disk cache), NOT on a good parse. `worker2/Worker/Utilities/Sftp/DeleteFile.php` deletes over SFTP. Password travels in the SQS message.
23
+ - **Confirmed 2.0 `worker2/Worker/Sftp/Import.php` `Run` is read-only on the SFTP** (only `nlist`, `stat`, `get`).
24
+ - **Acer proven via data:** legacy `Logs.FileLog` has 458 `ASN_LEXMARK` rows with `acer` in `fileName` (2025-08-18 → 2026-09-25), 0 `ASN_ACER` rows. 1.0 `acer.php` is dead code; Mark confirmed Acer files arrive through CDW (shared CDW/Lexmark account), so 1.0 processes them as Lexmark.
25
+ - **`Logs.FileLog` index check (legacy, read-only):** only `PRIMARY(id)`, `FileLog_job(job)`, `dtStamp`. EXPLAIN of a job+fileName+fileTimestamp lookup uses `FileLog_job`, ~1,899 rows for `ASN_LENOVO` (954 Lexmark, 113 Apple). Fine if it selects only `id`.
26
+ - **CTO review (2026-09-25) of the first (polling) design: AGREE** + 3 fixes (checksum match, patterns from all CronJobs rows by account, one job per run). Those ideas were carried into the rewrite.
27
+ - **4 CronJobs migrations set to `isActive = 0`** (`dbchanges2/Core/2026-09-14a - Add SFTP Import cron - NYCDOE *.sql`, line 4). Mark committed it as `ed0acb8` (2026-09-28) on `TRUE-80519`, in sync with `origin/TRUE-80519`.
28
+ - **Plan rewritten** — `C:\Users\mhammontree\.claude\plans\phase-2-asn-parsing-plan.md` §6.1–6.5 (Mark's idea: 1.0 triggers 2.0, 2.0 deletes after a good parse).
29
+ - **/capture published** KB commit `8377d069` on `origin/_main` (both NYCDOE docs updated).
30
+
31
+ ## What did NOT work — DO NOT RETRY THESE
32
+ - **"2.0 decides, 1.0 keeps asking" polling design (2026-09-25):** replaced, not wrong but worse. 1.0 re-queued a gated delete every 5 min; 2.0 had to download + sha256 every waiting file each run; delete waited on 2.0's `*/15` cron; needed a decision on whether inactive CronJobs count; rollback was a two-step change (kill switch AND crons off). Mark's trigger design removes all of these.
33
+ - **Matching the gated delete on `Files.name` or name+size:** unsafe. A same-content re-send under a new name has no `Files` row of its own (`Import.php:171` dedupes on checksum) → never deleted; a same-name re-send with new content matches the old `succeeded` row → deleted before import. Use checksum.
34
+ - **Trusting a manufacturer sent from 1.0:** 1.0 Lexmark hands over Acer files too (shared folder), so matching only the Lexmark pattern would treat an Acer file as junk and delete it. Resolve patterns by SFTP username (CDW returns Lexmark + Acer rows).
35
+ - **Earlier "1.0 copies the file to a temp SFTP subfolder" bridge (2026-08-28):** dropped — the file stays in the real vendor folder until 2.0 is done.
36
+ - **Assuming the 2026-09-14a CronJobs rows were inactive:** they were written `isActive = 1`; now `0`.
37
+
38
+ ## Not tried yet (candidates for next session)
39
+ - Fresh `cto` pass on the rewritten design (§6.2) — the 2026-09-25 AGREE was on the polling version.
40
+ - Review §6 with Jeff (Mark authors his own proposal; plan is reference material only).
41
+ - Add `'released'` to the `c_status` ENUM in `dbchanges2/Client_Nycdoe/2026-09-14a - Add SFTP import status fields to Files.sql` (not shipped yet) — for manual release of `failed` files.
42
+ - Build: `Sftp/Import/RunForFile(clientId, username, filePath)`, `Sftp/Import/DeleteSource(clientId, filesUuid)`, `deleteSourceOnSuccess` flag on the 4 `Sftp/Parse/*` methods (+ later on `Run`), `mtime` minimum-age guard in `Run`.
43
+ - 1.0 changes in `lenovo.php` / `apple.php` / `lexmark.php`: replace `DeleteSftpFile` runTask with `RunForFile`; skip files already in `Logs.FileLog` but re-queue `RunForFile`; `[nycdoe] asn_delete_mode = gated|immediate` (missing = immediate). Lint with PHP 7.2 (xampp_7_2_33).
44
+ - Codeception tests for the delete decision logic (keep it in a plain method with no SFTP calls, like `ParseCest`).
45
+ - SFTP file-age monitor (OneUptime, phpseclib3 pattern) + alert on `c_status = 'failed'`.
46
+ - Check whether `Core.WorkerJobs` stores the SFTP password from today's `DeleteSftpFile` parameters (security).
47
+ - Older open items still open: Acer/Lenovo date + comma-repair re-checks on real files; ask Rohan about other tenants' `Cases.longDescription`; INC flow redesign; Phase 3.
48
+
49
+ ## Current file state
50
+ | File | Status | Notes |
51
+ |------|--------|-------|
52
+ | `C:\Users\mhammontree\.claude\plans\phase-2-asn-parsing-plan.md` | Updated (local, not in a repo) | §6 Activation: 6.1 how 1.0 deletes, 6.2 trigger design, 6.3 9-step activation order, 6.4 risks, 6.5 cutover; change history entries 2026-09-25 + 2026-09-29 |
53
+ | `dbchanges2/Core/2026-09-14a - Add SFTP Import cron - NYCDOE {Lenovo,Apple,Lexmark,Acer}.sql` | Committed `ed0acb8`, pushed to `origin/TRUE-80519` | `isActive = 0` |
54
+ | worker2 `TRUE-80519` | Clean, in sync with origin (`70f1061`) | Phase 1/2 + Codeception committed; no gated-delete code yet |
55
+ | _underscore `TRUE-80519` | In sync with origin (`1223e2a9`) | 3 unrelated CSS files show as modified (`Selectize` scss css, `underCSS.css`) — not from this work |
56
+ | `knowledge/clients/nycdoe/features/servicenow-integration.md`, `asn-sftp-ingestion-2-0.md` | Published `8377d069` | 1.0 delete path, Acer via CDW, FileLog index, activation design (marked not built) |
57
+
58
+ ## Decisions made
59
+ - **1.0 must not delete an ASN file until 2.0 has imported and parsed it** (Mark). Rejected: shadow run racing 1.0's delete; temp-subfolder copy.
60
+ - **1.0 triggers 2.0, 2.0 deletes after a good parse** (Mark, 2026-09-29). 1.0 replaces its `DeleteSftpFile` runTask with `Sftp/Import/RunForFile` (no password in SQS); Parse queues `Sftp/Import/DeleteSource` on `succeeded`; `DeleteSource` re-checks status + remote checksum before deleting. Rejected: the polling design (see DO NOT RETRY).
61
+ - **2.0 CronJobs stay `isActive = 0` for all of shadow mode** — used only as config, so 1.0 is the only trigger and 2.0 can never delete a file 1.0 hasn't read. Accepted trade-off: 2.0's own listing (`Run`) is only exercised at cutover.
62
+ - **Patterns are resolved by SFTP username across all CronJobs rows**, never from a 1.0-supplied manufacturer (Acer via CDW).
63
+ - **Files matching no 2.0 pattern are deleted as today** (junk, retired `DOE-OPEN-LENOVO-*`).
64
+ - **Kill switch defaults to `immediate`**; rollback is one config change. 1.0 ships in `immediate` first, then flips to `gated`.
65
+ - **Cutover end state = same path**: turn crons on with `deleteSourceOnSuccess = true`, remove 1.0, delete `RunForFile` + the 1.0 bridge code.
66
+ - Vendors upload and forget — longer file lifetime on the SFTP has no impact (Mark).
67
+
68
+ ## Blockers
69
+ none — waiting on Jeff's review of §6 before building.
70
+
71
+ ## Exact next step
72
+ > Open `C:\Users\mhammontree\.claude\plans\phase-2-asn-parsing-plan.md` §6 and review it with Jeff; then run a fresh `cto` review of §6.2 (RunForFile / DeleteSource) before writing any code.
73
+
74
+ ---
75
+ _Saved by /session-save on 2026-09-29_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.915",
3
+ "version": "1.0.917",
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",