toga-ai 1.0.960 → 1.0.962

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: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-23
9
+ updated: 2026-10-06
10
10
  owners: ["jcardinal", "bala", "mhammontree", "apeterson"]
11
11
  files:
12
12
  - _underscore/Model/Client/EmailTemplate.php
@@ -84,6 +84,7 @@ Rules:
84
84
  - Rejected design: an approve button inside the email (mail scanners pre-fetch links). Runner-up:
85
85
  single held row, preview only in Tools.
86
86
 
87
+ Symptom of a missing migration in an env: order email 500s with `Unknown column isReviewRequired` (Client) or `emailTemplateId` (Logs_Client) — client-sandbox hit this 2026-10; apply the two `2026-09-23a` files.
87
88
  Migrations: `Core/2026-09-23e - EmailTemplateReviewRecordField.sql` (RecordFields 2632 on
88
89
  Records 202), `Client/2026-09-23a - EmailTemplateReview.sql`, `Logs_Client/2026-09-23a -
89
90
  EmailReview.sql`. Deploy: SQL (Logs_Client first) → `_underscore` → tools.
@@ -230,6 +231,7 @@ worker method) in-process instead.
230
231
 
231
232
  ## Change history
232
233
 
234
+ - 2026-10-06 — Noted the 1054 symptom when the email-review migrations are missing in an env (client-sandbox). (bala)
233
235
  - 2026-09-23 — Added the review hold (`isReviewRequired` + `REVIEW` addresses → `HELD` real email +
234
236
  `[REVIEW]` copy); fail-loud on missing REVIEW rows / unknown `toCcBcc`. (jcardinal)
235
237
  - 2026-09-17 — Added two caller-side gotchas found building the NYCHH transfer-order emails:
@@ -6,8 +6,8 @@ project: Database Changes
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: [apeterson]
9
+ updated: 2026-10-06
10
+ owners: [apeterson, bala]
11
11
  files:
12
12
  - api2/Config/production.ini
13
13
  - api2/Config/sandbox-client.ini
@@ -124,8 +124,12 @@ Used 2026-09-03 to bring sandbox-client's stale `Client_Nychh` TableViews back i
124
124
  - **The reference counts written down here go stale too.** After the 2026-09-04 reset, sandbox-client read `Core.Surfaces` = **54** and `Client_Nychh.TransferOrders` = **1,932**, against the prod figures previously recorded here — 53 (2026-09-03) and 1,927 (2026-08-31). Prod itself moved on. Restored counts landing slightly **above** an older recorded prod number is the expected pattern, not evidence of a bad restore. Re-read the count from the source at reset time instead of comparing to a number in this doc.
125
125
  - 🚨 **The `dbchanges2` folder name IS the target database — pipe each folder to its own DB.** `Core/` → `Core`, `Client_<Name>/` → that one tenant, `Client/` → **every** tenant. Files reference tables **unqualified** by design (the cluster-isolation rule), so running a `Core/` file against a client DB does not complain about the database — it fails as `Table 'Client_Nychh.Messages' doesn't exist`, which names a missing table rather than the real mistake. If you see "table doesn't exist" for a table you know is in Core, check which schema you piped the file into before you touch the SQL.
126
126
  - 🚨 **A `Client/` fan-out hits EVERY `Client_*` schema on the cluster — including dated BACKUP databases.** The production client cluster carries copies that are not tenants. Found 2026-09-01: **`Client_Compass_20260831_120000`**, a dated backup of `Client_Compass`, sitting alongside the live schemas. Any "run this against every `Client_*` database" loop picks it up, which at best wastes a run and at worst mutates a snapshot someone is holding for a restore. **Before a fan-out, list the target schemas and eyeball them** — `SHOW DATABASES LIKE 'Client\_%'` — and exclude anything matching a `_YYYYMMDD_HHMMSS` suffix or otherwise absent from `Core.Clients`. Do not assume the tenant list and the schema list agree.
127
+ - **DBeaver Ctrl+Enter mis-splits a file when a `#` comment contains an apostrophe** (e.g. `# don't`) — statements run partly or not at all. Run the file with Alt+X (execute script), or strip the comments first.
127
128
  - **The repo is evidence of intent, never of what an environment contains.** Confirming a file ran is its own procedure — see [Verifying whether a migration actually ran](./verifying-a-migration-ran.md).
128
129
 
130
+ ## Change history
131
+ - 2026-10-06 — Added the DBeaver Ctrl+Enter apostrophe-in-comment mis-split gotcha. (bala)
132
+
129
133
  ## Related
130
134
 
131
135
  - [dbchanges2 architecture](../architecture.md)
@@ -6,6 +6,7 @@
6
6
  | [Bundle item visibility & selectability (the three flags, and why they are enforced nowhere but the client)](features/bundle-item-visibility-and-selectability.md) | Which kit components a shopper can see/select is decided entirely in the browser, enforced nowhere on the server; open when touching bundle rendering, kit check |
7
7
  | [Cart Bundle Submission & the bundleUuid Identity Contract](features/cart-bundle-submission-and-identity.md) | How cart bundles (kits) become SalesOrderItems on submit/edit, and the bundleUuid identity contract every consumer must obey; open before touching cart-bundle s |
8
8
  | [Cart Notification Emails — auto-add/remove lifecycle + duplicate prevention](features/cart-notification-emails.md) | Cart Notifications section: CC-email duplicate prevention + auto-add/remove of order-for-user & delegate-manager emails; open when touching notification emails |
9
+ | [Cart Order For user — Add New User duplicate guard + approving-manager rule](features/cart-order-for-user-and-approving-manager.md) | Cart "Order For" user section: the Add New User duplicate check (username OR email), who the Order For list can find, and the config-driven rule that blocks che |
9
10
  | [Cart Order-Total & Shipping Computation](features/cart-order-total-computation.md) | How the cart total (subtotal/shipping/tax/total) is computed and where shipping cost flows from; open when a cart total or shipping charge looks wrong. |
10
11
  | [Cart Page — config-driven form architecture (current state + planned refactor)](features/cart-page-config-architecture.md) | Current state of the Cart form (field-config-driven, section-hardcoded, two dual-mode forms) plus the DECIDED-but-unbuilt config-driven refactor to the toga2.5 |
