toga-ai 1.0.891 → 1.0.893
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.
- package/knowledge/2.0/apps/_underscore/features/cli-script-bootstrap.md +8 -2
- package/knowledge/2.0/apps/_underscore/features/item-classification-itemclasses.md +8 -3
- package/knowledge/2.0/apps/_underscore/features/model-magic-field-access.md +9 -4
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/codeception-integration-tests.md +51 -0
- package/knowledge/2.0/standards/backend-testing.md +7 -5
- package/knowledge/2.0/standards/framework-rules.md +18 -12
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/nycdoe/INDEX.md +2 -1
- package/knowledge/clients/nycdoe/features/asn-sftp-ingestion-2-0.md +63 -0
- package/knowledge/clients/nycdoe/features/servicenow-integration.md +8 -3
- package/knowledge/clients/nycdoe/profile.md +9 -5
- package/package.json +1 -1
- package/rules/common/git-workflow.md +19 -0
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-09-25
|
|
10
|
+
owners: ["bala", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Database.php
|
|
13
13
|
- _underscore/Environment.php
|
|
@@ -53,6 +53,9 @@ For DB-**free** unit tests of interceptor logic, use the PHPUnit scaffold instea
|
|
|
53
53
|
raised. This is the CLI face of the lazy-transaction behavior documented on
|
|
54
54
|
`_underscore/architecture.md` — `_Database::register()` starts a lazy transaction that MySQL rolls
|
|
55
55
|
back when the connection closes uncommitted.
|
|
56
|
+
- **Keep the CWD at the app root for the whole run, not just the `require`.** `Loader::autoloader`
|
|
57
|
+
resolves project classes (e.g. `_Worker_Sftp_Parse`) against `./` lazily at first use, so a
|
|
58
|
+
harness that `chdir`s back after booting breaks autoloading on the first call into app code.
|
|
56
59
|
- **`ENVIRONMENT` must be set before the framework loads**, not after — `_Environment::initialize`
|
|
57
60
|
fatals on a missing value rather than defaulting.
|
|
58
61
|
- **Local MySQL and PROD both run `sql_mode = ''`**, so `ONLY_FULL_GROUP_BY` is **not** enforced in
|
|
@@ -74,3 +77,6 @@ For DB-**free** unit tests of interceptor logic, use the PHPUnit scaffold instea
|
|
|
74
77
|
- [Client Email Template Sending](./email-template-sending.md) — its local test-harness section.
|
|
75
78
|
- [French (fr-CA) Order Email Localization (Compass Canada)](../../../clients/compass-canada/features/french-order-email-localization.md)
|
|
76
79
|
— the work these traps surfaced in.
|
|
80
|
+
|
|
81
|
+
## Change history
|
|
82
|
+
- 2026-09-25 — Added lazy-CWD autoloader gotcha (found building the worker2 Codeception harness) (mhammontree)
|
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: ["rgirish"]
|
|
9
|
+
updated: 2026-09-25
|
|
10
|
+
owners: ["rgirish", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/ItemClass.php
|
|
13
13
|
- _underscore/Model/Client/Item.php
|
|
@@ -120,7 +120,9 @@ Queried live via SuiteQL (2026-09-11): **311 classifications**, columns
|
|
|
120
120
|
- **A hierarchy path column must be `TEXT`, not `varchar(512)`.** NetSuite `fullname` values are
|
|
121
121
|
long, and the 2.0 standard caps varchar at 255.
|
|
122
122
|
- **The table is already `utf8mb4_0900_ai_ci`**, so new columns need no explicit `COLLATE`.
|
|
123
|
-
- **`c_` custom fields are NOT declared as properties on the 2.0 Model class
|
|
123
|
+
- **`c_` custom fields are NOT declared as properties on the 2.0 Model class** when written
|
|
124
|
+
through the /v2 API (a direct `_Model` write does need a declaration — see
|
|
125
|
+
[_Model magic-field access](model-magic-field-access.md)). The API engine
|
|
124
126
|
reads them from `CustomRecordFields` (`ItemClasses` is `Core.Records` id **105**). Confirmed:
|
|
125
127
|
no `public $c_netsuite*` property exists on any `Model/Client` class. Copy the pattern in
|
|
126
128
|
`dbchanges2/_modules/netsuite/2026-08-18 - TransferOrdersNetsuiteStatus.sql`.
|
|
@@ -136,3 +138,6 @@ Queried live via SuiteQL (2026-09-11): **311 classifications**, columns
|
|
|
136
138
|
- [Prudential ItemClasses device-tier data](../../../../clients/prudential/features/itemclasses-device-tier-data.md)
|
|
137
139
|
- [Client schema-drift audit](../../dbchanges2/workflows/client-schema-drift-audit.md)
|
|
138
140
|
- [NetSuite item-class sync (1.0)](../../../../1.0/apps/library/features/netsuite-item-class-sync.md)
|
|
141
|
+
|
|
142
|
+
## Change history
|
|
143
|
+
- 2026-09-25 — Scoped the "c_ not declared" note to /v2 API writes (mhammontree)
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-25
|
|
10
10
|
owners: ["jcardinal", "mhammontree", "apeterson"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Core/Model.php
|
|
@@ -79,9 +79,11 @@ class does not declare. Two things follow immediately, and they cut the search s
|
|
|
79
79
|
|
|
80
80
|
1. **It is never a schema problem.** No SQL ran. Do not check `information_schema`, do not suspect a
|
|
81
81
|
missing migration on *that* column — check the model class.
|
|
82
|
-
2. **
|
|
83
|
-
|
|
84
|
-
declare the property on the model
|
|
82
|
+
2. **It can be a `c_` column too.** The /v2 API engine handles `c_` columns dynamically, but
|
|
83
|
+
`_Model` itself does not: a direct model read/write (`__get`/`__set`) of an undeclared `c_`
|
|
84
|
+
field throws this same message. The fix is binary: declare the property on the model (a
|
|
85
|
+
client override such as `_Model_Nycdoe_ServiceRequest`, or a `_Trait_Netsuite_*`), or stop
|
|
86
|
+
using it.
|
|
85
87
|
|
|
86
88
|
**The trap that produces it: two similarly-named models, one of which lacks the column.** Swapping
|
|
87
89
|
one for another at a call site **lints clean and only dies at runtime** — nothing in PHP or CI
|
|
@@ -135,3 +137,6 @@ wrong: **1054** = the database is behind the model (see
|
|
|
135
137
|
- [Surface FIELD ↔ RecordField binding](./surface-field-recordfield-binding.md)
|
|
136
138
|
- [Framework branch ahead of schema](../../dbchanges2/workflows/framework-branch-ahead-of-schema.md)
|
|
137
139
|
- [Rate subscription cancellation](../../../../clients/rate/features/subscription-cancellation.md)
|
|
140
|
+
|
|
141
|
+
## Change history
|
|
142
|
+
- 2026-09-25 — Corrected: undeclared `c_` fields also throw on direct `_Model` access; dynamic `c_` handling is the /v2 API engine only (mhammontree)
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
| [ClickUp Task Description Fluffer (Talos KB Investigation)](features/clickup-task-fluffer-talos.md) | Async worker that "fluffs" a new ClickUp task by running its name+description through the Talos `dev-core` KB agent and writing the report back; open for the fl |
|
|
20
20
|
| [ClickUp Webhook Health Monitor → OneUptime (Monitor/Clickup/CheckWebhook)](features/clickup-webhook-health-monitor.md) | worker2 cron that checks the team's ClickUp webhook subscriptions and pages via a OneUptime Incoming-Request monitor; open when tuning the ClickUp webhook alarm |
|
|
21
21
|
| [ClickUp Work Type Automation (Committed / Conditional / Stretch)](features/clickup-work-type-automation.md) | Auto-maintains each ClickUp task's Work Type (Committed/Conditional/Stretch) from its dependencies, status, and due date; open when work-type promotion/demotion |
|
|
22
|
+
| [worker2 Codeception integration tests (2.0 test-framework pilot)](features/codeception-integration-tests.md) | First 2.0 app-level automated test suite: Codeception 5 / PHPUnit 11 in worker2 (pilot, Jeff's direction), real-DB integration tests with hard local-only safety |
|
|
22
23
|
| [worker2 — Committed Secrets & Config Exposure (debt register)](features/committed-secrets-and-config-debt.md) | Standing list of credentials committed to the worker2 repo (locations only); open before adding a credential to worker2 or assuming a config value is private. |
|
|
23
24
|
| [Compass VIP Support Importer (worker2)](features/compass-vip-support-importer.md) | worker2 action that ingests Compass's quarterly VIP spreadsheet and sets each VIP's support tech (`Users.c_supportedByUserId` in `Client_Compass`); open when wo |
|
|
24
25
|
| [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: worker2 Codeception integration tests (2.0 test-framework pilot)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-25
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/codeception.yml
|
|
13
|
+
- worker2/tests/Integration.suite.yml
|
|
14
|
+
- worker2/tests/Support/Helper/Integration.php
|
|
15
|
+
- worker2/tests/Integration/ParseCest.php
|
|
16
|
+
- worker2/tests/local.env.php.example
|
|
17
|
+
- worker2/composer.json
|
|
18
|
+
- worker2/.gitattributes
|
|
19
|
+
- worker2/.htaccess
|
|
20
|
+
related:
|
|
21
|
+
- ../../../standards/backend-testing.md
|
|
22
|
+
- ../../_underscore/features/cli-script-bootstrap.md
|
|
23
|
+
- ../../_underscore/features/model-interceptor-unit-testing.md
|
|
24
|
+
- ../../../../clients/nycdoe/features/asn-sftp-ingestion-2-0.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
First 2.0 app-level automated test suite: Codeception 5 / PHPUnit 11 in worker2 (pilot, Jeff's direction), real-DB integration tests with hard local-only safety gates; open before adding or running worker2 tests.
|
|
28
|
+
|
|
29
|
+
## Summary
|
|
30
|
+
|
|
31
|
+
worker2 is the **pilot repo** for a 2.0 test framework. One suite, `Integration` (not `unit`/`functional` — the code under test needs a real DB), boots the real `_underscore` framework and calls worker actions in-process against a **local** client DB. First coverage: `Sftp/Parse` (6 tests). **Tests are a manual/local step — never a deploy-time gate.** 1.0's pattern (`codecept.phar` + `.ebextensions/040_run_tests.config` running tests against production on every deploy) was deliberately NOT copied.
|
|
32
|
+
|
|
33
|
+
## How it works
|
|
34
|
+
|
|
35
|
+
1. **Dev-only install.** Packages are `require-dev` only (`codeception/codeception ^5`, `phpunit/phpunit ^11`, `codeception/module-asserts`). The deploy hook `.platform/hooks/postdeploy/015_install_composer.sh` runs `composer install --no-dev`, so nothing ships; the non-dev block of `composer.lock` was byte-identical before/after.
|
|
36
|
+
2. **Never deployed.** `.gitattributes` `export-ignore`s `/tests` + `/codeception.yml`; `.htaccess` denies `^tests/`.
|
|
37
|
+
3. **Per-dev environment.** Copy `tests/local.env.php.example` → `tests/local.env.php` (gitignored) returning your Apache `SetEnv ENVIRONMENT` value (e.g. `dev-<name>-laptop`). Or export `ENVIRONMENT`.
|
|
38
|
+
4. **Run** from the worker2 root with PHP 8: `vendor/bin/codecept run Integration`.
|
|
39
|
+
5. **Safety gates (run before any write):** helper refuses unless `ENVIRONMENT` starts `dev-`; `assertUsingLocalClientDatabaseOnly($clientId)` refuses unless the registered `DB_CLIENT` `hostname`/`writeHost`/`readHost` are all `127.0.0.1`/`localhost`/`::1`. Call it before every action call.
|
|
40
|
+
6. **Boot + transaction.** `_before()` boots once (same two lines as `index.php`) then calls parameterless `_Database::transactionStart()` — the call `_Route` makes for a real request. `commitDatabaseTransaction()` then really commits.
|
|
41
|
+
7. **Fixtures** mirror production field-by-field (`createFileFixture` mirrors `Import.php` `processFile()`); each test cleans up its own rows.
|
|
42
|
+
|
|
43
|
+
## Gotchas
|
|
44
|
+
|
|
45
|
+
- **Calling a worker action directly bypasses `_Route`, so there is no open transaction** — without the explicit `_Database::transactionStart()` the commit is a silent no-op and reads miss writes. Do not work around the symptom (e.g. disabling read-host connections); start the transaction. See [CLI script bootstrap](../../_underscore/features/cli-script-bootstrap.md).
|
|
46
|
+
- **Do not `chdir` back after booting.** `Loader::autoloader` resolves project classes against the CWD lazily at first use, so restoring the old CWD breaks autoloading of `_Worker_*` classes mid-test.
|
|
47
|
+
- **`_Database::$_registers` is public static** — read it directly; no Reflection.
|
|
48
|
+
- **Prove a regression test fails without the fix** before trusting it (done for the Lenovo comma repair: serial mismatch once columns shift).
|
|
49
|
+
|
|
50
|
+
## Change history
|
|
51
|
+
- 2026-09-25 — Created: Codeception/PHPUnit pilot, Integration suite for Sftp/Parse; cto-reviewed design, php-reviewer fixes to the harness (mhammontree)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: 2.0 Backend Testing (
|
|
2
|
+
title: 2.0 Backend Testing (ad-hoc client-pinned scripts; worker2 Codeception pilot)
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
project: _Underscore
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-09-
|
|
8
|
+
updated: 2026-09-25
|
|
9
9
|
owners: [mhammontree]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Query.php
|
|
@@ -14,17 +14,19 @@ files:
|
|
|
14
14
|
- _underscore/Model.php
|
|
15
15
|
- test/@Mark/Rate/verify_cancel_subscription.php
|
|
16
16
|
- test/@Mark/Rate/verify_aig_contract_identifier_persistence.php
|
|
17
|
+
- worker2/tests/Support/Helper/Integration.php
|
|
17
18
|
related:
|
|
18
19
|
- ../apps/_underscore/architecture.md
|
|
19
20
|
- ../apps/_underscore/features/per-client-database-connections.md
|
|
20
21
|
- ../apps/test/architecture.md
|
|
22
|
+
- ../apps/worker2/features/codeception-integration-tests.md
|
|
21
23
|
---
|
|
22
24
|
|
|
23
|
-
How to verify 2.0 backend behavior
|
|
25
|
+
How to verify 2.0 backend behavior — ad-hoc `test`-repo scripts that pin a real client DB (everywhere), or the worker2 Codeception pilot suite; open before writing any 2.0 backend verification.
|
|
24
26
|
|
|
25
27
|
## Summary
|
|
26
28
|
|
|
27
|
-
The 2.0 backend
|
|
29
|
+
The 2.0 backend has **no shared PHPUnit harness** — the only automated suite is the **worker2 Codeception/PHPUnit pilot** ([codeception-integration-tests](../apps/worker2/features/codeception-integration-tests.md)), local-only and never a deploy gate. Everywhere else, verify backend behavior with **ad-hoc scripts in the `test` repo** (per-dev folders, e.g. `@Mark`) that bootstrap `_underscore` from a 2.0 app and pin a real client database explicitly.
|
|
28
30
|
|
|
29
31
|
## Bootstrapping a verification script
|
|
30
32
|
|
|
@@ -131,7 +133,7 @@ Also design around what a direct-call script **cannot** see: calling a model met
|
|
|
131
133
|
|
|
132
134
|
## Rules
|
|
133
135
|
|
|
134
|
-
-
|
|
136
|
+
- Outside worker2, do not add or assume a PHPUnit suite; write a `test`-repo script. In worker2, add tests to the Codeception `Integration` suite (local-only; never wire it into deploy).
|
|
135
137
|
- Always set `ENVIRONMENT` before bootstrap (PowerShell: `$env:ENVIRONMENT='...'`, or `putenv`).
|
|
136
138
|
- Always pin the concrete `Client_<Id>` schema to `DB_CLIENT` (and logs/archive if the code logs).
|
|
137
139
|
- **Commit seeded rows before you read them back** — the read connection cannot see uncommitted writes; commit cleanup too so it persists.
|
|
@@ -5,8 +5,8 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-09-
|
|
9
|
-
owners: [jcardinal, ajean, apeterson]
|
|
8
|
+
updated: 2026-09-25
|
|
9
|
+
owners: [jcardinal, ajean, apeterson, mhammontree]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
12
12
|
- ../apps/_underscore/architecture.md
|
|
@@ -19,7 +19,7 @@ related:
|
|
|
19
19
|
- framework-rules-cases.md
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
Core 2.0 `_underscore` framework rules — class naming, the worker contract & dispatch, the api2 `{success,data,errors}` envelope, dbchanges2 schema/isolation rules, and `c_` dynamic
|
|
22
|
+
Core 2.0 `_underscore` framework rules — class naming, the worker contract & dispatch, the api2 `{success,data,errors}` envelope, dbchanges2 schema/isolation rules, and `c_` columns (dynamic in /v2, declared for direct `_Model` use); open when writing/reviewing any 2.0 code.
|
|
23
23
|
|
|
24
24
|
These rules apply to all 2.0 repos: `_underscore` (core), `worker2`, and `api2`. They are
|
|
25
25
|
loaded on-demand by `/kickoff` when a 2.0 repo is in scope (not always-on) — see the
|
|
@@ -317,18 +317,24 @@ continue. Data accuracy outranks job "completion," even when the failure is not
|
|
|
317
317
|
ACL error on one record) — a developer reconciling a stalled job is the intended outcome, not a
|
|
318
318
|
job that reports success having quietly skipped records.
|
|
319
319
|
|
|
320
|
-
## `c_`-prefixed custom columns are
|
|
320
|
+
## `c_`-prefixed custom columns are dynamic in the /v2 API — not in `_Model`
|
|
321
321
|
|
|
322
|
-
Columns prefixed `c_` are **custom/dynamic** columns
|
|
322
|
+
Columns prefixed `c_` are **custom/dynamic** columns. The **/v2 API engine** handles them
|
|
323
323
|
**without any model-class declaration**:
|
|
324
324
|
|
|
325
|
-
-
|
|
326
|
-
|
|
325
|
+
- Through /v2 they are read, written, and filtered **without** being declared as properties on
|
|
326
|
+
the `_Model_*` subclass. Raw `_Query` result rows also return them as-is.
|
|
327
327
|
- They require **no `Core.RecordFields` entry and no ACL entry**.
|
|
328
328
|
- Precedent examples (`c_clickupUserIdentifier`, the 2026-07 transcript-routing columns): see framework-rules-cases.md.
|
|
329
329
|
|
|
330
|
-
**Consequence:**
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
330
|
+
**Consequence:** for data that only flows through /v2 (or raw `_Query`), adding a `c_` column
|
|
331
|
+
is a **schema-only** change (a `dbchanges2` migration). Do not add `RecordFields`/ACL rows for a
|
|
332
|
+
`c_` column. Use a non-`c_` column when you *do* want the full declared, ACL-governed field
|
|
333
|
+
treatment.
|
|
334
|
+
|
|
335
|
+
**Exception — direct `_Model` access.** `_Model` itself is **not** dynamic: `__get`/`__set` of an
|
|
336
|
+
undeclared field throws `There is no field called 'c_…'`. Code that reads or writes a `c_` column
|
|
337
|
+
through a model instance (worker2 actions, crons, scripts) **must declare it as a property** on a
|
|
338
|
+
client model override (e.g. `_Model_Nycdoe_ServiceRequest`) or a `_Trait_*`. Precedent:
|
|
339
|
+
`_underscore/Model/Nycdoe/{File,Case,ServiceRequest}.php` (TRUE-80519). Symptom and fix:
|
|
340
|
+
[model-magic-field-access](../apps/_underscore/features/model-magic-field-access.md).
|
package/knowledge/INDEX.md
CHANGED
|
@@ -20,7 +20,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
20
20
|
## 2.0 framework
|
|
21
21
|
|
|
22
22
|
- **_underscore** (_Underscore) _(framework core)_ — 90 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
23
|
-
- **worker2** (Worker) —
|
|
23
|
+
- **worker2** (Worker) — 77 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
24
24
|
- **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
25
25
|
- **dbchanges2** (Database Changes) _(framework core)_ — 19 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
26
26
|
- **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
5
|
+
| [NYCDOE 2.0 ASN SFTP Ingestion + Parsing (Sftp/Import → Sftp/Parse → Case/ServiceRequest)](features/asn-sftp-ingestion-2-0.md) | 2.0 | 2.0 rewrite of DOE's vendor-SFTP ASN intake (TRUE-80519 Phases 1–2): per-manufacturer fetch to `Files`, header-validated parse into one Case per customer PO + o |
|
|
5
6
|
| [NYCDOE EDI 850 Receive & 997 Acknowledgement](features/edi-850-receive-and-997-ack.md) | 1.0 | DOE's EDI 850-in → NetSuite SO + inline 997-out ack; open for the 997 build, its "always accept" stub, or a DOE ack complaint. |
|
|
6
7
|
| [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | Bidirectional DOE ticket hold-status sync (ServiceNow ⇄ TOGaDesk), the four defects that let holds silently revert or never re-send, and the outbound cron's sel |
|
|
7
8
|
| [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | Full DOE ServiceNow/ASN integration map — ticket mirror, ASN → NetSuite SO/TOGa Desk pipeline, outbound status/POD, EDI; open for any DOE cron/pipeline work or |
|
|
8
|
-
| [New York City Department of Education](profile.md) | 1.0 | NYC DOE client profile (1.0 worker tier, ServiceNow + EDI
|
|
9
|
+
| [New York City Department of Education](profile.md) | 1.0 | NYC DOE client profile (1.0 worker tier live; 2.0 ASN rewrite in progress), ServiceNow + EDI; open for DOE's platform scope, endpoints, API clients, and where i |
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NYCDOE 2.0 ASN SFTP Ingestion + Parsing (Sftp/Import → Sftp/Parse → Case/ServiceRequest)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: nycdoe
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-25
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Sftp/Import.php
|
|
13
|
+
- worker2/Worker/Sftp/Parse.php
|
|
14
|
+
- _underscore/Model/Nycdoe/File.php
|
|
15
|
+
- _underscore/Model/Nycdoe/Case.php
|
|
16
|
+
- _underscore/Model/Nycdoe/ServiceRequest.php
|
|
17
|
+
- dbchanges2/Client_Nycdoe/2026-09-22b - Add ASN parsing fields to ServiceRequests.sql
|
|
18
|
+
- dbchanges2/Client_Nycdoe/2026-09-22c - Add customerPurchaseOrder field to Cases.sql
|
|
19
|
+
- worker2/tests/Integration/ParseCest.php
|
|
20
|
+
related:
|
|
21
|
+
- servicenow-integration.md
|
|
22
|
+
- ../profile.md
|
|
23
|
+
- ../../../2.0/apps/worker2/features/codeception-integration-tests.md
|
|
24
|
+
- ../../../2.0/apps/_underscore/features/cli-script-bootstrap.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
2.0 rewrite of DOE's vendor-SFTP ASN intake (TRUE-80519 Phases 1–2): per-manufacturer fetch to `Files`, header-validated parse into one Case per customer PO + one ServiceRequest per line; open for any 2.0 DOE ASN/SFTP work.
|
|
28
|
+
|
|
29
|
+
## Summary
|
|
30
|
+
|
|
31
|
+
The 2.0 replacement for 1.0's `legacy_import_asn.php` + `asnprocessor/*` (see [ServiceNow / ASN Integration](servicenow-integration.md) for the 1.0 pipeline, still live). **ASN-first:** nothing is created in 2.0 except from an ingested ASN file. Phase 1 (fetch) and Phase 2 (parse) are built and verified locally against real sample files for all 4 manufacturers; Phase 3 (NetSuite SO, freeze/growth lineage) is not built. Not yet deployed/scheduled in prod.
|
|
32
|
+
|
|
33
|
+
## How it works
|
|
34
|
+
|
|
35
|
+
1. **`Sftp/Import/Run`** — one `Core.CronJobs` row per client+manufacturer (`dbchanges2/Core/2026-09-14a - Add SFTP Import cron - NYCDOE <Mfr>.sql`). Connects (secret by reference via `_Component_Secrets::resolve`), lists files matching `fileNamePattern`, dedupes (size/mtime, then checksum), saves each new file as a `Files` row (`Files.data` is `FIELD_STORAGE` → auto S3), `c_status='pending'`, then `_Worker::runTask('Sftp/Parse/<adapter>', …)`. Client-agnostic; the client's `Files` model class is passed per CronJobs row. `RunLocalTest()` swaps SFTP listing for a local folder (local SFTP is blocked: dev AWS SSO cannot assume WorkloadsRuntime).
|
|
36
|
+
2. **`Sftp/Parse/{Lenovo,Lexmark,Acer,Apple}`** — loads the `Files` row, **validates the header by exact name match** (mismatch → `c_status='failed'` + throw; never a column-count guess — the direct fix for 1.0's "Lenovo Off-Layout ASN File"), parses rows, then:
|
|
37
|
+
- **Case = one per customer PO** (`Cases.c_customerPurchaseOrder`; `Cases.number` is `FIELD_AUTOINCREMENT`, not settable). One file can hold several POs.
|
|
38
|
+
- **ServiceRequest = one per line**, `serviceRequestTypeId` = `Onsite`, stage = `New` (both looked up by name, never hardcoded ids). Fields: `c_partNumber`, `c_serialNumber`, `c_ritmNumber`, `c_filesId`.
|
|
39
|
+
- **Dedupe on re-run:** serialized → match `c_serialNumber`; non-serialized → `(caseId, c_partNumber)` — one record per part per Case regardless of qty. Re-running a file creates nothing (verified all 4).
|
|
40
|
+
- Fail-loud: a bad row aborts the job; no catch-and-skip.
|
|
41
|
+
|
|
42
|
+
| Mfr | Layout | Header const | Date column / format |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| Lenovo | own 30-col layout (not CDW) | `LENOVO_HEADER` | Ship Date `M/D/YYYY H:MM:SS AM/PM` (date part) |
|
|
45
|
+
| Lexmark | CDW 47-col | `LEXMARK_HEADER` | `Order_Date` `YYYY/MM/DD` |
|
|
46
|
+
| Acer | CDW 47-col, 3 names differ from Lexmark | `ACER_HEADER` | `YYYY/MM/DD` |
|
|
47
|
+
| Apple | own | `APPLE_HEADER` | `order_date` `M/D/YY` or `M/D/YYYY` |
|
|
48
|
+
|
|
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
|
+
|
|
51
|
+
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
|
+
|
|
53
|
+
## Gotchas
|
|
54
|
+
|
|
55
|
+
- **Direct `_Model` writes need every `c_` field declared as a real property** — `_Model::__set()` throws `There is no field called 'c_…'` otherwise. The "c_ needs no declaration" behavior is the /v2 API engine's, not `_Model`'s. Hence `_Model_Nycdoe_File` / `_Model_Nycdoe_Case` / `_Model_Nycdoe_ServiceRequest`.
|
|
56
|
+
- **1.0 `asnprocessor/lenovo.php` is not a source of truth for today's Lenovo layout.** Its 47-col mapping is stale; the real file is 30 cols (confirmed on 3 live samples). Verify every header and date format against a **real current sample file**, never the 1.0 code — wrong Lenovo header, Lexmark date, and Apple 2-digit year (`'25'` → year `0025`) were all caught this way.
|
|
57
|
+
- **Acer and Lexmark share column positions, not header names** (Acer `First_Name_Shipping`/`Last_Name_Shipping`/`SerialNo` vs Lexmark `Contact_Name_Shipping`/`Email_Shipping`/`Svc_Tag_ID`). Keep separate header constants. Lenovo stays its own path (not a CDW supplier).
|
|
58
|
+
- **Lenovo unescaped comma in `Product Long Description` (col 18) is repaired, not rejected** — `repairLenovoRowColumnCount()` merges the extra fragments back (ported from 1.0; the only field Lenovo breaks this way). A true header mismatch is still fatal. No real occurrence seen in 2.0 yet — re-confirm when Lenovo next sends one.
|
|
59
|
+
- **`DOE-OPEN-LENOVO-*` files are a retired process — never parse; delete on sight** (Skyler/Eric). Lenovo's `fileNamePattern` `DOE-Lenovo-ASN-*.csv` already excludes them. SFTP cleanup of old ones is deferred.
|
|
60
|
+
- **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
|
+
|
|
62
|
+
## Change history
|
|
63
|
+
- 2026-09-25 — Phase 2 parsing built for all 4 mfrs; header validation, comma repair, real-sample header/date corrections; integration tests added (mhammontree)
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: nycdoe
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-25
|
|
10
10
|
owners: [mhammontree, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/nycdoe/import_asn.php
|
|
@@ -43,6 +43,7 @@ related:
|
|
|
43
43
|
- edi-850-receive-and-997-ack.md
|
|
44
44
|
- ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
|
|
45
45
|
- hold-status-sync.md
|
|
46
|
+
- asn-sftp-ingestion-2-0.md
|
|
46
47
|
- ../../../1.0/apps/worker/architecture.md
|
|
47
48
|
- ../../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md
|
|
48
49
|
---
|
|
@@ -145,7 +146,7 @@ The five 1.0 `App_SystemMonitor_ServiceNow*` monitors scrape logs for **arrived-
|
|
|
145
146
|
- **Any structured HTTP response below 500 is a PASS** — it proves DNS, TLS, the load balancer, routing, and the auth layer are alive. A `401` is a *healthy* answer, not a failure.
|
|
146
147
|
- **Accepted coverage gap: token validity is NOT monitored.** The OAuth client `App_Api_NYCDOEV2` lives in the 1.0 `library` repo, which worker2 cannot reach. Re-implementing it in worker2 would duplicate DOE's credentials into a second repo and race the shared access token. An expired/revoked token will surface via the 1.0 failure monitors, not this probe — do not "fix" this by forking the client.
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
All **live** DOE business logic is 1.0. The only other 2.0 code is the in-progress ASN rewrite on the `Client_Nycdoe` tenant — see [2.0 ASN SFTP Ingestion](asn-sftp-ingestion-2-0.md). See [OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md).
|
|
149
150
|
|
|
150
151
|
### Operating rules when changing this integration
|
|
151
152
|
1. **Trace the full pipeline downstream before fixing** — the constraint that makes an edit unsafe usually lives downstream (the freeze point and the consumer/display query).
|
|
@@ -170,7 +171,8 @@ This probe is the client's **only 2.0 footprint**. DOE is **1.0 by default** —
|
|
|
170
171
|
- **Asset-tag repair is a SEPARATE, TWO-STORE backfill** — see the derived-`assets.tag` gotcha below. Generate the SQL **mechanically** from the customer's serial→tag CSV (never hand-transcribe), match serials through `App_NYCDOE::serialKey()` (the leading-`S` gotcha below), scope the statements to the ASN/MSO, and fill **only still-empty** tags so the file is re-runnable. Done this way for ASN `26720` / PO `S202648137` / MSO `329924` (87 units) on 2026-08-13, PR dbchanges#265.
|
|
171
172
|
- **Known remaining damage from this defect** (each needs its own customer serial→tag list): ASN `26641` / PO `S202668066` — **162 of 363** units still tagless; ASN `26706` / PO `WR270003276` — **1 of 7**.
|
|
172
173
|
- **A fixture-based regression test is owed with the parser fix**: assert that serial, quantity and assetTag each land in the right field for **both** layouts (do not detect by column count alone). ⚠ Do not quote constants out of `asnprocessor/lenovo.php` into a doc or ticket — that file contains hardcoded FTP credentials.
|
|
173
|
-
-
|
|
174
|
+
- **`DOE-Open-Lenovo` is a retired Lenovo process — it should never be parsed; delete on sight** (Skyler/Eric, 2026-09-25). The 1.0 parser still picks it up (unchanged).
|
|
175
|
+
- **Recommended 1.0 code fixes (proposed, NOT approved, NOT implemented):** map columns by **header name** and hard-reject an unknown layout with a loud alert; stop building the dedupe key from a field that can be a constant; widen `AdvanceShippingNoticeItems.partNumber`. The 2.0 rewrite already does exact header validation and excludes `DOE-Open-Lenovo` by filename — see [2.0 ASN SFTP Ingestion](asn-sftp-ingestion-2-0.md).
|
|
174
176
|
- **UNRESOLVED.** On ASN `26720`, items `54672`/`54673` carried 31 and 25 units with **correct** serials in `serialNumber` and `assetTag` NULL — which the three collapsed queue rows cannot account for. The run that created those units was **not identified**. Still open; do not assume it is explained.
|
|
175
177
|
- **Ruled out:** the recent **Quad** ASN import work is unrelated — Quad lives under `worker/crons/toga2/**` against the toga2 `AdvanceShippingNoticeItemUnits` schema, while the DOE path is `Core.AdvanceShippingNoticeUnits` via `asnprocessor/lenovo.php`.
|
|
176
178
|
|
|
@@ -257,3 +259,6 @@ This probe is the client's **only 2.0 footprint**. DOE is **1.0 by default** —
|
|
|
257
259
|
- [NYCDOE EDI 850 Receive & 997 Acknowledgement](edi-850-receive-and-997-ack.md) — 850-in/997-out
|
|
258
260
|
- [Worker (1.0) architecture](../../../1.0/apps/worker/architecture.md) — cron dispatch, role election, schedule files
|
|
259
261
|
- 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`)
|
|
262
|
+
|
|
263
|
+
## Change history
|
|
264
|
+
- 2026-09-25 — Corrected "no 2.0 tenant" (TRUE-80519 2.0 ASN rewrite now exists); DOE-Open-Lenovo marked retired (mhammontree)
|
|
@@ -7,26 +7,29 @@ apps:
|
|
|
7
7
|
- togadesk
|
|
8
8
|
- dbchanges
|
|
9
9
|
- worker2
|
|
10
|
+
- _underscore
|
|
11
|
+
- dbchanges2
|
|
10
12
|
project: Worker
|
|
11
13
|
client: nycdoe
|
|
12
14
|
type: profile
|
|
13
15
|
status: active
|
|
14
|
-
updated: 2026-09-
|
|
16
|
+
updated: 2026-09-25
|
|
15
17
|
owners: [mhammontree, sking]
|
|
16
18
|
files: []
|
|
17
19
|
related:
|
|
18
20
|
- features/servicenow-integration.md
|
|
19
21
|
- features/edi-850-receive-and-997-ack.md
|
|
22
|
+
- features/asn-sftp-ingestion-2-0.md
|
|
20
23
|
---
|
|
21
24
|
|
|
22
|
-
NYC DOE client profile (1.0 worker tier, ServiceNow + EDI
|
|
25
|
+
NYC DOE client profile (1.0 worker tier live; 2.0 ASN rewrite in progress), ServiceNow + EDI; open for DOE's platform scope, endpoints, API clients, and where its ticket/ASN/EDI logic lives.
|
|
23
26
|
|
|
24
27
|
## Summary
|
|
25
|
-
NYC DOE — TOGA client whose
|
|
28
|
+
NYC DOE — TOGA client whose **live** integration runs in the **1.0 worker tier** (~30 cron scripts under `worker/crons/sync/nycdoe/` and `worker/crons/notifications/nycdoe/`, on the `sync` and `notification` worker roles). ServiceNow is DOE's system of record for tickets (Incidents and RITMs); TOGA mirrors those locally, drives fulfillment through TOGa Desk and NetSuite, and pushes status / ETA / proof-of-delivery back. A parallel EDI relationship (850 POs in, invoices out) runs over DOE's SFTP. A **2.0 rewrite (TRUE-80519)** of the ASN intake is being built against the `Client_Nycdoe` tenant — not yet live.
|
|
26
29
|
|
|
27
30
|
## Platforms & data
|
|
28
|
-
- **1.0 worker tier** — all business logic. Local mirror tables in `db_common` (`NYCDOETickets`, `NYCDOELocations`, `AdvanceShippingNotice*`).
|
|
29
|
-
-
|
|
31
|
+
- **1.0 worker tier** — all live business logic. Local mirror tables in `db_common` (`NYCDOETickets`, `NYCDOELocations`, `AdvanceShippingNotice*`).
|
|
32
|
+
- **2.0 (`worker2` + `_underscore` + `dbchanges2`, tenant `Client_Nycdoe`)** — (1) the OneUptime liveness probe `Monitor/Nycdoe/ServiceNowEndpointHealth` (`worker2/Worker/Monitor/Nycdoe.php`, TRUE-80587, live); (2) the ASN rewrite `Sftp/Import` → `Sftp/Parse` → Case/ServiceRequest (Phases 1–2 built, not deployed) — see [2.0 ASN SFTP Ingestion](features/asn-sftp-ingestion-2-0.md). Entity map: `Case` = 1.0 managed_service_order, `ServiceRequest` = 1.0 repair_order; DOE does not use `Ticket`. Client models: `_underscore/Model/Nycdoe/`.
|
|
30
33
|
- **TOGa Desk:** DOE is client id **16** (`db_togadesk`) — repair orders, managed service orders, the "#received / #ordered" Receiving Summary UI.
|
|
31
34
|
- **NetSuite:** Sales Orders, PO reconciliation, item receipts, Item Fulfillments, invoices (SuiteTalk toolkit in `library`).
|
|
32
35
|
- **`dbchanges` (1.0):** DOE schema + **data-repair** changes ship here (`dbchanges/Core/` targets the legacy/V1 `Core` schema). Stranded-ASN recoveries are data-only repairs — see the [1.0 dbchanges authoring workflow](../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md).
|
|
@@ -46,3 +49,4 @@ NYC DOE — TOGA client whose entire integration runs in the **1.0 worker tier**
|
|
|
46
49
|
|
|
47
50
|
## Key features (this client)
|
|
48
51
|
- [ServiceNow / ASN Integration](features/servicenow-integration.md) — full integration map: ticket sync, the ASN pipeline (core flow), outbound status sync, EDI, SME-confirmed business rules.
|
|
52
|
+
- [2.0 ASN SFTP Ingestion + Parsing](features/asn-sftp-ingestion-2-0.md) — the TRUE-80519 2.0 rewrite (fetch → header-validated parse → Case/ServiceRequest).
|
package/package.json
CHANGED
|
@@ -65,6 +65,25 @@ it: a rewrite silently discards whatever the developer (or a teammate) changed i
|
|
|
65
65
|
|
|
66
66
|
Only rewrite when explicitly told to.
|
|
67
67
|
|
|
68
|
+
## Check git before stating commit state
|
|
69
|
+
|
|
70
|
+
Developers commit, push, and switch branches on their own between turns. Claude's memory of
|
|
71
|
+
"I edited this, so it is not committed" goes stale fast, and a wrong "not committed" reminder
|
|
72
|
+
confuses people.
|
|
73
|
+
|
|
74
|
+
Claude **must never** say a change is committed, not committed, pushed, or not pushed based on
|
|
75
|
+
memory. Before any such statement, run a fresh check **in the same turn**:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
git -C <repo> status --short <file>
|
|
79
|
+
git -C <repo> log -1 --format="%h %ad %s" --date=short -- <file>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Report what git shows: e.g. "committed in `23a3e11`" or "uncommitted changes in `<file>`".
|
|
83
|
+
- For push state, check `git -C <repo> status -sb` (ahead/behind) — do not assume.
|
|
84
|
+
- If Claude did not check, it says **nothing** about commit or push state. No habit reminders
|
|
85
|
+
like "still not committed" at the end of a summary.
|
|
86
|
+
|
|
68
87
|
## Branch protection
|
|
69
88
|
|
|
70
89
|
Never commit directly to `_main`. All changes go through feature branches and pull requests.
|