toga-ai 1.0.712 → 1.0.713

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.
@@ -3,11 +3,12 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
6
- | [Re-runnable additive migrations (uuid4 in SQL, INSERT guards, the DISTINCT trap, conditional ALTERs)](features/rerunnable-additive-inserts.md) | Most `dbchanges2` files are **additive data grants** run by hand against production, often more than once (once per environment, or twice because someone was no | dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql, dbchanges2/Client_Compass/2026-08-28 - GrantViewOrdersRoleSalesOrdersNavigation.sql, dbchanges2/Core/2026-08-27a - Insert - Netsuite Location SyncAll CronJob.sql |
6
+ | [Re-runnable additive migrations (uuid4 in SQL, INSERT guards, the DISTINCT trap, conditional ALTERs)](features/rerunnable-additive-inserts.md) | Most `dbchanges2` files are **additive data grants** run by hand against production, often more than once (once per environment, or twice because someone was no | dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql, dbchanges2/Core/2026-08-27a - Insert - Netsuite Location SyncAll CronJob.sql |
7
7
  | [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | toga25-supply/db-migrations/PLAYBOOK.md, toga25-supply/db-migrations/SURFACE-FEATURE-RUNBOOK.md, dbchanges2/Client_Compass/, dbchanges2/Client_CompassCanada/, dbchanges2/Client_Quad/, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Core/2026-08-24b - SalesOrderStatusFilterSurfaceSeed.sql, dbchanges2/Client_Compass/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_CompassCanada/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_Quad/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Client_Nychh/2026-08-24b - SalesOrderStatusFilterHides.sql, dbchanges2/Core/2026-08-21 - SalesOrderDecisionSurfacesReseed.sql, dbchanges2/Core/2026-08-24 - RestoreApproveDenyRowActionsVisibility.sql, dbchanges2/Client_Quad/2026-08-24 - ProdPortApprovePoNumberEnabledRule.sql, dbchanges2/Client_Quad/2026-07-21b - ApproveDisabledTooltipPoNumber.sql, dbchanges2/Client_Quad/2026-08-21 - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql, dbchanges2/Client_CompassCanada/2026-08-24 - ProdPortApprovalsGateAndApproveStepTwoRule.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql, dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql, dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Core/2026-07-23a - PoNumberDetailFieldValueKey.sql, dbchanges2/Core/2026-07-29a - SalesOrderApprovalDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-05b - ApprovalDetailsShippingMethodConcatCharge.sql, dbchanges2/Core/2026-08-14a - NoteBadgesVocabularySurface.sql, dbchanges2/Client/2026-08-14a - NoteBadgeThemeTokens.sql, dbchanges2/Client/2026-08-14b - NoteBadgeUserColorFix.sql, dbchanges2/Client_Compass/2026-08-14a - NoteBadgeDelegateThemeTokens.sql, dbchanges2/Client_CompassCanada/2026-08-14a - NoteBadgeDelegateThemeTokens.sql, dbchanges2/Core/2026-08-26a - ServiceRequestRecordHeaderSurfaceSeed.sql, dbchanges2/Core/2026-08-26b - ServiceRequestDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql |
8
8
  | [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | > **A local browser wizard now automates this.** Steps 2–9 below (create DBs, generate Core/API > inserts, append to `Clients_Db.txt`) — plus the dbchanges2 bla | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
9
9
  | [Auditing a client DB that drifted from its models (partially applied module migration)](workflows/client-schema-drift-audit.md) | A recurring 2.0 failure mode: **one client's database drifts from what the PHP models declare**, usually because a `_modules/<module>/` migration was applied to | dbchanges2/Client_Growrk/2026-05-28.sql, dbchanges2/Client_Growrk/2026-08-10c - GrowrkServiceRequestCustomFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10d - GrowrkServiceRequestTypeAndDispositionSeeds.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Growrk/2026-08-10 - GrowrkUnitInventoryFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10b - GrowrkUnitItemDescriptionAcl.sql, dbchanges2/Client_Growrk/_modules.txt |
10
10
  | [Framework branch running AHEAD of schema — environment-wide 1054/EO-1 after a deploy](workflows/framework-branch-ahead-of-schema.md) | A **third** kind of 2.0 schema drift, distinct from the two already documented: nobody's database went backwards — **the code went forwards**. | _underscore/Model/Core/RecordField.php, _underscore/Model/Client/TableViewField.php, _underscore/Model/Client/TableView.php, _underscore/Model/Core/Surface.php, _underscore/Model/Client/Urgency.php, api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-08-14c - RecordFieldsValidationMetadata.sql, dbchanges2/Client/2026-08-14a - RecordFieldSettingsClientTable.sql, dbchanges2/Client/2026-08-13a - UserNavPreferencesClientTable.sql, dbchanges2/Client/2026-08-13c - PersonaNavVisibilityClientTable.sql |
11
11
  | [Local vs prod MySQL config parity — why “it passed locally” is not evidence](workflows/local-vs-prod-mysql-config-parity.md) | Several migration failures that look like "prod-only bugs" are actually **per-machine MySQL server-configuration differences**. | |
12
12
  | [Repairing non-prod metadata drift (works in prod, broken in beta/dev-sandbox)](workflows/nonprod-metadata-drift-repair.md) | Almost all 2.0 platform behavior is **metadata** — `Core.Records`/`RecordFields`, `Core.RecordScripts`, `Core.ApiPayloadInterceptors`, and per-client `Acl*` row | dbchanges2/Core/2026-06-30a - ItemFulfillmentStageDefaultInterceptor.sql, dbchanges2/Client_Compass/2026-08-06 - RemoveBrokenSalesOrderItemPostPostInterceptor.sql, dbchanges2/Core/2026-07-16a - TrackingNumberSignatureTypeRecordField.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Aig/_modules.txt, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql, api2/Config/beta.ini, api2/Config/sandbox-dev.ini, dbchanges2/Client/2026-07-22a - EntitlementServiceAddressId.sql, dbchanges2/Client/2026-07-23a - EntitlementServiceAddressIdFieldPermission.sql |
13
+ | [Targeting the right database (tenant restores, and running a change file against the wrong DB)](workflows/targeting-the-right-database.md) | Two ways to point at the wrong database — a dump taken from the wrong host, and a change file piped into the wrong schema. | api2/Config/production.ini, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql |
13
14
  | [Verifying whether a dbchanges2 migration actually ran in an environment](workflows/verifying-a-migration-ran.md) | dbchanges2 has no execution ledger you can query — "did this file run here?" has to be answered from the **rows the file would have produced**. | Client/2026-08-11b - SalesOrderPurchaseOrdersField.sql, Client/2026-08-13 - UnitsForItemsPO_SalesOrderItemsJoin.sql |
@@ -6,11 +6,10 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-28
10
- owners: [bala, kyalamarthi]
9
+ updated: 2026-08-31
10
+ owners: [bala, kyalamarthi, apeterson]
11
11
  files:
12
12
  - dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql
13
- - dbchanges2/Client_Compass/2026-08-28 - GrantViewOrdersRoleSalesOrdersNavigation.sql
14
13
  - dbchanges2/Core/2026-08-27a - Insert - Netsuite Location SyncAll CronJob.sql
15
14
  related:
16
15
  - ../architecture.md
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-08-31
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - toga25-supply/db-migrations/PLAYBOOK.md
@@ -563,7 +563,10 @@ All four gated buttons (17/18/19/21) carry the **Core** rule `order._status in [
563
563
  so on a **canceled** order the approval-workflow button is **hidden**.
564
564
 
565
565
  **Next free Core ids in PROD, 2026-08-28:** `Surfaces` **47**, `SurfaceElements` **170**, `Messages`
566
- **227**, `Actions` **17**. (Re-verify per the 🚨 rule below before writing a seed — "next free" vs
566
+ **227**, `Actions` **17**. **Superseded — on 2026-08-31 (sandbox-client, freshly reset from prod) the
567
+ MAX ids were `Surfaces` 52 / `SurfaceElements` 189 / `Messages` 260, and `Core/2026-08-28a`+`b` then
568
+ claimed `Surfaces` 53–57 / `SurfaceElements` 190–206 / `Messages` 261–284.** (Re-verify per the 🚨
569
+ rule below before writing a seed — "next free" vs
567
570
  "highest used" is exactly what caused the 2026-08-24 collision.)
568
571
 
569
572
  **Per-tenant overrides, as they actually exist in prod:**
@@ -605,6 +608,41 @@ the LIST surface + 4 record-modal SECTION surfaces + the `transfer-order-stage`
605
608
  verified free on **local AND production** before authoring. See
606
609
  [Transfer Orders page](../../toga25-supply/features/transfer-orders-page.md).
607
610
 
611
+ #### ⚠ Those blocks were RENUMBERED on 2026-08-31 — a reservation expires when someone else ships
612
+
613
+ The 2026-08-28 blocks above were reserved from a **2026-08-28 snapshot** of production, and were
614
+ **claimed by another feature before the transfer-order files shipped**: `sales-order-denial-details`
615
+ took `Surfaces` **47**, `SurfaceElements` **170–176** and `Messages` **227–232**. Re-verified against
616
+ sandbox-client (freshly reset from production) on 2026-08-31, where MAX ids were `Surfaces` **52** /
617
+ `SurfaceElements` **189** / `Messages` **260**, the two transfer-order files were renumbered to a
618
+ contiguous block above MAX:
619
+
620
+ | | Reserved 2026-08-28 (stale) | **Live, as committed 2026-08-31** |
621
+ |---|---|---|
622
+ | `Core.Messages` | 227–260 | **261–284** |
623
+ | `Core.Surfaces` | 47–52 | **53–57** |
624
+ | `Core.SurfaceElements` | 170–189 | **190–206** |
625
+
626
+ `Surfaces` 48–51, `SurfaceElements` 177–186 and `Messages` 233–250 are deliberately left as **unused
627
+ gaps** rather than back-filled — a contiguous block above MAX is trivially verifiable and trivially
628
+ reversible; hunting free holes is neither.
629
+
630
+ Two durable rules:
631
+
632
+ 1. **A reserved block is a snapshot, not a lock.** Nothing in Core enforces it. **Re-verify the block
633
+ is still free immediately before the file runs**, not just when it is written, and say so in the
634
+ file header. The longer a seed sits unmerged, the more likely its block is gone.
635
+ 2. **Renumbering is cheap; colliding is not.** Because these seeds use literal ids with plain
636
+ `INSERT`, a stale block fails loudly on the primary key — which is the designed behavior and the
637
+ reason `INSERT IGNORE` is banned here (see the 2026-08-24 incident below). Bump to a fresh block
638
+ above MAX rather than trying to reuse the gap.
639
+
640
+ > These explicit ids are **correct and required**, not a portability bug. `Core.Surfaces` /
641
+ > `Core.SurfaceElements` / `Core.Messages` are reserved-literal tables precisely so a `Client_*`
642
+ > override file can inline them without a cross-cluster `Core.*` read. Do not "fix" a surface seed
643
+ > into a `messageKey`-resolving `INSERT … SELECT`: the client-side references have no way to resolve
644
+ > a Core id at runtime.
645
+
608
646
  **⚠ The reserved blocks are not an inventory of every surface id — pre-2026-08-21 surfaces sit
609
647
  outside them.** The reserved-id discipline begins at the reseed, so surfaces seeded before it took
610
648
  plain `AUTO_INCREMENT` ids: the **`navigation`** TAB_STRIP is `Core.SurfaceElements` **109–114**
@@ -978,6 +1016,15 @@ hits `Duplicate entry '251' for key 'messages.PRIMARY'`. The resolution that kee
978
1016
  `config.valueKey` when it lives in the page.
979
1017
 
980
1018
  ## Change history
1019
+ - 2026-08-31 — **Renumbered the transfer-order reserved blocks** to `Messages` **261–284**,
1020
+ `Surfaces` **53–57**, `SurfaceElements` **190–206**: the 2026-08-28 blocks were reserved from a
1021
+ snapshot and `sales-order-denial-details` claimed `Surfaces` 47 / `SurfaceElements` 170–176 /
1022
+ `Messages` 227–232 first. New rule — **a reserved block is a snapshot, not a lock; re-verify it is
1023
+ free immediately before the file RUNS, not just when it is written**, and bump to a contiguous
1024
+ block above MAX (leaving gaps) rather than back-filling. Also recorded explicitly that literal
1025
+ Core ids in a surface seed are **required, not a portability bug**, so nobody "fixes" one into a
1026
+ `messageKey`-resolving `INSERT … SELECT`. From `Core/2026-08-28a`/`b`, committed in `dbchanges2`
1027
+ `1c3fe2e`. (apeterson)
981
1028
  - 2026-08-28 — **Extended the transfer-order reserved blocks** to `Messages` **227–260**, `Surfaces`
982
1029
  **47–52**, `SurfaceElements` **170–189** (`Core/2026-08-28b`, the `transfer-orders-list-actions`
983
1030
  BUTTON_BAR; all ids verified free on local **and** production first). Three authoring rules added:
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: "Targeting the right database (tenant restores, and running a change file against the wrong DB)"
3
+ framework: "2.0"
4
+ repo: dbchanges2
5
+ project: Database Changes
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-08-31
10
+ owners: [apeterson]
11
+ files:
12
+ - api2/Config/production.ini
13
+ - dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql
14
+ related:
15
+ - ../architecture.md
16
+ - ./verifying-a-migration-ran.md
17
+ - ../features/surface-layer-schema.md
18
+ - ../../../clients/nychh/profile.md
19
+ ---
20
+
21
+ ## Summary
22
+
23
+ Two ways to point at the wrong database — a dump taken from the wrong host, and a change file piped
24
+ into the wrong schema. **Both "succeed."** One of them silently overwrites a shared environment with
25
+ your laptop's data; the other fails with an error that names the wrong problem. This is the
26
+ procedure that catches each before it costs a day.
27
+
28
+ ## Where the databases are
29
+
30
+ Connection **locations** only — passwords live in the config file, never here.
31
+
32
+ | Cluster | Host | Config section (`api2/Config/production.ini`) |
33
+ |---|---|---|
34
+ | Core | `reader1.core.database.togahub.com` | `[database]` |
35
+ | Client (tenant) — read | `reader1.client.database.togahub.com` | `[databaseClient]` |
36
+ | Client (tenant) — write | `writer.client.database.togahub.com` | `[databaseClient]` |
37
+ | Logs | — | `[databaseLogs]` |
38
+ | Client sandbox | `client.sandbox.database.togahub.com` | (sandbox config) |
39
+
40
+ Username is `admin`. **The password is in the matching section of
41
+ `api2/Config/production.ini` — read it from there; never paste it anywhere, including here.**
42
+
43
+ **Keep the password out of your shell history and out of `ps`** by building a throwaway
44
+ credentials file from the ini instead of passing `-p` on the command line:
45
+
46
+ ```bash
47
+ # awk the password out of the [databaseClient] section into a 0600 defaults file
48
+ umask 077
49
+ awk '/^\[databaseClient\]/{s=1} s&&/^password/{print; exit}' Config/production.ini \
50
+ | sed 's/ *= */=/' > /tmp/my.cnf.$$
51
+ printf '[client]\nhost=reader1.client.database.togahub.com\nuser=admin\n' | cat - /tmp/my.cnf.$$ > /tmp/creds.cnf
52
+ mysql --defaults-extra-file=/tmp/creds.cnf Client_Nychh -e 'SELECT COUNT(*) FROM TransferOrders'
53
+ ```
54
+
55
+ Delete the file when you are done.
56
+
57
+ ## Steps — before you trust ANY tenant dump
58
+
59
+ 1. **Prove which environment you actually reached, with a row count known to DIFFER between
60
+ environments.** Not a table list, not a schema diff — a *count*. Worked example (2026-08-31):
61
+ `Client_Nychh.TransferOrders` = **1,927 rows in production** and **24 `TO-SEED-*` rows locally**.
62
+ One `SELECT COUNT(*)` settles it.
63
+ 2. **Re-check the host you typed.** `-h127.0.0.1` / `-hlocalhost` in a command you believed was
64
+ pointed at prod is the whole failure mode; a defaults file (above) removes the chance to fumble it.
65
+ 3. **Restore.** Drop + restore will run perfectly whatever the source was — success proves nothing.
66
+ 4. **Verify the restored copy against the SOURCE you intended**, repeating step 1 against the
67
+ restored schema.
68
+
69
+ ## Gotchas
70
+
71
+ - 🚨 **A wrong-source restore is silent and total.** A dump taken from `127.0.0.1`, believed to be
72
+ production, was restored onto sandbox-client `Client_Nychh`. Both the drop and the restore
73
+ succeeded; sandbox simply became a copy of the developer's laptop, carrying local `TO-SEED-*`
74
+ transfer orders into a shared environment. **The tell was that sandbox matched local EXACTLY** —
75
+ 353 tables, identical row counts. An exact match with your local DB is evidence of a bad restore,
76
+ not of a good one.
77
+ - **A small post-restore drift vs. live prod is EXPECTED, not an error.** `--single-transaction`
78
+ makes the dump a point-in-time snapshot, so a live table keeps moving after it: `Units` read 8,627
79
+ on prod and 8,618 on the restored sandbox. Do not chase that; only a *category* difference
80
+ (missing tables, order-of-magnitude counts) means the restore was wrong.
81
+ - 🚨 **The `dbchanges2` folder name IS the target database — pipe each folder to its own DB.**
82
+ `Core/` → `Core`, `Client_<Name>/` → that one tenant, `Client/` → **every** tenant. Files reference
83
+ tables **unqualified** by design (the cluster-isolation rule), so running a `Core/` file against a
84
+ client DB does not complain about the database — it fails as
85
+ `Table 'Client_Nychh.Messages' doesn't exist`, which names a missing table rather than the real
86
+ mistake. If you see "table doesn't exist" for a table you know is in Core, check which schema you
87
+ piped the file into before you touch the SQL.
88
+ - **The repo is evidence of intent, never of what an environment contains.** Confirming a file ran
89
+ is its own procedure — see
90
+ [Verifying whether a migration actually ran](./verifying-a-migration-ran.md).
91
+
92
+ ## Change history
93
+ - 2026-08-31 — Initial. Captured after a dump taken from `127.0.0.1` was restored onto sandbox-client
94
+ `Client_Nychh` believing it was production (the tell: sandbox matched local exactly). Records the
95
+ verify-by-differing-row-count check, the `--single-transaction` snapshot-drift caveat, the
96
+ prod/sandbox cluster hostnames + which config section holds each password, the
97
+ `--defaults-extra-file` recipe that keeps the password out of history and `ps`, and the
98
+ folder-name-is-the-target-database rule with its misleading "table doesn't exist" error.
99
+ (apeterson)
@@ -6,7 +6,7 @@ project: TOGa Blox
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-08-27
9
+ updated: 2026-08-31
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - toga-blox/.github/workflows/publish.yml
@@ -85,6 +85,14 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
85
85
 
86
86
  ## Gotchas
87
87
 
88
+ - **⚠ There is a STALE UNDERSCORED dist-tag on the registry — `_sandbox-client` (with the
89
+ underscore) is NOT the channel.** Verified 2026-08-31, the registry carries **both**
90
+ `_sandbox-client` = `1.0.316-sandbox-client.1` (vestigial, predates the current pipeline) **and**
91
+ `sandbox-client` = `1.1.1-sandbox-client.140` (the live channel head). Branch `_<mode>` maps to
92
+ channel `<mode>` with the underscore **stripped** (same convention as `amplify.yml`), so the
93
+ **branch** `_sandbox-client` tracks the **tag** `sandbox-client`. Asking npm for "the latest
94
+ `_sandbox-client` tag" literally installs something **139 publishes behind**. Always resolve the
95
+ head with `npm view @agilant/toga-blox dist-tags` and install the underscore-less name.
88
96
  - **Publish before build.** `npm install @agilant/toga-blox@<mode>` fails `ETARGET No matching
89
97
  version` in the app build until this repo has published that channel. GitHub Actions runs the
90
98
  workflow version *on the pushed branch*, so the branch must carry the current `publish.yml`.
@@ -192,6 +200,10 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
192
200
  you push to a release branch.
193
201
 
194
202
  ## Change history
203
+ - 2026-08-31 — Recorded the **stale underscored dist-tag trap**: the registry carries both
204
+ `_sandbox-client` (`1.0.316-sandbox-client.1`, vestigial) and `sandbox-client`
205
+ (`1.1.1-sandbox-client.140`, live head). Installing the underscored name silently pins 139
206
+ publishes back. (apeterson)
195
207
  - 2026-08-27 — Released blox **1.1.0** on `_production` (published `1.1.1-production.139`) and fixed
196
208
  a shipped-broken tarball: the `feature-new-table` → `_production` merge left `global.css` at the
197
209
  repo root with `import "../global.css"`, so the emitted `dist/index.js` referenced a path outside
@@ -6,7 +6,7 @@ project: TOGa Blox
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-08-31
10
10
  owners: [tcox, apeterson]
11
11
  files:
12
12
  - toga-blox/package.json
@@ -88,6 +88,29 @@ dedupe: [
88
88
  - **This only reproduces on machines using the local checkout**, so across a team it reads as an
89
89
  *intermittent* bug that "works for everyone else." A `QueryClient`/context error with the provider
90
90
  visibly mounted is almost always this.
91
+ - **Version parity is a red herring — module IDENTITY is what matters.** In the reproduction both
92
+ copies were `@tanstack/react-query` **5.101.4**. Identical versions still mean two physical
93
+ directories, two module instances, and two React contexts. Do not clear the theory because
94
+ `npm ls` shows one version.
95
+ - **`dedupe` alone was not sufficient here — alias too.** Follow-up 2026-08-31: pin every
96
+ **context-bearing** peer with **both** `resolve.alias` (absolute path into the app's
97
+ `node_modules`, the same treatment `react`/`react-dom` already had) **and** `resolve.dedupe`:
98
+ `@tanstack/react-query`, `@tanstack/react-query-persist-client`, `@tanstack/react-table`,
99
+ `react-hook-form`. `react-hook-form` was nested under blox as well — the same latent bug for
100
+ `BaseInput` / `useFormContext`, just not throwing yet.
101
+ - **`optimizeDeps.include` is why the throw appears to come from inside blox.** With blox in
102
+ `optimizeDeps.include`, esbuild pre-bundles blox **together with its nested copy**, so the error
103
+ surfaces from `@agilant_toga-blox.js` rather than from app code, and the `?v=<hash>` in the error
104
+ URL is the pre-bundle cache key (which is why a config change with a stale
105
+ `node_modules/.vite` looks like it did nothing).
106
+ - **Verification technique — prove there is exactly one copy.** After rebuilding deps, confirm that
107
+ **exactly one** chunk under `node_modules/.vite/deps` contains the string `No QueryClient set`,
108
+ then confirm `@agilant_toga-blox.js`, `@tanstack_react-query.js` and
109
+ `@tanstack_react-query-persist-client.js` all import **that same** chunk. Before the fix blox
110
+ imported its own.
111
+ - **Installing blox from the registry replaces the symlink with a real directory**, which removes
112
+ this root cause outright — the alias/dedupe belt-and-braces is what keeps the *linked* dev loop
113
+ safe.
91
114
 
92
115
  ### 3. Disable blox's PostCSS config while linked
93
116
 
@@ -119,6 +142,14 @@ The consumer reads blox's `dist`, so rebuild blox after every change.
119
142
  drags every component's CSS into the consumer bundle.
120
143
 
121
144
  ## Change history
145
+ - 2026-08-31 — Closed out the duplicate-`react-query` case with the parts that made it hard:
146
+ **both copies were the SAME version (5.101.4)** — identity, not version, is what breaks context;
147
+ `optimizeDeps.include` pre-bundles blox with its nested copy so the throw appears to originate
148
+ inside `@agilant_toga-blox.js`; the fix needs `resolve.alias` (absolute path) **as well as**
149
+ `resolve.dedupe` for every context-bearing peer (react-query, react-query-persist-client,
150
+ react-table, **react-hook-form** — also nested, same latent bug for `BaseInput`); and a concrete
151
+ one-copy verification (exactly one `.vite/deps` chunk containing `No QueryClient set`, imported by
152
+ blox and both react-query chunks). (apeterson)
122
153
  - 2026-08-28 — Added the Vite half of the one-copy rule: **every peer carrying a React context must
123
154
  be in `resolve.dedupe`**, not just React. `toga25-supply` threw "No QueryClient set" **with
124
155
  `QueryClientProvider` visibly in the stack** because `@tanstack/react-query` was missing from
@@ -3,7 +3,7 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [TOGa 2.5 Supply — Architecture](architecture.md) | `toga25-supply` ("TOGa 2.5 Supply") is the **React/TypeScript frontend** for the 2.0 Supply application — an iteration and improvement of `toga2-supply`. | toga25-supply/src/main.tsx, toga25-supply/src/App.tsx, toga25-supply/src/routes.tsx, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/layout/, toga25-supply/src/pages/, toga25-supply/src/hooks/useTableCellInteractions.ts |
6
- | [Action-Button Rule Engine (Flag / Rule grammar)](features/action-button-rule-engine.md) | A declarative, fully config-driven rule engine that resolves the boolean-ish flags (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. | toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
6
+ | [Action-Button Rule Engine (Flag / Rule grammar)](features/action-button-rule-engine.md) | A declarative, fully config-driven rule engine that resolves the boolean-ish flags (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. | toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.test.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalFlowDetailInputs.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASS/approvalActionFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASSCANADA/approvalActionFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
7
7
  | [Client API-Fetch Fields (useClientApiFields / apiFields.json)](features/client-api-fetch-fields.md) | The mechanism that resolves a client's **API-fetch projection** — which `fields` / `ojoin` / `join` / `where` to request from the TOGa API for a given page — fr | toga25-supply/src/fieldsConfig/resolveApiConfig.ts, toga25-supply/src/fieldsConfig/useClientApiFields.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx |
8
8
  | [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/ |
9
9
  | [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql |
@@ -6,16 +6,22 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-25
9
+ updated: 2026-08-31
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts
13
+ - toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.test.ts
13
14
  - toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts
15
+ - toga25-supply/src/layout/RecordApprovalModal/view/ApprovalFlowDetailInputs.tsx
16
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASS/approvalActionFields.json
17
+ - toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASSCANADA/approvalActionFields.json
14
18
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx
15
19
  - toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx
16
20
  related:
17
21
  - client-configurable-fields.md
22
+ - record-modals-and-nested-tables.md
18
23
  - ../architecture.md
24
+ - ../../../clients/compass-usa/features/approval-decision-flow.md
19
25
  ---
20
26
 
21
27
  ## What it is
@@ -55,8 +61,11 @@ type FieldOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte"
55
61
  - **`RuleContext`** is `{ order, currentStage?, stages? }`. `currentStage`/`stages` carry
56
62
  approval-workflow state when available.
57
63
  - **Named rules** (`{ type }`) are bespoke leaf checks that a field predicate cannot express
58
- — they live in the `NAMED_RULES` registry. Currently only `stepTwoAssigned` (walks the
59
- approval stages to confirm a step-2 assignee exists). An unknown `type` resolves to `true`.
64
+ — they live in the `NAMED_RULES` registry. Members (both walk the approval stages, which a
65
+ field predicate cannot express):
66
+ - `stepTwoAssigned` — a step-2 assignee exists.
67
+ - `stepOneUndecided` — step 1 carries **no** decision yet.
68
+ An unknown `type` resolves to `true`.
60
69
 
61
70
  ### Resolution primitives
62
71
 
@@ -78,6 +87,25 @@ type FieldOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte"
78
87
  It returns a new `tenantFields` with a patched `recordActionFields.actionOptions`. Callers:
79
88
  `useSalesOrderRecordModalLayoutModel.tsx` and `useApprovalModalViewModel.tsx`.
80
89
 
90
+ ### It also resolves flags on approval-workflow STAGE INPUTS
91
+
92
+ `buildPatchedTenantFields` additionally walks
93
+ `recordActionFields.approvalWorkflow.stages[].inputs[]` and resolves each input's flags the same
94
+ way. This placement is deliberate and is the layering rule to preserve: **rule evaluation stays in
95
+ the `pages/SalesOrders` layer, so the shared `layout/RecordApprovalModal` receives only plain
96
+ booleans and holds no tenant logic.** A shared-layout component must never import the rule engine
97
+ or read tenant config; if it needs a new gate, resolve the flag here and pass it down.
98
+
99
+ `ApprovalFlowDetailInputs.tsx` consumes one such resolved boolean: a resolved `isEnabled === true`
100
+ on a stage's `assignedTo` input **overrides the decided-stage lock** —
101
+
102
+ ```ts
103
+ isAssignedToDisabled = areInputsDisabled && !isAssignedToUnlocked
104
+ ```
105
+
106
+ The note textarea and the approve/deny controls stay locked regardless, so a recorded decision can
107
+ never be flipped — only the assignee is reopened.
108
+
81
109
  ### Back-compat shim
82
110
 
83
111
  The previous single-purpose `EnableRule` grammar (`alwaysEnabled`, `requireStepTwoAssigned`,
@@ -103,6 +131,14 @@ Retire the shim once all configs express flags as `Rule`.
103
131
 
104
132
  ## Gotchas
105
133
 
134
+ - **⚠ Both Compass `approvalActionFields.json` files repeat the heading string
135
+ `"Approval: Manager Approval"` on step 1 AND step 2** (in the `MANAGER` role block). A
136
+ text/heading-anchored edit therefore silently patches the **wrong stage**. Target stages by
137
+ **structure** (`stage.valueKey === "2"`) and assert placement afterwards.
138
+ - **A tenant opts into a stage-input gate by config only.** Adding
139
+ `"isEnabled": { "type": "stepOneUndecided" }` to a step-2 `assignedTo` input changes that tenant
140
+ and nothing else: tenants that omit the key hit `resolveFlag`'s `false` fallback and keep their
141
+ existing behaviour exactly (DEFAULT and QUAD were left untouched for this reason).
106
142
  - **`recordActionFields` is the current key** (renamed from `approvalActionFields` /
107
143
  `orderViewFields`-era naming). `buildPatchedTenantFields` reads
108
144
  `recordActionFields.actionOptions`; missing config falls back to `[]`.
@@ -116,4 +152,17 @@ Retire the shim once all configs express flags as `Rule`.
116
152
  default.
117
153
 
118
154
  ## Change history
155
+ - 2026-08-31 — **Added named rule `stepOneUndecided` and extended `buildPatchedTenantFields` to
156
+ resolve flags on `approvalWorkflow.stages[].inputs[]`**, so a VIP-pre-approved step-2 manager can
157
+ be reassigned (Compass USA + Compass Canada). `ApprovalFlowDetailInputs` now treats a resolved
158
+ `isEnabled === true` on a stage's `assignedTo` as an override of the decided-stage lock while the
159
+ note and approve/deny controls stay locked. Both tenants opt in via config only (step-2
160
+ `assignedTo`, ADMIN + MANAGER blocks); DEFAULT and QUAD unchanged. **The rule deliberately does
161
+ NOT key on `isPreApproved`** — blox derives that solely from
162
+ `!!stage.ApprovalDecisionTypes.name`, which is NULL on precisely the broken orders (see
163
+ [Compass Approval-Decision Flow](../../../clients/compass-usa/features/approval-decision-flow.md));
164
+ "step 1 undecided" is the safe equivalent because an order cannot legitimately be past step 2
165
+ while step 1 is pending. Recorded the duplicate-heading trap in both Compass config files and the
166
+ keep-rules-out-of-`RecordApprovalModal` layering rule. Verified: `tsc -b` clean, 44/44 vitest
167
+ (10 new in `evaluateEnableRule.test.ts`), `vite build --mode sandbox-client` clean. (apeterson)
119
168
  - 2026-06-25 — Documented the generic Flag/Rule engine: combinators, field predicates, named-rule registry, `resolveFlag`/`buildPatchedTenantFields`, and the legacy `EnableRule` back-compat shim. (apeterson)
@@ -238,6 +238,16 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
238
238
  join is not feasible for a list, so that column needs the backend field (promote `_purchaseOrders`
239
239
  to base `_Model_Client_SalesOrder`) or be hidden for Quad. **Not addressed** — open follow-up.
240
240
 
241
+ - **A decided stage's `assignedTo` can be selectively UNLOCKED — and the emitted PUT must stay
242
+ assignee-only.** `ApprovalFlowDetailInputs` unlocks just the assignee when the stage input's
243
+ resolved `isEnabled` is `true` (Compass USA/Canada step 2; see
244
+ [Action-Button Rule Engine](action-button-rule-engine.md)), leaving note + approve/deny locked.
245
+ The payload consequence is load-bearing: the request carries **only `assignedToUserId` and never
246
+ `isApproved`**, because `_Model_Compass_ApprovalDecision::_evaluateManagerVip` **early-returns
247
+ when `isset($api->httpPayload->isApproved)`**. Keeping `isApproved` out is what lets `postPut` →
248
+ `handleManagerReassignment` run and revoke a VIP auto-approval for a non-VIP / over-limit new
249
+ manager. Adding `isApproved` "for completeness" would silently disable that backend rule.
250
+
241
251
  - **ONE `TableRecordModal` must own every state — loading, error and loaded.** Rendering a second
242
252
  `TableRecordModal` for the loaded state puts a **different element type at the same position in the
243
253
  tree**, so React unmounts the first and mounts the second: the CSS keyframe restarts and the user
@@ -252,6 +262,9 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
252
262
  [Transfer Orders page](transfer-orders-page.md).
253
263
 
254
264
  ## Change history
265
+ - 2026-08-31 — Recorded the **selectively-unlocked `assignedTo` on a decided approval stage** and
266
+ the rule that its PUT must remain assignee-only (`isApproved` present makes
267
+ `_evaluateManagerVip` early-return, killing the reassignment revoke path). (apeterson)
255
268
  - 2026-08-28 — Added two modal rules confirmed on the Transfer Orders build: **one
256
269
  `TableRecordModal` owns loading/error/loaded** (a second one at the same tree position swaps
257
270
  element types and replays the slide-up animation), and a record whose children are **not** declared
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-08-31
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - toga25-supply/src/pages/TransferOrders/TransferOrders.tsx
@@ -115,9 +115,22 @@ bind to whichever Locations join happened to be first.
115
115
 
116
116
  ## The Core surface vertical
117
117
 
118
- Reserved id blocks (verified free on local, sandbox-client **and** production before writing):
119
- **`Messages` 227–250, `Surfaces` 47–51, `SurfaceElements` 170–186**, all literal ids with plain
120
- `INSERT` — never `INSERT IGNORE`.
118
+ Reserved id blocks, **as committed** (`dbchanges2` `1c3fe2e`, 2026-08-31): `Core/2026-08-28a` +
119
+ `2026-08-28b` together claim **`Messages` 261–284, `Surfaces` 53–57, `SurfaceElements` 190–206**, all
120
+ literal ids with plain `INSERT` — never `INSERT IGNORE`.
121
+
122
+ ⚠ **These are NOT the 227–250 / 47–51 / 170–186 blocks the first draft reserved.** Those were taken
123
+ from a 2026-08-28 prod snapshot and `sales-order-denial-details` shipped into them first
124
+ (`Surfaces` 47, `SurfaceElements` 170–176, `Messages` 227–232). Re-verified on sandbox-client
125
+ (freshly reset from prod) 2026-08-31 — MAX was `Surfaces` 52 / `SurfaceElements` 189 / `Messages`
126
+ 260 — and renumbered to a contiguous block above MAX, leaving the old range as unused gaps. Any
127
+ surface/element id quoted below is from the original draft numbering; **read the file, not this
128
+ table, for the live id**. Re-verify the block is free again immediately before running. See
129
+ [Surface Layer Schema](../../dbchanges2/features/surface-layer-schema.md).
130
+
131
+ **Run order: `Core/` → `Client/` (fan-out) → `Client_Nychh/`.** The client files reference Core
132
+ record ids, and the NYCHH TableView + ACL depend on columns the fan-out files add. Within `Client/`,
133
+ the stages table must precede the file that adds the stage-id FK column.
121
134
 
122
135
  | Surface | id | Type | Notes |
123
136
  |---|---|---|---|
@@ -359,6 +372,12 @@ visible in Core (neutral default; other tenants populate `TransferOrders.purchas
359
372
  earlier migration without names, so a name-based lookup on them finds nothing.
360
373
 
361
374
  ## Change history
375
+ - 2026-08-31 — **Committed the seven-file migration set** (`dbchanges2` `1c3fe2e`, branch
376
+ `TRUE-80852`, pushed) and **renumbered the Core reserved blocks** to `Messages` 261–284 /
377
+ `Surfaces` 53–57 / `SurfaceElements` 190–206 — the 2026-08-28 blocks had been claimed by
378
+ `sales-order-denial-details` before this shipped. Recorded the **`Core/` → `Client/` →
379
+ `Client_Nychh/` run order**. Also corrected the prod picture: `Client_Nychh.TransferOrders` holds
380
+ **1,927 production rows** — only the UI vertical is new, the data is long-live. (apeterson)
362
381
  - 2026-08-28 — Second pass, same day. Discovered the vertical sits on **four untracked layers** of
363
382
  prod/sandbox state (record + field ACL for records 312/313 and their logic-group chain, plus the
364
383
  `TransferOrderItems.dtCreated/dtUpdated` columns) — a fresh environment cannot reproduce
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-08-31
10
10
  owners: [jcardinal, apeterson]
11
11
  files:
12
12
  - toga25-supply/amplify.yml
@@ -17,6 +17,7 @@ files:
17
17
  - toga25-supply/.env.development
18
18
  related:
19
19
  - ../architecture.md
20
+ - ../../toga-blox/workflows/dynamic-publish-pipeline.md
20
21
  - ../features/transfer-orders-page.md
21
22
  - ../../toga-blox/workflows/local-link-into-a-consumer-app.md
22
23
  ---
@@ -109,6 +110,56 @@ developer override in a **gitignored `.env.development.local`** (Vite loads `.lo
109
110
  the intended per-developer layer). Until that lands, check `git status` before committing on any
110
111
  branch where you pointed the dev server at localhost.
111
112
 
113
+ ## ⚠ The build spec and the `.env` set are NOT reliably present on a branch
114
+
115
+ Verified 2026-08-31 on `_sandbox-client`, and this is the failure mode to check **first** when a
116
+ deploy misbehaves:
117
+
118
+ - **`amplify.yml` existed in exactly ONE commit (`5a276be`) and was absent from the root of EVERY
119
+ remote branch — including `_production`.** So Amplify had been building from a **console-stored**
120
+ build spec, which means the fail-loud `_<mode>` guardrail above was **not actually running**. It
121
+ was recreated on `_sandbox-client`. Note the recreated spec runs **`npx tsc -b` before
122
+ `vite build`**, which the previously documented version did not: **a type error now fails the
123
+ deploy.**
124
+ - **TWO competing `.env` conventions coexist across branches.** The `_<mode>` convention needs
125
+ `sandbox-client`, `sandbox-dev`, `client-*`, `qa-*`, `qc-*`, `production`, `demo`, `stage`. But
126
+ `_sandbox-client`, `_production`, `_beta` and `_main` carry an **OLD** set
127
+ (`.env.alpha` / `.beta` / `.gamma` / `.sprint` / `.test`); only `_stage`, `_sandbox-dev` and the
128
+ snapshot `_sandbox-client-backup820` carry the new 21-mode set. Dry-running the derivation across
129
+ all **27** remote `_*` branches, only **`_beta` and `_stage`** would have built. A "reset from
130
+ prod" is how a branch loses the new set.
131
+ - **The audit — dry-run the guardrail locally before landing the spec.** For every remote `_*`
132
+ branch: `VITE_MODE="${b#_}"; [ -f ".env.$VITE_MODE" ]`. That tells you exactly which branches
133
+ would fail, in seconds, without a deploy.
134
+ - Restoration is a verbatim copy, not a rewrite: `git checkout <known-good-ref> -- .env.<mode> …`
135
+ (19 files restored from `origin/_sandbox-client-backup820`, comments intact). Do **not** overwrite
136
+ files that already exist (`.env`, `.env.development`, `.env.stage` were left alone). Env-file
137
+ format is `VITE_API = 'https://...'` — spaces around `=`, single-quoted — and they contain only
138
+ public base URLs, no secrets.
139
+ - **⚠ Open data issue, needs a human:** on `_sandbox-client`, `.env.stage` points at
140
+ `https://api.dev.sandbox.togahub.com/v2` (**dev-sandbox**) while the backup has
141
+ `https://api.stage.togahub.com/v2`. As-is, `_stage` would deploy pointing at dev-sandbox. Also 4
142
+ orphan old-convention files remain with no matching `_<mode>` branch
143
+ (`.env.alpha`/`.gamma`/`.sprint`/`.test`; `.env.beta` still has one).
144
+
145
+ ## Deleting a remote branch silently breaks its Amplify deploy trigger
146
+
147
+ An Amplify branch is **its own resource with its own webhook association** — it is **not** inferred
148
+ from the repo. Deleting the git branch either deletes the Amplify branch resource (when automatic
149
+ branch deletion is enabled) or leaves it with a stale association. Either way **re-creating the git
150
+ branch does NOT reconnect it**: pushes land in GitHub and Amplify ignores them entirely.
151
+
152
+ - **A missing `amplify.yml` is NOT the cause of a missing trigger** — a build would still start and
153
+ fall back to the console spec. Don't conflate the two.
154
+ - **Diagnose:** GitHub → *Settings → Webhooks* → the Amplify webhook → **Recent Deliveries**. A
155
+ delivery with `200` and no Amplify job = the branch is not connected; **no delivery at all** = the
156
+ webhook is gone. Or `aws amplify list-apps` then `aws amplify list-branches --app-id <id>`.
157
+ - **Fix:** reconnect the branch in the Amplify console **and re-add its domain association** — a
158
+ deleted branch loses its subdomain mapping, so the build can go **green while the URL still
159
+ 404s**.
160
+ - Amplify also **dedupes by commit SHA**, so a recreated branch pointing at an already-built SHA is
161
+ **skipped** — push a new commit to force a build.
162
+
112
163
  ## Adding a new environment
113
164
 
114
165
  1. Create `.env.<mode>` with the public `VITE_API` base URL.
@@ -144,12 +195,45 @@ branch where you pointed the dev server at localhost.
144
195
  (the legacy v7 table stack). All three `react-table` v7 packages were **already** declared at
145
196
  identical versions on `_sandbox-client` — `_production` was the outlier. Fixed in `92644c8`
146
197
  (pushed), verified with a clean vite build (1761 modules).
198
+ - **⚠ The missing-blox-declaration defect RECURS per branch — it is not fixed once.** Verified
199
+ 2026-08-31: `_sandbox-client` **also** had no `@agilant/toga-blox` dependency entry and no lock
200
+ entry despite ~200 imports, exactly as `_production` had (fixed there in `92644c8`). The symlink
201
+ is what hides it, so **re-run the audit on every branch you deploy**. Fixed by installing the
202
+ **exact** pin `1.1.1-sandbox-client.140` (never a caret — some published versions ship an empty
203
+ `dist`). That install surfaced one more masked peer: **`react-day-picker`** (blox peer `^9.5.1`)
204
+ was neither declared nor present in the app's own `node_modules`; added at `^9.14.0`. All 19 blox
205
+ peers are now declared. Installing from the registry also replaced the symlink with a real
206
+ directory, which independently removed the duplicate-`@tanstack/react-query` root cause (see
207
+ [local-link-into-a-consumer-app](../../toga-blox/workflows/local-link-into-a-consumer-app.md)).
208
+ Verified after install: `tsc -b` clean, 44/44 tests, `vite build --mode sandbox-client` clean
209
+ (1773 modules), exactly one react-query copy.
210
+ - **⚠ Latent and NOT fixed — `framer-motion` peer mismatch.** The app declares `^12.35.0`; blox's
211
+ peer range is `^11.0.8`; `legacy-peer-deps=true` silenced the warning, so blox installed a
212
+ **nested framer-motion v11**. framer-motion provides React context (`AnimatePresence`,
213
+ `MotionConfig`) — i.e. the **same duplicate-instance bug class**, just not throwing today. Fix
214
+ direction: widen blox's peer to `^11 || ^12`, **or** add `framer-motion` to the app's
215
+ dedupe/alias list (which forces blox onto v12 — needs testing).
147
216
  - **The audit that catches the above — reusable for any blox consumer.** Enumerate the shared lib's
148
217
  `peerDependencies` from `node_modules/<pkg>/package.json`, then check each one for a **real
149
218
  directory** (not a symlink passthrough) in the *app's* `node_modules`. Do this whenever a repo
150
219
  has ever been developed against a linked local checkout.
151
220
 
152
221
  ## Change history
222
+ - 2026-08-31 — Restored the branch's deploy inputs and recorded three systemic findings.
223
+ (1) **`amplify.yml` was absent from the root of every remote branch** (it lived in a single commit
224
+ `5a276be`), so Amplify had been building from a **console-stored spec** and the fail-loud
225
+ `_<mode>` guardrail was never actually running; recreated it, and the new spec adds `npx tsc -b`
226
+ before `vite build` so a type error now fails the deploy. (2) **Two competing `.env` conventions
227
+ coexist across branches** — a dry-run of the derivation across all 27 remote `_*` branches showed
228
+ only `_beta` and `_stage` would build; restored 19 `.env.<mode>` files verbatim from
229
+ `origin/_sandbox-client-backup820`, and recorded the local dry-run audit. (3) **Deleting a remote
230
+ branch silently breaks its Amplify trigger** — the branch resource/webhook association is not
231
+ inferred from the repo, re-creating the git branch does not reconnect it, the domain association
232
+ must be re-added (green build, 404 URL), and Amplify dedupes by commit SHA. Also: the
233
+ missing-`@agilant/toga-blox`-declaration defect **recurred** on `_sandbox-client` (fixed with the
234
+ exact pin `1.1.1-sandbox-client.140`, plus the masked peer `react-day-picker`), and logged the
235
+ **latent, unfixed `framer-motion` v11-vs-v12 duplicate-context risk**. Flagged an open data issue:
236
+ `_sandbox-client`'s `.env.stage` points at dev-sandbox. (apeterson)
153
237
  - 2026-08-28 — Recorded that **`npm run dev` does not point at a local API**: mode `development`
154
238
  loads the tracked `.env.development`, which ships
155
239
  `VITE_API = https://api.dev.sandbox.togahub.com/v2` — so "dev" reads/writes dev-sandbox data. The
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
21
21
  - **_underscore** (_Underscore) _(framework core)_ — 71 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
22
22
  - **worker2** (Worker) — 58 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
23
23
  - **api2** (API) — 25 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
24
- - **dbchanges2** (Database Changes) _(framework core)_ — 14 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
24
+ - **dbchanges2** (Database Changes) _(framework core)_ — 15 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
25
25
  - **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
26
26
  - **saml** (SAML SSO Gateway) — 5 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
27
27
  - **toga2-view** (TOGa View Frontend) — 12 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
@@ -22,7 +22,7 @@
22
22
  | [Stranded Approval Reassignment (repointing approvals off dead duplicate Compass Users rows)](features/stranded-approval-reassignment.md) | 2.0 | A Compass employee who leaves and comes back after more than `CONTACT_UNLINK_GRACE_DAYS` (5) is **inserted as a brand-new `Users` row** by the PEOPLE importer i | _underscore/Model/Compass/ApprovalDecision.php, worker2/Worker/Client/Compass/ApprovalReassignment.php, worker2/Worker/Client/Compass/PeopleFile.php |
23
23
  | [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
24
24
  | [Compass Cross-Kit Bundle Corruption — Detection & Repair](workflows/cross-kit-bundle-corruption.md) | 2.0 | A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle (kit) line items and **fees/warranties** to the **wrong kit**, pe | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts |
25
- | [Granting a Compass user the same toga25-supply navigation as another user (role vs. SurfaceOverride)](workflows/granting-navigation-access.md) | 2.0 | "Give user X the same menu items user Y has" is the **navigation analog** of the recurring [bundle/persona grant](./granting-persona-bundle-access.md) request. | dbchanges2/Client_Compass/2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql, dbchanges2/Client_Compass/2026-08-28 - GrantViewOrdersRoleSalesOrdersNavigation.sql, dbchanges2/Client_Compass/2026-08-12a - NavigationRoleGrants.sql, dbchanges2/Client_Compass/2026-02-25 - AgilantUsersViewAccess.sql, dbchanges2/Client_Compass/2026-08-11 - CompassODPViewVendorItemsAccess.sql, dbchanges2/Core/2026-08-12a - NavigationSurfaceSeed.sql |
25
+ | [Granting a Compass user the same toga25-supply navigation as another user (role vs. SurfaceOverride)](workflows/granting-navigation-access.md) | 2.0 | "Give user X the same menu items user Y has" is the **navigation analog** of the recurring [bundle/persona grant](./granting-persona-bundle-access.md) request. | dbchanges2/Client_Compass/2026-08-12a - NavigationRoleGrants.sql, dbchanges2/Client_Compass/2026-02-25 - AgilantUsersViewAccess.sql, dbchanges2/Client_Compass/2026-08-11 - CompassODPViewVendorItemsAccess.sql, dbchanges2/Core/2026-08-12a - NavigationSurfaceSeed.sql |
26
26
  | [Granting a Compass user access to a bundle (persona grant) and the SuperUser role](workflows/granting-persona-bundle-access.md) | 2.0 | "Give user X sight of kit N" is a **recurring** Compass request, usually paired with "and make them a super user". | dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql |
27
27
  | [Compass Office Depot Duplicate PO-Line Cleanup (dual-catalog SKU)](workflows/odp-duplicate-po-line-cleanup.md) | 1.0 | The NetSuite fulfillment-sales-order importer duplicated Office Depot purchase-order lines on Compass USA because it reconciled the fulfillment SO against the e | library/app/api/toga2.php, dbchanges2/Client_Compass/2026-07-22a - CleanupOfficeDepotDuplicatePurchaseOrderItems.sql |
28
28
  | [Recovering a Lost Compass ODP EDI 850 Import (re-drop from Logs.FileLog)](workflows/odp-edi-import-recovery.md) | 1.0 | How to recover a Compass **Office Depot EDI 850** import that failed partway — the case where cron **3a** created the ODP SalesOrder header, the follow-up item | worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php, worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, worker/schedules/cron.worker.sync.json |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: compass-usa
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-08-31
10
10
  owners: ["apeterson", "dfranks", "bala"]
11
11
  files:
12
12
  - _underscore/Model/Compass/ApprovalDecision.php
@@ -17,6 +17,7 @@ files:
17
17
  - _underscore/Model/Quad/SalesOrder.php
18
18
  related:
19
19
  - mr-ma-order-approval-and-status.md
20
+ - ../../../2.0/apps/toga25-supply/features/action-button-rule-engine.md
20
21
  - ../../../2.0/apps/_underscore/features/sales-order-denial-reason.md
21
22
  - people-file-user-lifecycle.md
22
23
  - stranded-approval-reassignment.md
@@ -342,6 +343,45 @@ requester**. All of these comparisons are case-insensitive (`strcasecmp()`), mat
342
343
  [Record Change Audit Log](../../../2.0/apps/_underscore/features/record-change-audit-log.md).
343
344
  - Real incident: order SA134461 — VIP manager Spyros Gravas, subtotal $926.47 (both auto-approve
344
345
  conditions met) still required manual override because the requester had no supervisor.
346
+ - **⚠ The `!$hasManager` branch strands orders in an unrecoverable decided-but-untyped state — the
347
+ worst of the no-supervisor family.** When the "Order for" contact is changed to a user with **no**
348
+ `Users.supervisorUserId`, `_Model_Compass_SalesOrder::postPut`'s `!$hasManager` branch
349
+ (`Model/Compass/SalesOrder.php` ~L1018) PUTs `assignedToUser`, `decidedByUser`, `isApproved`,
350
+ `approvalDecisionType`, `note` **and** `dtDecision` all to `null` — deliberately wiping an existing
351
+ VIP auto-approval. A later **manual** approval then sets `isApproved = 1` + `decidedByUserId` but
352
+ never stamps `approvalDecisionTypeId` (correct — it is not a *system* decision). End state:
353
+ `isApproved = 1`, `approvalDecisionTypeId = NULL`, and frequently a **stale** "Auto-approved:
354
+ Manager is VIP and order total <= 5000" note left behind.
355
+ - **Frontend consequence (the part that makes it unrecoverable):** blox derives `isPreApproved`
356
+ from `!!stage.ApprovalDecisionTypes.name`, so it is `false`. The approval modal therefore locks
357
+ the stage's inputs **and hides the Stage Actions block that contains the only "Reset to Pending"
358
+ control** — no admin action can recover the order. This is why the 2026-08-31 frontend fix gates
359
+ on `stepOneUndecided` rather than on `isPreApproved` (see
360
+ [Action-Button Rule Engine](../../../2.0/apps/toga25-supply/features/action-button-rule-engine.md)).
361
+ - **Diagnostic signature in the order activity log**, in order: `"Manager approval skipped due to
362
+ VIP logic"` (creation-time VIP auto-approve), then `"Manager assignment cleared: new user has no
363
+ supervisor"` **and** `"VIP auto-approval removed: new user has no supervisor"` (written **only**
364
+ by that `!$hasManager` branch), then `"Manager Approved by <admin>"` (the manual approval).
365
+ - **Prod evidence 2026-08-31** (`Client_Compass`, orders **SA136701 / SA136702**): step-1 decision
366
+ NULL; step-2 `isApproved = 1`, `approvalDecisionTypeId = NULL`, `decidedByUserId = 152887` (the
367
+ admin, **not** the assigned manager), assignee `JEFF.KRAUSE@COMPASS-USA.COM` with `c_isVip = 1`,
368
+ and the VIP auto-approve text still in `note`. **Caveat: for those two specific orders the writer
369
+ was NOT positively identified** (no log access; prod RDS is unreachable from a laptop). The
370
+ mechanism above is proven from a **third** order's activity log — treat it as the mechanism, not
371
+ as confirmed provenance of those two rows.
372
+ - **Still open (not addressed):** nothing stops **new** orders reaching this state — the
373
+ `!$hasManager` branch should either re-derive a manager for the new contact or leave a clean
374
+ *pending* stage instead of a half-cleared one. And once step 1 **is** approved, a decided stage
375
+ still hides its own "Reset to Pending" control.
376
+ - **Two dead theories — do not re-derive them.** Both were chased against api2's write path
377
+ (`Component/Api/V2/V2.php` ~L4858-5040) and are false:
378
+ 1. api2 **does** accept a bare numeric FK id on write (it falls through to `$model->$key = $val`),
379
+ so passing `'approvalDecisionType' => 1` as a scalar through `internalApiRequest` is **not**
380
+ silently dropped.
381
+ 2. In api2's write path an **unknown** field or a **non-writable** field is a **hard error**
382
+ (`unknownFields` / `fieldsNotWritable` → an `EZ`/`EV` message, whole request fails), never a
383
+ silent strip. So a row that saved its other fields **cannot** have had one field quietly
384
+ discarded by ACL.
345
385
  - **Never drop the order requester from notifications on manager reassignment.** If the outgoing
346
386
  manager happens to also be the order requester (same email), deleting the old-manager address
347
387
  from `SalesOrderEmailAddresses` would silently remove the requester from all order notifications.
@@ -397,6 +437,16 @@ requester**. All of these comparisons are case-insensitive (`strcasecmp()`), mat
397
437
  approve as manager on his behalf. (Fixed 2026-07-23.)
398
438
 
399
439
  ## Change history
440
+ - 2026-08-31 — **Root-caused the "decided but untyped" step-2 approvals that no admin can recover**
441
+ (both tenants, via the shared parent). `_Model_Compass_SalesOrder::postPut`'s `!$hasManager` branch
442
+ nulls the entire step-2 decision when the "Order for" contact has no `supervisorUserId`; a
443
+ subsequent manual approval sets `isApproved = 1` without `approvalDecisionTypeId`, so the frontend
444
+ reads `isPreApproved = false`, locks the stage **and** hides the only "Reset to Pending" control.
445
+ Recorded the three-note activity-log signature, prod evidence SA136701/SA136702 (with the caveat
446
+ that the writer of those two rows was not positively identified), the still-open prevention gap,
447
+ and two **dead** api2 theories (a bare numeric FK id **is** accepted on write; an unknown or
448
+ non-writable field is a hard error, never a silent strip). Investigation only — no backend change;
449
+ the frontend mitigation shipped in `toga25-supply`. (apeterson)
400
450
  - 2026-08-28 — Recorded that this flow's `buildRejectionNoteHtml()` path is the **only** consumer of
401
451
  `ApprovalDecisions.note` anywhere in the platform — no supply or commerce frontend renders the
402
452
  denial reason (and Quad reaches it through its **own** `_Model_Quad_ApprovalDecision`, not this
@@ -6,11 +6,9 @@ project: Database Changes
6
6
  client: compass-usa
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-08-31
10
10
  owners: [apeterson, bala]
11
11
  files:
12
- - dbchanges2/Client_Compass/2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql
13
- - dbchanges2/Client_Compass/2026-08-28 - GrantViewOrdersRoleSalesOrdersNavigation.sql
14
12
  - dbchanges2/Client_Compass/2026-08-12a - NavigationRoleGrants.sql
15
13
  - dbchanges2/Client_Compass/2026-02-25 - AgilantUsersViewAccess.sql
16
14
  - dbchanges2/Client_Compass/2026-08-11 - CompassODPViewVendorItemsAccess.sql
@@ -253,6 +251,15 @@ tenant-wide behavior change and needs its own ticket and sign-off.
253
251
  `AclActionPermissions` when a menu item is missing. It is always a `SurfaceOverrides` row.
254
252
 
255
253
  ## Change history
254
+ - 2026-08-31 — 🚨 **Both migration files this doc describes are GONE.** Verified with
255
+ `git log --all`: neither `Client_Compass/2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql`
256
+ nor `Client_Compass/2026-08-28 - GrantViewOrdersRoleSalesOrdersNavigation.sql` was ever committed on
257
+ any branch, and neither is in the working tree — the 08-27a file was deleted this session at the
258
+ developer's request (local backup only), and a third untracked file,
259
+ `2026-08-28a - ViewOrdersRoleReadOnlySurface.sql`, was deleted with it. Removed both from `files:`.
260
+ **The recipes and the role/element analysis below still stand — the SQL must be rewritten before it
261
+ can ship.** General lesson: an uncommitted migration is not a deliverable; a doc that cites one is
262
+ citing something that may not exist tomorrow. (apeterson)
256
263
  - 2026-08-28 — Added the **reverse case**: a role with no `SurfaceOverrides` nav row leaves the user
257
264
  with zero menu items, which `toga25-supply` renders as a **blank page with no error** and users
258
265
  report as *"cannot log in"* (a fresh `Users.dtLastLogin` proves auth succeeded). Identified the
@@ -6,4 +6,4 @@
6
6
  | [NYCHH NetSuite → TransferOrders import (stocking-flag routing, PO bridges, origin location)](features/netsuite-transfer-order-import.md) | 1.0 | NYCHH's NetSuite **transfer orders are represented as NetSuite sales orders** (same as the GroWrk pattern), so the shared NetSuite → TOGa Supply importer (`App_ | library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/netsuite.php, worker/crons/toga2/netsuite/sync_togasupply_hh.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/diagnose_hh_stuck_transfer_order.php, dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql, dbchanges2/Client/2026-08-28a - TransferOrderItemsTimestampColumns.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestampColumnsReposition.sql, dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql, dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql |
7
7
  | [NYCHH PO links are UPSTREAM — the downstream SO→PO route returns empty](features/po-number-upstream-direction.md) | 2.0 | NYCHH's sales orders are created **from the customer's purchase order**, so their SO↔PO links live in the **upstream** table `PurchaseOrders_SalesOrders` (route | _underscore/Model/Client/SalesOrder.php, _underscore/Trait/Netsuite/SalesOrder.php, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, dbchanges2/Client/2026-08-11b - SalesOrderPurchaseOrdersField.sql, dbchanges2/Client/2026-08-25 - SalesOrderPurchaseOrdersFieldAllClients.sql |
8
8
  | [NYCHH transfer-order 2.0 model — inventory quantities + V2 field enablement](features/transfer-order-inventory-quantities.md) | 2.0 | The 2.0 (`_underscore`) side of NYCHH transfer-order support: a **two-branch inventory quantity model** on the NYCHH `Item` override, plus the **client-override | _underscore/Model/Nychh/Item.php, _underscore/Model/Nychh/PurchaseOrderItem.php, _underscore/Model/Nychh/TransferOrder.php, _underscore/Model/Nychh/TransferOrderStage.php, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderItem.php, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
9
- | [NYC Health & Hospitals](profile.md) | 2.0 | NYC Health & Hospitals (NYCHH) is a TOGA 2.0 client on the `_underscore` platform, prod schema `Client_Nychh`. | dbchanges2/Client_Nychh/2026-08-18 - InventoryUnitsItemColumns.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
9
+ | [NYC Health & Hospitals](profile.md) | 2.0 | NYC Health & Hospitals (NYCHH) is a TOGA 2.0 client on the `_underscore` platform, prod schema `Client_Nychh`. | dbchanges2/Client_Nychh/2026-08-18 - InventoryUnitsItemColumns.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
@@ -16,13 +16,11 @@ project: _Underscore
16
16
  client: nychh
17
17
  type: profile
18
18
  status: active
19
- updated: 2026-08-28
19
+ updated: 2026-08-31
20
20
  owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
21
21
  files:
22
22
  - dbchanges2/Client_Nychh/2026-08-18 - InventoryUnitsItemColumns.sql
23
23
  - dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql
24
- - dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql
25
- - dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql
26
24
  - dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql
27
25
  - dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql
28
26
  - dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql
@@ -68,14 +66,26 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
68
66
  arrive as sales orders; the 1.0 importer routes them by the `custbody_stocking_order` flag (not the
69
67
  dollar amount), bridges the PO, and resolves origin/destination locations. See
70
68
  [NYCHH NetSuite → TransferOrders import](./features/netsuite-transfer-order-import.md).
71
- - **Transfer Orders is an NYCHH-only screen (2026-08-28, local only).** NYCHH is the first — and
69
+ - 🚨 **`Client_Nychh.TransferOrders` HAS production data — 1,927 rows (verified 2026-08-31 on
70
+ `reader1.client.database.togahub.com`).** The transfer-order *tables and data* have been live in
71
+ prod for a while (the 1.0 NetSuite importer writes them); only the **2026-08-28 UI vertical**
72
+ (TableView, Core surfaces, nav override) is new. An earlier version of this profile said transfer
73
+ orders were "local only … not yet run on sandbox-client or production", and that stale line
74
+ produced a wrong recommendation this session — that a prod-to-local restore would *wipe* the
75
+ transfer-orders feature. **Never infer prod state from this repo or from a migration's run
76
+ status; read prod.** Restoring or overwriting `Client_Nychh` destroys real transfer-order rows.
77
+ - **The Transfer Orders SCREEN is NYCHH-only and new (2026-08-28).** NYCHH is the first — and
72
78
  today the only — tenant with a `transfer-orders` TableView (`Client_Nychh/2026-08-28a`, which also
73
79
  seeds the ACL chain for record 351 that the Status column silently depends on) and the one
74
80
  `SurfaceOverrides` row that reveals the nav item (`2026-08-28b`, client-wide, matching how this
75
81
  tenant already grants Sales Orders and Inventory — NYCHH does not scope navigation by role). The
76
82
  Core surfaces and the stage colour tokens are **shared/neutral**, hidden for every other client
77
- until they opt in. Not yet run on sandbox-client or production; **beta is blocked** because it
78
- lacks the `TransferOrderStages` table. A **Create Transfer Order** modal is started (UI built, data
83
+ until they opt in. The seven migration files were **committed 2026-08-31** (`dbchanges2` commit
84
+ `1c3fe2e`, branch `TRUE-80852`, pushed); run order is **`Core/` → `Client/` fan-out →
85
+ `Client_Nychh/`**, because the client files reference Core record ids and the NYCHH TableView/ACL
86
+ need the columns the fan-out files add. Run state per environment must be **verified against that
87
+ environment**, never assumed from the repo. **beta is blocked** because it lacks the
88
+ `TransferOrderStages` table. A **Create Transfer Order** modal is started (UI built, data
79
89
  layer flagged off pending three open data-model questions). See
80
90
  [Transfer Orders page](../../2.0/apps/toga25-supply/features/transfer-orders-page.md).
81
91
  - 🚨 **The transfer-order ACL + schema on prod/sandbox are UNTRACKED (2026-08-28).** Record and field
@@ -122,4 +132,14 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
122
132
  ship in the same release as that Core file.** NYCHH was the only drifted client (Client_Elite's
123
133
  equivalents are correct). See
124
134
  [Surface Layer Schema](../../2.0/apps/dbchanges2/features/surface-layer-schema.md).
135
+ **Where that file actually lives (checked 2026-08-31):** `Client_Nychh/2026-08-24 -
136
+ FixInventoryGroupingsUnitsTopologyOverrideIds.sql` is committed on branch **`TRUE-81190`** only —
137
+ it is **not on `_main`**, so shipping `Core/2026-08-21 - SalesOrderDecisionSurfacesReseed.sql`
138
+ without merging `TRUE-81190` is the exact break described above.
139
+ - 🚨 **Two NYCHH migrations named in earlier notes were never committed anywhere** (`git log --all`,
140
+ 2026-08-31): `Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql` and
141
+ `Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql`. Zero commits touch either
142
+ path on any branch, and neither is in the working tree — so the transfer-order **custom-field
143
+ write permission** and the **stage status identifier** are, like the ACL layers above, prod/local
144
+ state with no file behind them. Rewrite them before anyone builds a fresh environment.
125
145
  - This profile is a starting point; expand as more NYCHH-specific behavior is captured.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.712",
3
+ "version": "1.0.713",
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",