11
12
  | [Catalog cache freshness — the persisted query cache and the per-query staleTime overrides](features/catalog-cache-freshness.md) | The persisted React Query cache (`localStorage["commerce"]`) plus the per-query `staleTime` overrides that still serve prices up to 24h old, how to opt a query |
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: Cart Order For user — Add New User duplicate guard + approving-manager rule
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-06
10
+ owners: ["bala"]
11
+ files:
12
+ - src/pages/Cart/api/CartApi.ts
13
+ - src/pages/Cart/view/modals/NewUserForm.tsx
14
+ - src/pages/Cart/CartPage.tsx
15
+ - src/pages/Cart/helpers/checkIsProceedToCheckoutEnabled.ts
16
+ - src/pages/Cart/view/cartForm/CartFormSection.tsx
17
+ - src/components/Inputs/AdvancedInput.tsx
18
+ - src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts
19
+ - src/pages/Cart/viewModel/FIELDS/*/NEWUSERFORMFIELDS.ts
20
+ related:
21
+ - ./cart-page-config-architecture.md
22
+ - ./cart-notification-emails.md
23
+ - ./header-order-for-dropdown.md
24
+ - ../../../../clients/compass-usa/features/approval-decision-flow.md
25
+ - ../../../../clients/compass-usa/features/people-file-user-lifecycle.md
26
+ - ../../../../clients/quad/profile.md
27
+ ---
28
+
29
+ Cart "Order For" user section: the Add New User duplicate check (username OR email), who the Order For list can find, and the config-driven rule that blocks checkout when the user has no approving manager; open before touching NewUserForm, `addUser`, delegate manager, or checkout gating.
30
+
31
+ ## Summary
32
+
33
+ Two guards on the cart's Order For user, both added in TRUE-82423:
34
+ 1. **Add New User** refuses to create a user whose `c_hrEmpUsername` **or** `email` already exists. Before, only the username was checked, so buyers created a second `Users` row for an existing person.
35
+ 2. **Approving manager required** (Compass USA + Canada only). If the picked user has no supervisor, the Delegate Manager box auto-ticks and Proceed to Checkout stays disabled until a delegate manager is picked. Without this the order saves with **no step-2 approver and no error** (see [approval-decision-flow](../../../../clients/compass-usa/features/approval-decision-flow.md)).
36
+
37
+ ## How it works
38
+
39
+ ### Add New User duplicate check (`CartApi.ts` `addUser`)
40
+ - Before `POST /v2/users`, `GET /v2/users` with a top-level `or` in `Options.where`: `c_hrEmpUsername` eq typed **or** `email` eq typed. `utils/queryHelpers` `assembleOptionsWhere` supports a top-level `or`.
41
+ - Hit → error under the form: "A user with this Employee Username or email already exists. Please search for them in Order For". The error clears when username or email changes.
42
+ - `NewUserForm` modal is `min-h-[700px]` (`max-sm:min-h-0`), not fixed `h-[700px]`, so Cancel/Save stay inside when the error shows.
43
+ - Copy lives in `FIELDS/<CLIENT>/NEWUSERFORMFIELDS.ts` (COMPASS, COMPASSCANADA EN+FR, QUAD).
44
+
45
+ ### Who the Order For list can find
46
+ For roles without search, the cart Order For list shows only the logged-in person's **active direct reports** (`Users.isActive = 1` AND `supervisor.uuid = me`). Inactive users and other managers' staff are not in it. That is why buyers fell back to Add New User and made duplicates.
47
+
48
+ ### Approving-manager rule (`CartPage.tsx`, `checkIsProceedToCheckoutEnabled.ts`)
49
+ - **ON only when config says so:** the `orderFor` section's `delegateManager` input carries a `requiredMessage` (COMPASS + COMPASSCANADA EN/FR `CARTPAGE.ts`). No client-name or fieldKey checks in code. QUAD omits it.
50
+ - When the selected user has no `supervisorUser`, a `CartPage` effect auto-ticks `hasDelegateManager` (re-ticks if the buyer unticks). It auto-unticks only if it was the one that ticked and the next user has a manager. The tracking ref resets in `handleClearUser`.
51
+ - `checkIsProceedToCheckoutEnabled` gets `isApprovingManagerMissing` (non-edit, non-QUAD branch). Checkout is disabled while:
52
+ - the manager lookup is pending (`selectedUser.uuid !== orderFor.uuid`), or
53
+ - delegate is ticked or needed and no `delegateManager.uuid` is picked.
54
+ - The red `requiredMessage` under Delegate Manager uses the same `BaseText` style as other cart errors (`text-base`, `text-crimson-700`, `font-regular`). `AdvancedInput` takes optional `isInvalid` / `errorMessageId` → `aria-invalid` / `aria-describedby`; `BaseText` accepts `id`.
55
+ - Delegate ticked but left empty on a user who HAS a supervisor is fine: the backend falls back to the user's own supervisor.
56
+
57
+ ## Gotchas
58
+
59
+ - **The duplicate check is ACL scoped.** `GET /v2/users` returns only users the caller's role can see, so it can still miss an existing user. Not a hard uniqueness guarantee; the DB has none either.
60
+ - **Placeholder "e.g. DOEJ03" is wrong.** Real Compass usernames are `firstname.lastname`. Buyers typing employee numbers, full emails, or guessed names is how the username-only check was beaten.
61
+ - **Never apply manager-required rules to Quad.** Quad has no managers by design (approval is per location). See [Quad profile](../../../../clients/quad/profile.md).
62
+ - **Audit who created a duplicate:** `Logs_<Tenant>.Api` records each Add New User as `GET /v2/users` (where `c_hrEmpUsername:eq:<typed>`) then `POST /v2/users`, with the creator's `userId`.
63
+ - **Duplicate `Users` rows per `contactId` break 1.0 joins on `contactId`** (e.g. the MITS transmit cron sent orders twice). See [MITS transmit alerting](../../../../clients/compass-usa/features/mits-sales-order-transmission-alerting.md).
64
+ - Typecheck with `npx tsc -b`, not `npx tsc --noEmit -p .` (see [amplify-build-and-deploy](../workflows/amplify-build-and-deploy.md)).
65
+
66
+ ## Change history
67
+ - 2026-10-06 — Add New User checks username OR email; approving-manager rule (auto-tick delegate, checkout gated) driven by `requiredMessage` config; NewUserForm min height (TRUE-82423). (bala)
68
+
69
+ ## Related
70
+ - [cart-page-config-architecture](./cart-page-config-architecture.md)
71
+ - [cart-notification-emails](./cart-notification-emails.md) — delegate manager email lifecycle
72
+ - [header-order-for-dropdown](./header-order-for-dropdown.md) — the header pill, a different Order For
73
+ - [Compass approval-decision flow](../../../../clients/compass-usa/features/approval-decision-flow.md)
74
+ - [Compass PEOPLE-file user lifecycle](../../../../clients/compass-usa/features/people-file-user-lifecycle.md) — the other source of duplicate users
@@ -6,8 +6,8 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: ["apeterson", "tcox"]
9
+ updated: 2026-10-06
10
+ owners: ["apeterson", "tcox", "bala"]
11
11
  files:
12
12
  - src/pages/Cart/CartPage.tsx
13
13
  - src/pages/Cart/view/cartForm/CartForm.tsx
@@ -165,14 +165,18 @@ Result: `useForm` registers **exactly** the fields the client collects — no un
165
165
  - **Config-key naming drift.** `CARTPAGE.ts` uses `inputs:` for the `orderFor` section but `formFields:` for `shippingAddress` / `costCenter` / `notifications`, while `CartFormSection` reads a `fields` prop. Three names for one concept; `CartForm` hand-maps each section's array into the `fields` prop. **This pre-existing drift is what tripped the prior spike.** (Target: rename all to a single `fields` key.)
166
166
  - **`useForm` is seeded with a hardcoded union of every client's fields.** `CartPage.tsx` (~lines 230–258) calls `useForm` with one static `defaultValues` object that is the **union** of all clients' fields, with the **COMPASS-only magic shipping-method UUID `087a9d0b-…` baked in**. Two mode-change `reset()` effects (~lines 267, 282) re-seed from that same union shape. This is wrong for per-client field sets: it registers fields a client never renders and bakes one client's default into all of them, and risks RHF controlled/uncontrolled warnings. As of 2026-09-10 `hasDelegateManager` (`false`) and `delegateManager` (`{uuid:"",name:""}`) are part of that union and are typed in `CartFormFieldNames` — previously they were undefined and untyped. Because the default object is **truthy**, any check on the field must test `delegateManager?.uuid`, not the object.
167
167
  - **`CartFormSection` runs `field.onChange` BEFORE the config handler.** For an `advancedSelect` it calls `field.onChange(value)` then `config?.onSelect(value, valueKey)` (and `field.onChange(null)` then `config?.onClearInput(valueKey)`). So a config handler can never read the field's **previous** value from the form — it is already overwritten. Handlers that need the prior pick must keep it in a `useRef` (see [cart-notification-emails](cart-notification-emails.md) for the Delegate Manager case). The planned `hydrateCartConfig` (Phase 5) must preserve this ordering contract or document a change.
168
- - **Client differences are hardcoded, not config.** Examples observed: COMPASS renders `DuplicateKitGuardrail`, QUAD does not; COMPASS seeds the cart form (cost center / manager / emails) from the order-for user, QUAD does not. These are per-client conditionals in cart code today.
169
- - **Standards debt in the dual forms** (fold in as each file is touched): silent error swallows (e.g. the cost-center `onError` that never reaches Sentry), the magic Standard-Ground shipping UUID, and a 13-arg `checkIsProceedToCheckoutEnabled` helper (violates the 4+ params rule).
168
+ - **Client differences are hardcoded, not config.** Examples observed: COMPASS renders `DuplicateKitGuardrail`, QUAD does not; COMPASS seeds the cart form (cost center / manager / emails) from the order-for user, QUAD does not. These are per-client conditionals in cart code today. Counter-example (2026-10-06): the approving-manager rule is switched on purely by a `requiredMessage` on the `delegateManager` input in config, no client check (see [cart-order-for-user-and-approving-manager](cart-order-for-user-and-approving-manager.md)). Follow that pattern.
169
+ - **Standards debt in the dual forms** (fold in as each file is touched): silent error swallows (e.g. the cost-center `onError` that never reaches Sentry), the magic Standard-Ground shipping UUID, and a 14-arg `checkIsProceedToCheckoutEnabled` helper (`isApprovingManagerMissing` added 2026-10-06) (violates the 4+ params rule).
170
170
  - `useAssignClientFields.ts` returns `FIELDS?.[clientName]?.[language]?.[role]` with no transform — there is no fallback (a missing combo renders empty). See [client-fields](client-fields.md).
171
171
 
172
+ ## Change history
173
+ - 2026-10-06 — Noted the config-driven approving-manager rule as the C6 pattern to follow; `checkIsProceedToCheckoutEnabled` now 14 args (TRUE-82423). (bala)
174
+
172
175
  ## Related
173
176
 
174
177
  - [architecture](../architecture.md)
175
178
  - [client-fields](client-fields.md)
176
179
  - [cart-notification-emails](cart-notification-emails.md)
180
+ - [cart-order-for-user-and-approving-manager](cart-order-for-user-and-approving-manager.md)
177
181
  - [cypress-testing](../workflows/cypress-testing.md)
178
182
  - [toga25-supply client-configurable-fields](../../toga25-supply/features/client-configurable-fields.md)
@@ -6,7 +6,7 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-21
9
+ updated: 2026-10-06
10
10
  owners: ["jcardinal", "apeterson", "tcox", "bala"]
11
11
  files:
12
12
  - toga2-commerce/amplify.yml
@@ -87,6 +87,8 @@ AWS Amplify (build + hosting); GitHub Actions (`sync-stage-environments.yml` + t
87
87
 
88
88
  > The 2026-08-26 commerce-side fixes below (`.npmrc` flag, added peers, `resolve.dedupe`, the `--toaster-*` token layer, the `ToasterList` rewiring) are **working-tree on branch `TRUE-80707`** — uncommitted, unmerged, and not in any deployed build.
89
89
 
90
+ - **⚠ Typecheck locally with `npx tsc -b`, never `npx tsc --noEmit -p .`** — root `tsconfig.json` has `"files": []` plus `references`, so `-p .` checks **nothing** and passes. Amplify runs `tsc -b`, so a type error only shows there.
91
+ - **Where it runs:** Amplify app `d3334bbnuy3sng`, account `975050298201` (toga-test, us-east-1). `_sandbox-client` → `compass.client.sandbox.togacommerce.com` / `compasscanada.client.sandbox.togacommerce.com`; `_sandbox-dev` → `*.dev.sandbox.togacommerce.com` (api `api.dev.sandbox.togahub.com`); `_beta` → `compass.beta.togacommerce.com`. `.env.<mode>` files hold a Sentry token — never copy them into docs or tickets.
90
92
  - **All the toga2-supply gotchas apply** (console build spec overriding repo `amplify.yml`; CRLF breaking block-scalar parsing; Vite silently falling back to `.env`/beta when a mode's env file is missing). See the related doc.
91
93
  - **blox dist-tag must exist before the app builds.** `npm install @agilant/toga-blox@qc-security` fails `ETARGET No matching version` until `publish.yml` has published that tag. `publish.yml` only triggers on branches listed in `on.push.branches` and only tags branches with an explicit `elif` case — creating the branch alone does nothing; the workflow file must be present on that branch (GitHub Actions runs the workflow version on the pushed branch).
92
94
  - **`--legacy-peer-deps` is required** because blox declared `react-router-dom@^6` as a peer while commerce uses `react-router-dom@7` (npm 7+ `ERESOLVE` otherwise). All non-prod build scripts use it. (That peer was **phantom** and was removed from blox on 2026-08-26 — see the blox publish-pipeline doc — but the flag stays until a build without it is proven.)
@@ -129,6 +131,7 @@ AWS Amplify (build + hosting); GitHub Actions (`sync-stage-environments.yml` + t
129
131
 
130
132
  ## Change history
131
133
 
134
+ - 2026-10-06 — Added `tsc -b` vs `tsc --noEmit -p .` (the latter checks nothing) and the Amplify app id / branch → domain map. (bala)
132
135
  - 2026-09-21 — Added the second symptom of stale-`node_modules` blox drift: the old `Toaster` `message.map()` crash on any toast (installed 1.0.317 vs pinned 1.1.1-production.139); fix is installing the locked version and restarting Vite with `--force`. (bala)
133
136
 
134
137
  ## Related
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-01
10
- owners: ["apeterson"]
9
+ updated: 2026-10-06
10
+ owners: ["apeterson", "bala"]
11
11
  files:
12
12
  - toga25-supply/src/layout/BundleItemRecordModalLayout/hooks/useBundleItemEditForm.tsx
13
13
  - toga25-supply/src/layout/BundleRecordModalLayout/types.ts
@@ -115,10 +115,20 @@ established it **creates a new row** rather than overwriting an unknown one. Nev
115
115
  carrying the `EV-10` DB failure inside `messages[].identifiers.error`, so blox `apiPut` did not throw
116
116
  and the modal showed "Bundle item updated" on a write that never landed. Check `messages[]` for
117
117
  `type: ERROR` — see [V2 error codes](../../api2/features/v2-api-error-codes.md).
118
+ - **⚠ A kit price change does NOT reprice orders (Compass/Canada).** A price edit inserts a new
119
+ `BundleItemPrices` row; commerce reads the newest, but `SalesOrderItems.price` is a snapshot, so
120
+ pending orders keep the old price. Some **new** orders also saved the old price after the update
121
+ (e.g. SA138394) — save-path cause **not yet found** (2026-10-06). Remediation pattern: a guarded
122
+ `UPDATE SalesOrderItems` matched on **id AND old price**, with a backup SELECT and rollback
123
+ comment — `dbchanges2/Client_Compass/2026-09-30a`, `2026-09-30b`, `2026-10-02a`, `2026-10-05a`.
124
+ - **Commerce kit price ≠ the `BundleItemPrices` value.** `toga2-commerce`
125
+ `src/pages/BundleView/helpers/formatOriginalBundleData.ts` adds required kit items of asset type
126
+ FEE into the hero price (kit 236: 1606.55 + 40.00 staging fee = 1646.55). Compare like with like.
118
127
  - **⚠ FIELDS JSON edits appear to do nothing** — the persisted React Query cache freezes them; see
119
128
  [persisted query cache](./persisted-query-cache.md).
120
129
 
121
130
  ## Change history
131
+ - 2026-10-06 — Price changes do not reprice existing (and some new) orders; guarded `SalesOrderItems` repricing files; commerce adds FEE items to the shown kit price. (bala)
122
132
  - 2026-10-01 — Corrected `_unitPrice`/`_leasePrice`: both are price-type-gated and **COALESCE to the
123
133
  item's `ItemPrices` row**, so a displayed price does not prove a `BundleItemPrices` row exists.
124
134
  Recorded the open breakage from the 2026-09-24 dedupe migration (`currencyId` NOT NULL DEFAULT vs.
@@ -32,7 +32,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
32
32
  - **talos-backend** (TOGa Talos Backend) — 1 doc(s) → [2.0/apps/talos-backend/INDEX.md](2.0/apps/talos-backend/INDEX.md)
33
33
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
34
34
  - **ai-bdr** (AI-BDR) — 13 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
35
- - **toga2-commerce** (TOGa Commerce) — 29 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
35
+ - **toga2-commerce** (TOGa Commerce) — 30 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
36
36
  - **toga25-supply** (TOGa 2.5 Supply) — 23 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
37
37
  - **toga25-iq** (TOGa IQ) — 1 doc(s) → [2.0/apps/toga25-iq/INDEX.md](2.0/apps/toga25-iq/INDEX.md)
38
38
  - **toga25-desk** (TOGa Desk 2.5) — 1 doc(s) → [2.0/apps/toga25-desk/INDEX.md](2.0/apps/toga25-desk/INDEX.md)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: compass-usa
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-06
10
10
  owners: ["apeterson", "dfranks", "bala"]
11
11
  files:
12
12
  - _underscore/Model/Compass/ApprovalDecision.php
@@ -97,6 +97,8 @@ Evaluated in exactly three places, all deriving the manager from a supervisor re
97
97
  1. **Delegate note** — newest `SalesOrderNotes` row of type `delegate` carries the delegate's email in parentheses; `postPost` and `postPut` each `preg_match` it out and look the user up **by email**.
98
98
  2. **Supervisor** — no delegate note → manager from requester's `Users.supervisorUserId` (`LEFT JOIN Users AS Managers ON Managers.id = Users.supervisorUserId`).
99
99
 
100
+ **Neither resolves → no step-2 decision at all.** `postPost` only creates the step-2 `ApprovalDecision` when a delegate note or a supervisor resolves; with neither, the order saves with no manager and **no error**. The commerce cart now blocks checkout in that case (Compass USA + Canada): [cart Order For approving-manager rule](../../../2.0/apps/toga2-commerce/features/cart-order-for-user-and-approving-manager.md). Quad is exempt (no managers by design).
101
+
100
102
  Both produce a **user id** stamped onto `ApprovalDecisions.assignedToUserId`; from then the approval is bound to that *row*, not to that person's email — which is why the gotchas below matter.
101
103
 
102
104
  **Every user lookup on a non-unique key must order active-first.** As of 2026-08-20 all **9** such lookups across the two files end:
@@ -177,6 +179,9 @@ When a step-2 (Manager) decision is reassigned, `_swapManagerEmailAddress()` upd
177
179
  - **⚠ A manager swap sent in the SAME save as an approval is never logged, and the email list keeps the old manager.** `_evaluateManagerVip()` returns immediately when the request carries `isApproved`, assuming a request is *either* an assignment *or* an approval. When both arrive together the `"Manager reassigned"` note is skipped (the feature works — fired 757 times since May) AND so is the `SalesOrderEmailAddresses` swap, so the outgoing manager stays on the notification list. A handler for the combined case was drafted on the `_sandbox-client` branch only and is NOT on `TRUE-81900` — known gap, not shipped.
178
180
  - **The upstream cause of an unassigned step 2 is a user saved with no `supervisorUserId`.** The admin add-user screen allows saving a user without one, and the PEOPLE-file import may or may not backfill it later. That data hole feeds both the `!$hasManager` wipe above and the never-created step-2 decision at order creation. Not fixed — the guard only stops the order being *stranded*, it does not give it a manager.
179
181
 
182
+ ## Change history
183
+ - 2026-10-06 — Recorded that no delegate + no supervisor saves an order with no step-2 decision, and that the commerce cart now blocks it (TRUE-82423). (bala)
184
+
180
185
  ## Related
181
186
  - [Stranded Approval Reassignment](stranded-approval-reassignment.md) — repair half: repointing approvals already stamped with a dead duplicate `Users` id.
182
187
  - [Compass MR/MA Order Auto-Approval & Status Gate](mr-ma-order-approval-and-status.md) — auto-approval + `_status` gating half (on `_Model_Compass_SalesOrder`).
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: compass-usa
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-09-16
8
+ updated: 2026-10-06
9
9
  owners: ["dfranks", "bala", "jcardinal"]
10
10
  files:
11
11
  - worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php
@@ -93,10 +93,16 @@ The worker2 OneUptime monitor now catches this starvation directly: `CompassSale
93
93
  - **The MITS crons hold their API credentials inline in the file.** Treat both files as secret-bearing when copying snippets anywhere (tickets, PR bodies, knowledge docs). The two hardcoded MITS credentials live inline in `1_transmit_compass_sales_orders_to_mits.php` — note their location, never paste the values.
94
94
  - **⚠ 1.0 `App_ApiTransaction` built with `ENCODE_JSON` returns an ALREADY-DECODED object — do NOT `json_decode()` it.** A transaction constructed with `App_ApiTransaction::ENCODE_JSON` hands back a decoded object from `execute()`, not a JSON string. Calling `json_decode()` on it raises a PHP 7.2 `"expects parameter 1 to be string, object given"` warning, which `App_Error` escalates to a **FATAL**. In `1_transmit_compass_sales_orders_to_mits.php` that fatal fired **after** the order transmitted to MITS but **before** stamping `c_dtTransmittedToCompass` — producing a ~42-order backlog and the endless `SalesOrder:Exists` retry loop above (MITS kept the created order, we never recorded it). Correct pattern: `$decodedResp = is_string($resp) ? json_decode($resp) : $resp;`. Reusable 1.0 footgun, not a Compass-only one — any `App_` code that `json_decode()`s an `ENCODE_JSON` response hits it.
95
95
  - **The transmit item query's `GROUP BY SalesOrderItems.id` hides false "duplicates."** In `1_transmit_compass_sales_orders_to_mits.php` (~L178-251, `GROUP BY` at L247) the `GROUP BY SalesOrderItems.id` collapses the `LEFT JOIN VendorItems` / `LEFT JOIN VendorItemCosts` fan-out to one row per line. Running that SELECT **without** the GROUP BY shows FALSE duplicate fee lines (fees have 2 `VendorItemCosts` rows) — the real cron sends one line per SalesOrderItem. Do not "diagnose" duplicates from the un-grouped query.
96
+ - **A contact with 2+ `Users` rows sent the order to MITS twice (fixed 2026-10-06, both tenants).** The order query `LEFT JOIN`ed `Users AS OrderUsers` on `contactId`; a duplicate user (e.g. one made by cart Add New User) returned the order twice, the second POST got **403 `SalesOrder:Exists`**, and fired a **false** "not Transmitted successfully to MITS" alert (SAC100881, SAC100890). Now a correlated subquery picks one email per contact (`ORDER BY isActive DESC, id`). Never join `Users` on `contactId` without collapsing to one row. Duplicate source: [cart Order For](../../../2.0/apps/toga2-commerce/features/cart-order-for-user-and-approving-manager.md), [PEOPLE file](people-file-user-lifecycle.md).
97
+ - **Outbound MITS calls are logged in legacy `Logs.API`** (endpoint `https://itfinance.compass-usa.com/api/app/sales`), not in `Logs_<Tenant>.Api`. Start there to see what was sent.
98
+ - **Two `salesDetails` lines for one kit SalesOrderItem is intentional** — the script adds a configuration line. Not a duplicate.
96
99
  - **`c_preferredVendorId` scalar subquery has no `LIMIT` → SQL 1242 on multi-vendor items.** Any item with >1 active `VendorItem` makes that scalar subquery return >1 row and **aborts the whole run**. This was the latent bug the catalog merge exposed (it fused VendorItems). A one-line `ORDER BY VendorItems.id LIMIT 1` guard fixes it, but the proper two-catalog data fix makes Compass items single-vendor so the guard is not needed.
97
100
  - **Adding a recipient does not turn ClickUp ticketing off.** Routing is both-and, not either-or: `_Worker_Infrastructure_Errors` runs `if ($recipients !== null)` and `if ((bool) $issue->createsClickupTask)` as **two independent** branches. **⚠ The comment above the insert in `tools/mvc/errors/post.php` still claims the opposite** ("it stops creating ClickUp tasks for the dev team and starts emailing these people instead") — describes the old `elseif` form and is **stale**. Trust `Errors.php`.
98
101
  - As of 2026-08-04 prod `Logs.IssueEmailAddress` is empty, so this is the **first business-routed issue on the platform**: `processBusinessIssue()` will execute in production for the first time when this issue next fires. Watch that run rather than assuming it works.
99
102
 
103
+ ## Change history
104
+ - 2026-10-06 — Order query no longer joins `Users` on `contactId` (duplicate users caused double POST + false 403 `SalesOrder:Exists` alerts); noted `Logs.API` and the intentional kit config line. (bala)
105
+
100
106
  ## Related
101
107
  - [Compass MITS PO Transmission to Vendors](mits-po-transmission-to-vendors.md)
102
108
  - [Compass item catalogs & duplicate items](./item-catalogs-and-duplicate-items.md)
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: compass-usa
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-28
9
+ updated: 2026-10-06
10
10
  owners: [bala, ajean, mhammontree]
11
11
  files:
12
12
  - worker2/Worker/Client/Compass/PeopleFile.php
@@ -52,6 +52,8 @@ where `5` is `CONTACT_UNLINK_GRACE_DAYS`. The window exists for the `contactId`-
52
52
  - When HR re-sends that person, the matcher finds nothing, so the row falls through to the **INSERT** path — **bypassing the reactivation branch**.
53
53
  - Result: two `Users` rows for the same human, confirmable by an **identical `c_hrEmpPersonnelNbr`** on both. Email matches too (`Users.email` uses a case-insensitive collation), but personnel number is the stronger key.
54
54
 
55
+ **Second source: the commerce cart "Add New User".** Until 2026-10-06 it checked only `c_hrEmpUsername`, so buyers typing an employee number or email made a new row for an existing person (same email + `contactId`, no `supervisorUserId`). Now it also checks `email`: [cart Order For](../../../2.0/apps/toga2-commerce/features/cart-order-for-user-and-approving-manager.md).
56
+
55
57
  **Username casing is NOT a contributing cause.** Both the lookup build and the post-insert refresh already `strtolower()` the `c_hrEmpUsername` key, so a casing change in the HR file cannot produce a miss. Do not look there.
56
58
 
57
59
  ### Contacts are created BEFORE the Users insert (FK order, fixed 2026-09-23)
@@ -128,5 +130,6 @@ The insert-time hook designed 2026-08-12 was built and grew into a reusable engi
128
130
  - [Users.email as login identity](../../../2.0/apps/_underscore/features/user-email-as-identity-and-password-reset.md)
129
131
 
130
132
  ## Change history
133
+ - 2026-10-06 — Noted cart Add New User as a second source of duplicate `Users` rows (now checks email too, TRUE-82423). (bala)
131
134
  - 2026-09-23 — Recorded that the import is **not** all-or-nothing (post-commit email + SFTP delete run outside the tenant transaction; job 1076884 failed yet kept 460 US / 26 CA users). TRUE-82230 confirmed in prod: 543 `Client_Compass` users created, **zero** null-or-0 `contactId`, two manual ACTION runs ~18s each — needed because the 09:15 cron row (job 1784367) was stranded. (mhammontree)
132
135
  - 2026-09-23 — **Fixed the `Users.contactId` FK write order (TRUE-82230).** Jeff's `NOT NULL` + `Users_ibfk_1` on both tenants broke the importer from 2026-09-22 09:15 (last good run 2026-09-21); the whole run rolled back and Canada never executed. New `insertContactsForNewUsers()` creates the Contact before the `Users` batch and puts `contactId` in the INSERT column list; existing users missing a contact keep the create-then-UPDATE path. Also recorded: `LAST_INSERT_ID()` must be read on the write connection (not `_Query`); `TEST_MODE` vs `DRY_RUN` neither being safe alone; the FIFO/one-file/empty-is-silent-success facts; and that a stale local DB hides this class of bug. Verified against prod-reloaded local schemas: 80 new US / 17 new CA users, 0 with null-or-0 `contactId`. (mhammontree)
@@ -66,7 +66,7 @@ The 2.0 replacement for 1.0's `legacy_import_asn.php` + `asnprocessor/*` (see [S
66
66
  5. **`Run()`** gained `deleteSourceOnSuccess` and `retiredFileNamePatterns` params and skips files modified < 5 min ago. Validates `fileModelClass` (`is_a _Model_Client_File`) and `adapter` (a method on `_Worker_Sftp_Parse`).
67
67
  6. **Kill switch** is 1.0's `[nycdoe] asn_delete_mode` (missing = old immediate delete).
68
68
 
69
- **Manual release of a failed file:** set `Files.c_status='released'`, then queue `Sftp/Import/DeleteSource` for that `filesUuid`.
69
+ **Manual release of a failed file:** see *Failed file* in the runbook below.
70
70
 
71
71
  **Before cutover (open):**
72
72
  - `Run()` has no retry for a lost Parse or lost `DeleteSource` job (only `RunForFile` re-drives). A row stuck `processing` turns `failed` only on the next 1.0 trigger after 30 min.
@@ -79,6 +79,29 @@ Core `2026-09-14a` Lenovo row carries `retiredFileNamePatterns`. Neither `2026-0
79
79
 
80
80
  Tests: `ParseCest.php` (all 4 mfrs, header rejection, comma repair, claim, kept `failed` status) + `ImportDecisionCest.php` (RunForFile decisions, path check, retired names) — see [worker2 Codeception integration tests](../../../2.0/apps/worker2/features/codeception-integration-tests.md).
81
81
 
82
+ ## Activation / deployment runbook (TRUE-80519)
83
+ PRs open 2026-10-06: library, worker2, dbchanges2, _underscore (worker optional for Part A).
84
+
85
+ **Part A — inert (no 1.0 behavior change):**
86
+ 1. dbchanges2 `Client_Nycdoe`, in order: `2026-09-14a`, `2026-09-22a`, `2026-09-22b`, `2026-09-22c`, `2026-09-25a`.
87
+ 2. dbchanges2 `Core` `2026-09-14a` ×4 (Acer/Apple/Lenovo/Lexmark CronJobs, `isActive=0`).
88
+ 3. Confirm `Files` has `c_status`/`c_manufacturer`/`c_dtProcessed` + checksum index, and 4 inactive `Sftp/Import/Run` rows.
89
+ 4. Deploy `_underscore` (Rohan's weekly window; `Model/Nycdoe` File/Case/ServiceRequest — commits `05a98743`, `1223e2a9`).
90
+ 5. Deploy worker2.
91
+ 6. Deploy library + worker with **no** ini change.
92
+ 7. Confirm 1.0 still imports to `AdvanceShippingNoticeQueue`, deletes from SFTP, one `Logs.FileLog` row per file.
93
+
94
+ **Part B — shadow mode (gated delete on):**
95
+ 1. Confirm the 4 Secrets Manager secrets `sftp/nycdoe/<mfr>` exist and WorkloadsRuntime can read them.
96
+ 2. Set `[nycdoe] asn_delete_mode = gated` in `worker/config.worker.ini`; deploy worker.
97
+ 3. Verify: one FileLog row per file, no re-processing/re-email; `Core.WorkerJobs` `Sftp/Import/RunForFile` results `queued`/`in_progress`/`already_done_delete_queued`; `Files.c_status` → `succeeded`; `Sftp/Import/DeleteSource` result `deleted`; files leave SFTP within ~15–30 min; `DOE-OPEN-LENOVO-*` deleted; other unmatched names kept.
98
+
99
+ **Rollback:** remove `asn_delete_mode` (or set `immediate`), deploy worker.
100
+
101
+ **Failed file:** investigate (Tools → Errors / `Logs.Issue`), set `Files.c_status='released'`, queue `Sftp/Import/DeleteSource {clientId: 38, filesUuid}`.
102
+
103
+ **Later (not this deploy):** cutover = turn the 4 crons on + remove NYCDOE processors from 1.0; decide manual ASN file handling; Phase 3 NetSuite; separate tickets for host-key check + FTP password rotation (see *Before cutover* above).
104
+
82
105
  ## Gotchas
83
106
 
84
107
  - **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`.
@@ -93,6 +116,7 @@ Tests: `ParseCest.php` (all 4 mfrs, header rejection, comma repair, claim, kept
93
116
  - **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).
94
117
 
95
118
  ## Change history
119
+ - 2026-10-06 — Activation/deployment runbook (Part A inert, Part B shadow mode, rollback, failed-file release) (mhammontree)
96
120
  - 2026-10-06 — Gated delete built (RunForFile/DeleteSource, Parse claim + status-outside-transaction fix, retired names, `released` status); open cutover items (mhammontree)
97
121
  - 2026-09-29 — Activation plan: gated-delete decision, proposed RunForFile/DeleteSource design, CronJobs rows set isActive=0 (mhammontree)
98
122
  - 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-29
9
+ updated: 2026-10-06
10
10
  owners: [mhammontree, sking]
11
11
  files:
12
12
  - worker/crons/sync/nycdoe/send_ticket_updates.php
@@ -178,7 +178,7 @@ Before this the cron ran the selection query **once per ticket under `LIMIT 1`**
178
178
  All three `error_log()` calls were removed (the per-ticket technician sys_id message, TRUE-82027's "alerted on N failures" line, and one added in TRUE-82129). **1.0 worker crons must produce no output** — they run unattended under CRON and `error_log()` goes to stderr on CLI. Failures surface only in `Logs.Issue` and the throttled alert email. General 1.0 worker rule, not DOE-specific (Mark, 2026-09-18).
179
179
 
180
180
  ### Missing technician `referenceId` is the NORMAL state, not an incident
181
- **265 of 389 (68%) TOGaDesk technicians have no `people.referenceId`** (ServiceNow sys_id), so `u_technician` is left **unchanged** on those pushes — deliberate: blanking it would wipe a technician ServiceNow may legitimately hold. State, hold, ETA and assignment_group still sync. This is why the per-ticket log line was removed. Reconcile with `worker/crons/sync/nycdoe/report_technician_identities.php` / `backfill_technician_identities.php` (needs its own ticket).
181
+ **265 of 389 (68%) TOGaDesk technicians have no `people.referenceId`** (ServiceNow sys_id), so `u_technician` is left **unchanged** on those pushes — deliberate: blanking it would wipe a technician ServiceNow may legitimately hold. State, hold, ETA and assignment_group still sync. This is why the per-ticket log line was removed. Reconcile with `worker/crons/sync/nycdoe/report_technician_identities.php` (read-only classifier; needs its own ticket). `backfill_technician_identities.php` is **deleted on worker branch `TRUE-80519`** (commit `9e1dc9afa`; takes effect when that PR merges) — recover it from git history before that commit if a backfill is ever run. `send_ticket_updates.php:537` comment still points at the deleted file.
182
182
 
183
183
  ### Failure alerting instead of a throw (TRUE-82027)
184
184
  **DECISION (Mark, 2026-09-17): no circuit breaker** — 1.0 is transitioning to 2.0 under a separate task, so the cheap path was chosen.
@@ -270,6 +270,7 @@ The SNOW round-trip note reconciliation (delete-and-reinsert in `process_tickets
270
270
  - `qqStatus()` collapses all recognized hold sub-statuses to base `HOLD` in `repair_orders.status`; the specific hold **sub-reason persists only in `repair_order_notes.newstatus`**. Any status badge / report / filter reading `repair_orders.status` directly cannot distinguish the six hold variants after a recompute.
271
271
 
272
272
  ## Change history
273
+ - 2026-10-06 — `backfill_technician_identities.php` deleted on worker branch TRUE-80519 (effective on merge); git-history pointer added. (mhammontree)
273
274
  - 2026-09-29 — Assignment-group scoping rule + inverted `isValidDepartmentName` + the three competing group definitions; TRUE-82027 verified in prod (marker rows + `Logs.Emails` method). (mhammontree)
274
275
  - 2026-09-25 — TRUE-82332: hold-release no longer overwrites the close `state`; close re-sent when SN not closed; stuck-ticket re-queue recipe; `Logs.API` query shapes. (mhammontree)
275
276
  - 2026-09-18 — TRUE-82129: fixed the by-reference `fetchOne()` notice that killed every run; batch loop + run caps replaced LIMIT 1 + per-ticket sleep; alert emails de-PII'd and escaped; unmapped-group email now grouped per run. (mhammontree)
@@ -81,7 +81,7 @@ The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents +
81
81
  | NetSuite SO create (Stage 3, **freeze**) | `1_send_asn_to_netsuite.php` | every 2 h at :07 |
82
82
  | NetSuite PO/serials (Stage 4) | `2_send_serials_to_netsuite.php` | every 2 h at :27 |
83
83
  | TOGa Desk repair orders (Stage 5) | `3_create_installation_ticket.php` | every 5 min |
84
- | NetSuite Item Fulfillment (Stage 6) | `4_create_ns_item_fulfillment.php` (**paused**, `active: 0`); `5_DOA_…` unscheduled | not running |
84
+ | NetSuite Item Fulfillment (Stage 6) | `4_create_ns_item_fulfillment.php` (**paused**, `active: 0`); `5_DOA_…` unscheduled (moving to `crons/sync/DOA/`, see below) | not running |
85
85
  | ETA → ServiceNow | `send_ticket_updates.php` | every 6 min |
86
86
  | RITM state → ServiceNow | `send_request_item_updates.php` | every 5 min |
87
87
  | Proof of delivery → ServiceNow | `send_nycdoe_proof_of_delivery.php` | every 5 min |
@@ -91,7 +91,7 @@ The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents +
91
91
  | EDI invoices out (NetSuite saved search 2108) | `send_edi_open_invoices.php` | every 2 h at :13 |
92
92
  | Installation schedule reports | `notifications/nycdoe/send_installation_schedules_doe.php` (3 business days ahead, weekends skipped, **holidays NOT skipped**), daily + current-week variants, weekly part-orders | 5–8 AM daily/Mondays |
93
93
 
94
- **Unscheduled / dormant:** `download_tickets*.php` (superseded by `import_inc.php` / `import_asn.php`), `DOA_*` variants, `doe_install_fix.php`, `send_installation_schedules.php` (base variant). Note `sync/syncro/download_tickets.php` in the schedule is the **Syncro** integration, not NYCDOE. A second `crons/infrastructure/import_asn.php` also calls the asnprocessors but is **not scheduled** — only `legacy_import_asn.php` runs.
94
+ **Unscheduled / dormant:** `download_tickets*.php` (superseded by `import_inc.php` / `import_asn.php`), `send_installation_schedules.php` (base variant). **Worker branch `TRUE-80519` (commit `9e1dc9afa`, takes effect when that PR merges):** the 5 DOA files (`5_DOA_set_item_fulfillment_as_shipped.php`, `DOA_3_B_create_installation_ticket.php`, `DOA_3_create_installation_ticket.php`, `DOA_3_create_installation_ticket_savedsearch.php`, `DOA_delete_query_for_doe.php`) move to `crons/sync/DOA/`; `doe_install_fix.php`, `backfill_technician_identities.php`, `test_assignment_revert_guard.php`, `test_multi_po_receipt_resolution.php` are deleted (none scheduled or called; recover from git history before `9e1dc9afa`). Note `sync/syncro/download_tickets.php` in the schedule is the **Syncro** integration, not NYCDOE. A second `crons/infrastructure/import_asn.php` also calls the asnprocessors but is **not scheduled** — only `legacy_import_asn.php` runs.
95
95
 
96
96
  ### The ASN pipeline
97
97
  ```
@@ -269,6 +269,7 @@ All **live** DOE business logic is 1.0. The only other 2.0 code is the in-progre
269
269
  - 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`)
270
270
 
271
271
  ## Change history
272
+ - 2026-10-06 — Dormant DOA files moved to `crons/sync/DOA/` and 4 unused scripts deleted on worker branch TRUE-80519 (effective on merge) (mhammontree)
272
273
  - 2026-10-06 — Stage 1b gated delete mode (`asn_delete_mode`, `releaseSourceFile`, FileLog saved before release) built, not deployed (mhammontree)
273
274
  - 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)
274
275
  - 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)
@@ -16,7 +16,7 @@ project: _Underscore
16
16
  client: quad
17
17
  type: profile
18
18
  status: active
19
- updated: 2026-09-28
19
+ updated: 2026-10-06
20
20
  owners: ["jcardinal", "bala", "apeterson", "ajean"]
21
21
  files: []
22
22
  related:
@@ -172,4 +172,5 @@ Client-specific DB change-sets live in `dbchanges2/Client_Quad/`.
172
172
  nav grants for Items/Vendor Items so Quad's own `@quad.com` users (five of the seven holders of
173
173
  role 7 `Global Admin`) do not get them. Migration written, **not yet run**. See
174
174
  [Quad internal-admin navigation](./workflows/granting-internal-admin-navigation.md).
175
+ - **Quad users have no managers, by design (decided 2026-10-06).** Approval is per location: location users see and approve orders. The commerce cart hides manager/delegate fields for Quad. **Never apply manager-required rules to Quad** (see [cart Order For approving-manager rule](../../2.0/apps/toga2-commerce/features/cart-order-for-user-and-approving-manager.md)).
175
176
  - This profile is a starting point; expand as more Quad-specific behavior is captured.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.960",
3
+ "version": "1.0.962",
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",