toga-ai 1.0.467 → 1.0.468
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/library/INDEX.md +1 -1
- package/knowledge/1.0/apps/library/features/netsuite-item-isfulfillable-sync.md +47 -16
- package/knowledge/1.0/apps/worker/INDEX.md +1 -0
- package/knowledge/1.0/apps/worker/workflows/isfulfillable-multi-client-backfill.md +90 -0
- package/knowledge/2.0/apps/_underscore/features/fulfillable-item-propagation.md +29 -2
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. | library/app/api/toga2.php |
|
|
10
10
|
| [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. | library/app/email/template.php, library/app/email/agilant.php |
|
|
11
11
|
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
|
|
12
|
-
| [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant | library/app/netsuite.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php |
|
|
12
|
+
| [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant | library/app/netsuite.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php |
|
|
13
13
|
| [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w | library/app/api/netsuite/rest.php, library/ssl/netsuite_ec_key.pem, test/@dave/Junk Drawer/nsq.php |
|
|
14
14
|
| [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php |
|
|
15
15
|
| [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`, title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watc | library/app/systemmonitor/netsuiteintegration.php, worker/crons/infrastructure/system_monitors.php |
|
|
@@ -6,15 +6,17 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-28
|
|
10
10
|
owners: [bala]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/netsuite.php
|
|
13
13
|
- library/app/api/toga2.php
|
|
14
|
+
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
14
15
|
- worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php
|
|
15
16
|
related:
|
|
16
17
|
- toga2-api-client-and-bridge.md
|
|
17
18
|
- ../../worker/features/netsuite-togasupply-per-client-sync.md
|
|
19
|
+
- ../../worker/workflows/isfulfillable-multi-client-backfill.md
|
|
18
20
|
- ../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md
|
|
19
21
|
---
|
|
20
22
|
|
|
@@ -26,24 +28,43 @@ item** in the 2.0 platform. The client-facing copy is reached separately by the
|
|
|
26
28
|
which walks the supply chain up from the source item. Only the source item is stamped here.
|
|
27
29
|
|
|
28
30
|
## Key files / entry points
|
|
29
|
-
- `library/app/netsuite.php`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- `
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `
|
|
31
|
+
- `library/app/netsuite.php`
|
|
32
|
+
- `App_NetSuite::getItemIsFulfillable($itemInternalId): bool|null` — a SOAP `ItemSearchBasic` by
|
|
33
|
+
internal id; returns the boolean (or `null` when unknown). Mirrors `getItemIsSerialized`.
|
|
34
|
+
- `App_NetSuite::getItemSerializedAndFulfillable($itemInternalId)` — **combined lookup** returning
|
|
35
|
+
**both** flags from **one** `ItemSearchBasic` (serialized via `instanceof SerializedInventoryItem`,
|
|
36
|
+
fulfillable via the record's `isFulfillable`). `getCreateItem` now calls this once instead of the
|
|
37
|
+
two separate SOAP searches (`getItemIsSerialized` + `getItemIsFulfillable`) — halves the per-line
|
|
38
|
+
NetSuite round-trips now that the sync reads fulfillability for every line (see refresh below).
|
|
39
|
+
- `library/app/api/toga2.php` — three item-creation paths now stamp `isFulfillable`:
|
|
40
|
+
- `getCreateItem` (~line 4364) — reads both flags via `getItemSerializedAndFulfillable` and, when
|
|
41
|
+
fulfillable is **non-null**, stamps `$payload['isFulfillable']` on the create/update PUT|POST. It
|
|
42
|
+
now also **refreshes the flag on EXISTING items** when NetSuite's value differs from the stored
|
|
43
|
+
value (not only on create), mirroring how the serialized flag works. Because the PUT is
|
|
44
|
+
diff-only, a re-sync with an unchanged value is a no-op. NetSuite internal id is resolved from the
|
|
45
|
+
order line or part number (`getItemInternalIdFromPartNumber` / `getItemGroupInternalIdFromPartNumber`);
|
|
46
|
+
stores `c_netsuiteInternalItemId`.
|
|
47
|
+
- `syncItemFulfillmentFromNetsuite` (~line 3562) and `syncInventoryAdjustmentFromNetsuite`
|
|
48
|
+
(~line 3882) — the two **other** direct `/items` creation paths now also stamp `isFulfillable` on
|
|
49
|
+
create. Previously only `getCreateItem` did, so items first created via these flows stayed **NULL**.
|
|
50
|
+
- `worker/crons/toga2/netsuite/common_sync_togasupply.php` — `isFulfillable` was added to the bulk
|
|
51
|
+
`/items` GET field list so the existing-item refresh has the stored value to compare NetSuite's
|
|
52
|
+
against (without it, the refresh has nothing to diff).
|
|
53
|
+
- `worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php` — one-time Compass backfill (see below).
|
|
54
|
+
The later **multi-client** catch-up lives in its own worker workflow doc
|
|
55
|
+
([isFulfillable multi-client backfill](../../worker/workflows/isfulfillable-multi-client-backfill.md)).
|
|
39
56
|
|
|
40
57
|
## How it works
|
|
41
58
|
1. Item sync resolves the NetSuite internal id for the item (order line, else part-number lookup).
|
|
42
|
-
2. `
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
2. `getItemSerializedAndFulfillable` SOAP-searches the item **once** and returns both the serialized
|
|
60
|
+
and fulfillable flags.
|
|
61
|
+
3. If fulfillable is non-null, `getCreateItem` adds `isFulfillable` to the item PUT/POST payload to
|
|
62
|
+
api2 — on **create**, and on **update** whenever the stored value differs from NetSuite's (the
|
|
63
|
+
existing-item refresh). api2's Items interceptor then propagates it up-chain (Phase 2). The two
|
|
64
|
+
other creation paths (`syncItemFulfillmentFromNetsuite`, `syncInventoryAdjustmentFromNetsuite`)
|
|
65
|
+
also stamp it on create.
|
|
66
|
+
4. Going forward the **daily sync** handles new items and keeps existing items current (the refresh);
|
|
67
|
+
items that predate the feature are caught up by the one-time backfills.
|
|
47
68
|
|
|
48
69
|
### One-time backfill (July 5+ orders)
|
|
49
70
|
`backfill_isfulfillable_jul5.php` catches up Agilant items on SalesOrders since **2026-07-05**:
|
|
@@ -85,6 +106,14 @@ nearly everything resolves to `fulfillable = 1`.**
|
|
|
85
106
|
in `Client_Compass.Apis` (name `Agilant`) — never reproduce the secret value.
|
|
86
107
|
|
|
87
108
|
## Change history
|
|
109
|
+
- 2026-07-28 — Code-review hardening: (1) `getCreateItem` now **refreshes `isFulfillable` on
|
|
110
|
+
existing items** when NetSuite's value differs (not create-only), mirroring the serialized flag;
|
|
111
|
+
diff-only PUT keeps re-syncs no-op. (2) The two other `/items` creation paths
|
|
112
|
+
(`syncItemFulfillmentFromNetsuite`, `syncInventoryAdjustmentFromNetsuite`) now also stamp the flag
|
|
113
|
+
on create — they previously left it NULL. (3) Added `isFulfillable` to the worker sync's bulk
|
|
114
|
+
`/items` GET field list so the refresh has a stored value to diff. (4) New combined
|
|
115
|
+
`App_NetSuite::getItemSerializedAndFulfillable` returns both flags from one `ItemSearchBasic`, so
|
|
116
|
+
`getCreateItem` no longer makes two SOAP searches per line. (bala)
|
|
88
117
|
- 2026-07-20 — Added `App_NetSuite::getItemIsFulfillable` (SOAP `ItemSearchBasic`, mirrors
|
|
89
118
|
`getItemIsSerialized`) and wired `getCreateItem` to stamp `isFulfillable` on the item PUT/POST to
|
|
90
119
|
api2 when non-null (internal id from order line or part-number lookup; stores
|
|
@@ -100,3 +129,5 @@ nearly everything resolves to `fulfillable = 1`.**
|
|
|
100
129
|
- [NetSuite → TOGa Supply Per-Client Sync](../../worker/features/netsuite-togasupply-per-client-sync.md)
|
|
101
130
|
— the sync engine this item flag rides in.
|
|
102
131
|
- [Phase 2 — isFulfillable propagation up the SO↔PO chain (2.0)](../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md).
|
|
132
|
+
- [isFulfillable multi-client backfill (worker)](../../worker/workflows/isfulfillable-multi-client-backfill.md)
|
|
133
|
+
— the cross-client catch-up cron and its client-DB / catalog-matching gotchas.
|
|
@@ -10,4 +10,5 @@
|
|
|
10
10
|
| [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/netsuite/rest.php, library/app/systemmonitor/netsuiteintegration.php |
|
|
11
11
|
| [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL | library/app/worker.php, worker/crons/worker/worker_heartbeat.php |
|
|
12
12
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
|
|
13
|
+
| [isFulfillable Multi-Client Backfill (all togasupply clients)](workflows/isfulfillable-multi-client-backfill.md) | One-time backfill that catches up `Items.isFulfillable` on **existing** items across **all 17 togasupply clients** (AIG, Broward Sheriff, Canon, Endeavor Health | worker/crons/toga2/netsuite/backfill_isfulfillable_all_clients.php, library/app/api/toga2.php |
|
|
13
14
|
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: isFulfillable Multi-Client Backfill (all togasupply clients)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-28
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/toga2/netsuite/backfill_isfulfillable_all_clients.php
|
|
13
|
+
- library/app/api/toga2.php
|
|
14
|
+
related:
|
|
15
|
+
- ../features/netsuite-togasupply-per-client-sync.md
|
|
16
|
+
- ../../library/features/netsuite-item-isfulfillable-sync.md
|
|
17
|
+
- ../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md
|
|
18
|
+
- ../../../clients/compass-usa/profile.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
One-time backfill that catches up `Items.isFulfillable` on **existing** items across **all 17
|
|
23
|
+
togasupply clients** (AIG, Broward Sheriff, Canon, Endeavor Health, ERAU, GroWrk, NYC Health +
|
|
24
|
+
Hospitals, Masonite, Miami-Dade, NCCI, Prudential, Quad, SBA, SHRSS, S&P Global, Trividia Health,
|
|
25
|
+
and Compass). It is the multi-client successor to the Compass-only `backfill_isfulfillable_jul5.php`
|
|
26
|
+
(see the [Phase 1 library doc](../../library/features/netsuite-item-isfulfillable-sync.md)). Like that
|
|
27
|
+
one, it re-PUTs each Agilant **source** item to api2 `/items` so the **api2 interceptor does the
|
|
28
|
+
recursive up-chain propagation** — it never writes the DB directly and never walks the chain itself.
|
|
29
|
+
|
|
30
|
+
## Steps (how the cron runs)
|
|
31
|
+
1. **Scope each client's Agilant-catalog items directly from that client's DB** — NOT via api2 —
|
|
32
|
+
keyed by `c_netsuiteInternalItemId`. Direct DB is required because that field is not an api2 GET
|
|
33
|
+
field (see gotchas). The Agilant catalog is matched by **`Catalogs.name = 'Agilant'`**, not by id.
|
|
34
|
+
2. **Read `isFulfillable` from NetSuite by internal id** via SuiteQL `WHERE id IN (...)`, **cached
|
|
35
|
+
across clients** so a shared internal id is fetched once for the whole run.
|
|
36
|
+
3. **PUT each source item to api2 `/items`** (`App_Api_Toga2::send`). The Items interceptor then
|
|
37
|
+
propagates the value up the SO↔PO chain onto every client-facing item (Phase 2).
|
|
38
|
+
4. **Per-client scoping rule:**
|
|
39
|
+
- **Compass** — limited to orders since a cutoff (~**2026-06-20**); it was already partly
|
|
40
|
+
backfilled and only orders after that had correct chain linkage to propagate up to the client
|
|
41
|
+
items.
|
|
42
|
+
- **Every other client** — takes **ALL** its Agilant items (never previously backfilled).
|
|
43
|
+
5. **Resilience:** clients whose DB read fails are **skipped and reported**, not fatal. Supports
|
|
44
|
+
`DRY_RUN`, `DB_HOST_PREFERENCE` (`localhost` / `writer.client` / `sandbox-dev`), and
|
|
45
|
+
`API_ENDPOINT_OVERRIDE` (the **9th arg** of `App_Api_Toga2::send`) so the run can target prod or a
|
|
46
|
+
beta/sandbox endpoint.
|
|
47
|
+
|
|
48
|
+
## Gotchas / known issues
|
|
49
|
+
- **Agilant catalog is matched by NAME, not id.** `Catalogs.name = 'Agilant'` is stable across every
|
|
50
|
+
client, but the `catalogId` **differs per client** — **Quad = 2, all other clients = 1** (verified
|
|
51
|
+
across all 17 client DBs). Hardcoding `catalogId = 2` would silently hit only Quad and miss the
|
|
52
|
+
other 16. Always resolve the catalog by name.
|
|
53
|
+
- **`c_netsuiteInternalItemId` is NOT an api2 GET field** — there is no `Core.RecordField` for it, so
|
|
54
|
+
it can only be read by direct DB query. That is why the backfill scopes items via direct DB reads
|
|
55
|
+
rather than an api2 `/items` GET.
|
|
56
|
+
- **Client DB names don't always follow the client identifier.** Compass's identifier is
|
|
57
|
+
`Compass_Usa` but its database is **`Client_Compass`**. All client DBs live on **one cluster**
|
|
58
|
+
(prod `prod-client`), so a single host/user/pass with a swapped **database name** reaches them all.
|
|
59
|
+
- **⚠ Environment must be fully deployed, or the backfill can't propagate.** PUTting `isFulfillable`
|
|
60
|
+
to `api.beta.togahub.com` returns **EV-9 "no write permission"** — the field exists but the write
|
|
61
|
+
ACL was never deployed there (only `dev-sandbox` had field + interceptor + ACL). The Core
|
|
62
|
+
RecordField, the `ApiPayloadInterceptors` rows, **and** the client column + write ACL must all be
|
|
63
|
+
present on the **same** environment, and the run must scope from that same cluster. Deploy order:
|
|
64
|
+
dbchanges (field/ACL/interceptors) before running the backfill. See the
|
|
65
|
+
[Phase 2 deploy gotchas](../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md).
|
|
66
|
+
- **🔐 Credentials.** The cron reuses the per-client api uuids/secrets already embedded in the
|
|
67
|
+
`sync_togasupply_*` crons; the api secrets live in each `Client_<client>.Apis` (name `Agilant`) —
|
|
68
|
+
never reproduce a secret value in code or docs.
|
|
69
|
+
|
|
70
|
+
## Run status
|
|
71
|
+
- 2026-07-28 — Ran across all NetSuite/togasupply clients. Compass scoped to orders after
|
|
72
|
+
~2026-06-20 (only those had correct chain linkage to propagate up to the client items); every other
|
|
73
|
+
client backfilled from all its Agilant items.
|
|
74
|
+
|
|
75
|
+
## Change history
|
|
76
|
+
- 2026-07-28 — Built and ran the multi-client `backfill_isfulfillable_all_clients.php`: direct
|
|
77
|
+
client-DB scoping of Agilant-catalog items (matched by `Catalogs.name`, not id) keyed by
|
|
78
|
+
`c_netsuiteInternalItemId`, NetSuite reads by internal id (cached across clients), re-PUT to api2 so
|
|
79
|
+
the interceptor recurses. Per-client scope (Compass ~post-2026-06-20, all others full), DRY_RUN /
|
|
80
|
+
DB_HOST_PREFERENCE / API_ENDPOINT_OVERRIDE knobs, failed-DB clients skipped + reported. Recorded the
|
|
81
|
+
catalog-by-name (Quad catalogId=2), `c_netsuiteInternalItemId`-not-a-GET-field, Compass DB-name, and
|
|
82
|
+
beta EV-9 deploy-gap gotchas. (bala)
|
|
83
|
+
|
|
84
|
+
## Related docs
|
|
85
|
+
- [isFulfillable from NetSuite during item sync (Phase 1, library)](../../library/features/netsuite-item-isfulfillable-sync.md)
|
|
86
|
+
— where the value is sourced and the earlier Compass-only backfill.
|
|
87
|
+
- [isFulfillable propagation up the SO↔PO chain (Phase 2, _underscore)](../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md)
|
|
88
|
+
— the api2 interceptor this backfill relies on to do the recursion.
|
|
89
|
+
- [NetSuite → TOGa Supply Per-Client Sync](../features/netsuite-togasupply-per-client-sync.md)
|
|
90
|
+
— the steady-state sync whose credentials/config this backfill reuses.
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-28
|
|
10
10
|
owners: [bala]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/Item.php
|
|
@@ -50,7 +50,14 @@ same bridge topology, walked for a different payload (a scalar flag rather than
|
|
|
50
50
|
one-time backfill) send that field, so day-to-day item edits skip the walk entirely.
|
|
51
51
|
2. **Load the source item** by uuid → `id` + `isFulfillable`. If the value is **NULL**, return
|
|
52
52
|
(nothing authoritative to propagate).
|
|
53
|
-
|
|
53
|
+
- **Writer-routed (read-your-writes).** Both this source read **and** the chain-walk CTE below
|
|
54
|
+
call `->setisReadHostEnabled(false)` so they execute on the **writer**, not a read replica.
|
|
55
|
+
`_Query` routes SELECTs to a read replica by default (`Query.php` ~line 273) — a separate
|
|
56
|
+
connection that cannot see this request's still-open write transaction and can lag replication,
|
|
57
|
+
so a replica read could return the *pre-write* value (or NULL for a brand-new item) and
|
|
58
|
+
propagate the wrong value or skip. The UPDATE was already writer-routed; now read + update share
|
|
59
|
+
the one request transaction.
|
|
60
|
+
3. **Walk the chain UP** with a `WITH RECURSIVE ChainSalesOrderItems` CTE (also writer-routed, see above):
|
|
54
61
|
- **Anchor:** `SalesOrderItems WHERE itemId = <sourceItemId>`.
|
|
55
62
|
- **Recursive step:** join `PurchaseOrderItems_SalesOrderItems` (PSO) then
|
|
56
63
|
`SalesOrderItems_PurchaseOrderItems` (SPO) to climb one tier
|
|
@@ -93,6 +100,20 @@ uniformly `1` (see the Phase-1 gotcha: NetSuite flags nearly everything fulfilla
|
|
|
93
100
|
- Engine itself is shared `_Model_Client_Item` logic; any client with the schema present gets it.
|
|
94
101
|
|
|
95
102
|
## Gotchas / known issues
|
|
103
|
+
- **⚠ Propagation reads MUST be writer-routed.** `_Query` SELECTs default to a read replica
|
|
104
|
+
(`Query.php` ~line 273), a separate connection that can't see the current request's open write
|
|
105
|
+
transaction and may lag replication. The interceptor runs inside the write request, so a
|
|
106
|
+
replica-routed read here can see the **pre-write** value (or NULL on a just-created item) and
|
|
107
|
+
propagate wrongly or skip. Both SELECTs in `propagateFulfillableAcrossChain` therefore set
|
|
108
|
+
`setisReadHostEnabled(false)`. Any future read added to an interceptor that must see the same
|
|
109
|
+
request's writes needs the same treatment.
|
|
110
|
+
- **⚠ Beta deploy gap — EV-9 "no write permission" (write ACL not deployed).** PUTting
|
|
111
|
+
`isFulfillable` to `api.beta.togahub.com` returns **EV-9**: the field *exists* there but its write
|
|
112
|
+
ACL was never deployed (only `dev-sandbox` had the full field + interceptor + ACL from earlier
|
|
113
|
+
work). The three pieces — Core RecordField, `ApiPayloadInterceptors` rows, **and** the client
|
|
114
|
+
column + write ACL — must all be present on the **same** environment (and a backfill run must scope
|
|
115
|
+
from that same cluster) or the write is rejected and nothing propagates. Reinforces the deploy-order
|
|
116
|
+
dependency: dbchanges (field + ACL + interceptors) before the code/backfill that writes the field.
|
|
96
117
|
- **⚠ Interceptors are DB-driven.** The `postPost`/`postPut` PHP does nothing without the
|
|
97
118
|
`ApiPayloadInterceptors` rows in the **target env's Core DB**. Missing rows → the hook never
|
|
98
119
|
fires **and** api2 EV-8's the unknown `isFulfillable` field, **403-ing the whole request** (not
|
|
@@ -108,6 +129,12 @@ uniformly `1` (see the Phase-1 gotcha: NetSuite flags nearly everything fulfilla
|
|
|
108
129
|
vs. using `itemtype` to hide services — is **pending product confirmation**.
|
|
109
130
|
|
|
110
131
|
## Change history
|
|
132
|
+
- 2026-07-28 — Fixed a **read-replica routing** bug: `propagateFulfillableAcrossChain`'s source-item
|
|
133
|
+
read and the recursive chain-walk CTE now `setisReadHostEnabled(false)` so they run on the writer
|
|
134
|
+
and see the request's own open write transaction (read-your-writes) — a replica read could return
|
|
135
|
+
the pre-write value or NULL and propagate wrongly/skip. Recorded the **beta EV-9 write-ACL deploy
|
|
136
|
+
gap** (field present but write ACL undeployed on `api.beta.togahub.com`; all three deploy pieces
|
|
137
|
+
must land on the same env). (bala)
|
|
111
138
|
- 2026-07-20 — Built the isFulfillable up-chain propagation engine:
|
|
112
139
|
`propagateFulfillableAcrossChain` + the payload-gated `postPost`/`postPut` interceptor on Items
|
|
113
140
|
(recursive SOI↔POI CTE, diff-only `UPDATE`), plus the Compass `parent::` fix so the price-override
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 14 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 17 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
10
10
|
- **togadesk** (TOGa Desk) — 10 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
11
11
|
- **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
package/package.json
CHANGED