toga-ai 1.0.208 → 1.0.210
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/tools/INDEX.md +1 -0
- package/knowledge/1.0/apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md +126 -0
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +63 -1
- package/knowledge/2.0/apps/_underscore/features/netsuite-rest-client.md +5 -3
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -6,3 +6,4 @@
|
|
|
6
6
|
| [Tools — Developers Folder (UUID & Password Generators)](features/developer-tools.md) | The first two tools shipped in the Tools app, both under the **Developers** folder and gated to personas **Development Team** / **TOGa Technology**. | tools/mvc/developers/uuid/get.php, tools/mvc/developers/password/get.php |
|
|
7
7
|
| [Tools Persona-Gated Navigation (App_Nav)](features/persona-gated-navigation.md) | `App_Nav` is the Tools app's two-level, **persona-gated** navigation. | tools/_/app/nav.php |
|
|
8
8
|
| [Tools SAML SSO Consumer & Persona-Gated Auth (App_Auth)](features/saml-sso-auth.md) | `App_Auth` is the Tools app's authentication layer: it consumes the SAML gateway `?saml=` handoff (see the 2.0 SAML downstream integration contract), establishe | tools/_/app/auth.php, tools/mvc/sso/get.php, tools/mvc/login/get.php, tools/mvc/login/post.php, tools/mvc/logout/get.php |
|
|
9
|
+
| [Deploying Tools to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)](workflows/deploy-to-elastic-beanstalk-al2023.md) | How the **Tools** 1.0 app boots on Elastic Beanstalk running `PHP 8.5 on 64bit Amazon Linux 2023/4.13.1 (aarch64)`. | tools/.ebextensions/004_http_to_https.config, tools/.ebextensions/006_mount-s3fs.config, tools/.ebextensions/007_setup_export_cache_folders.config, tools/.ebextensions/008_setup_ldap.config, tools/.ebextensions/009_setup_phpini.config, tools/.ebextensions/020_setup_git_libraries.config, tools/.ebextensions/050_register_instance_to_shared_application_load_balancer.config, tools/ebs/git.json |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deploying Tools to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: tools
|
|
5
|
+
project: Tools
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- tools/.ebextensions/004_http_to_https.config
|
|
13
|
+
- tools/.ebextensions/006_mount-s3fs.config
|
|
14
|
+
- tools/.ebextensions/007_setup_export_cache_folders.config
|
|
15
|
+
- tools/.ebextensions/008_setup_ldap.config
|
|
16
|
+
- tools/.ebextensions/009_setup_phpini.config
|
|
17
|
+
- tools/.ebextensions/020_setup_git_libraries.config
|
|
18
|
+
- tools/.ebextensions/050_register_instance_to_shared_application_load_balancer.config
|
|
19
|
+
- tools/ebs/git.json
|
|
20
|
+
related:
|
|
21
|
+
- ../architecture.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
How the **Tools** 1.0 app boots on Elastic Beanstalk running
|
|
27
|
+
`PHP 8.5 on 64bit Amazon Linux 2023/4.13.1 (aarch64)`. The EB platform was upgraded from
|
|
28
|
+
Amazon Linux 2 (AL2) to Amazon Linux 2023 (AL2023), which changes package names, the proxy
|
|
29
|
+
(nginx instead of Apache), the PHP ini scan directory, and how credentials reach the
|
|
30
|
+
instance. The `.ebextensions/*.config` files run alphabetically and are merged by EB; getting
|
|
31
|
+
them wrong leaves the app booting without `/var/www/library` and serving 500s on every
|
|
32
|
+
request. This doc is the migration checklist and the durable AL2023 gotchas learned doing it.
|
|
33
|
+
|
|
34
|
+
## AL2023 package migration checklist
|
|
35
|
+
|
|
36
|
+
When migrating any 1.0 app's `.ebextensions` from AL2 to AL2023, rename/remove these yum
|
|
37
|
+
packages (AL2023 uses different names for the same libraries):
|
|
38
|
+
|
|
39
|
+
- `libstdc++48` → `libstdc++`
|
|
40
|
+
- `php73-ldap` → `php-ldap` (in `008_setup_ldap.config`)
|
|
41
|
+
- **Remove `libcurl: []` entirely** — AL2023 ships `libcurl-minimal` pre-installed, and adding
|
|
42
|
+
`libcurl` to the yum packages list causes a package-conflict error that fails the deploy.
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
### PHP ini — write the file directly, do not run a script
|
|
47
|
+
|
|
48
|
+
`009_setup_phpini.config` writes `/etc/php.d/application.ini` with a cfn-init **`files:`**
|
|
49
|
+
block, **not** a container_command that runs a PHP script. On AL2023, `/etc/php.d/` is the
|
|
50
|
+
PHP ini scan directory, and cfn-init `files:` runs before container_commands and before
|
|
51
|
+
PHP-FPM starts, so the file is guaranteed present at startup with no PHP execution needed.
|
|
52
|
+
|
|
53
|
+
The old approach ran `ebs/setup_phpini.php` (now superseded) which called
|
|
54
|
+
`php_ini_scanned_files()`, parsed it to find the scan dir, and wrote there. On AL2023 that
|
|
55
|
+
failed silently (it was wrapped in `ignoreErrors: true`), so `include_path` never picked up
|
|
56
|
+
`/var/www/library`, producing fatal `require_once('_.php')` errors at runtime.
|
|
57
|
+
|
|
58
|
+
The `application.ini` sets `include_path = ".:/var/www/library"`, upload/post limits (16M),
|
|
59
|
+
`memory_limit = 2G`, `display_errors`, the `redis.so` extension, and the rediscluster session
|
|
60
|
+
handler (`session.save_path` points at the ElastiCache cluster cfg endpoint). Session GC
|
|
61
|
+
maxlifetime is 4 days.
|
|
62
|
+
|
|
63
|
+
### Cloning the libraries — bash, not the CodePipeline PHP script
|
|
64
|
+
|
|
65
|
+
`020_setup_git_libraries.config` clones the framework libraries onto the instance. The old
|
|
66
|
+
`setup_git_libraries.php` (downloaded from S3) requires a DB connection to the `Logs` database
|
|
67
|
+
and a matching row in the `CodePipelineEnvironments` table; for a new app with **no**
|
|
68
|
+
CodePipeline it exits silently without cloning anything, so the app boots without
|
|
69
|
+
`/var/www/library` and 500s on every request.
|
|
70
|
+
|
|
71
|
+
The replacement is pure bash via a cfn-init `files:` block that writes
|
|
72
|
+
`/tmp/clone_git_repos.sh` (mode `000755`) plus a `container_commands` entry that runs
|
|
73
|
+
`bash /tmp/clone_git_repos.sh`. It clones:
|
|
74
|
+
|
|
75
|
+
- `agilantsolutions/library` branch `_production` → `/var/www/library`
|
|
76
|
+
- `agilantsolutions/resources` branch `_production` → `/var/app/ondeck/resources`
|
|
77
|
+
|
|
78
|
+
`ebs/git.json` was corrected to match: branch `_production` for both repos and the resources
|
|
79
|
+
path set to `/var/app/ondeck/resources`.
|
|
80
|
+
|
|
81
|
+
### Duplicate config neutralization
|
|
82
|
+
|
|
83
|
+
EB merges every `.config` alphabetically; two configs with **identical** `container_commands`
|
|
84
|
+
keys silently overwrite each other, causing unpredictable failures (the redis build ran twice;
|
|
85
|
+
an http→https collision obscured failure diagnosis). Two duplicate pairs were neutralized
|
|
86
|
+
(replaced with a comment), keeping the canonical one of each:
|
|
87
|
+
|
|
88
|
+
- `004_enforce_http_to_https.config` (dup of `004_http_to_https.config`) — neutralized
|
|
89
|
+
- `010_setup_redis_sessions.config` (dup of `010_setup_redis.config`) — neutralized
|
|
90
|
+
|
|
91
|
+
### ignoreErrors on non-fatal infrastructure commands
|
|
92
|
+
|
|
93
|
+
Several `container_commands` write to system paths that do not exist on AL2023 (e.g.
|
|
94
|
+
`/etc/httpd/conf.d/` — AL2023 EB uses **nginx**, not Apache, as the reverse proxy). These were
|
|
95
|
+
marked `ignoreErrors: true` in `004`, `006`, `007`, `009`, and `050` so a non-fatal failure
|
|
96
|
+
does not block the deploy. This also covers `006`'s `01_credentials` step: modern EB uses IAM
|
|
97
|
+
roles, so `AWS_SECRET_KEY` / `AWS_ACCESS_KEY_ID` are not injected into `$_SERVER` and the
|
|
98
|
+
s3fs credential write fails cleanly. Use `ignoreErrors` only on genuinely non-fatal infra
|
|
99
|
+
setup — the git clone (`020`) deliberately does **not** use it (see gotchas).
|
|
100
|
+
|
|
101
|
+
## Gotchas (durable AL2023 / EB rules)
|
|
102
|
+
|
|
103
|
+
- **Write new-deploy files to `/var/app/ondeck/<path>`, never `/var/www/html/<path>` during
|
|
104
|
+
`container_commands`.** While container_commands run, `/var/www/html` still symlinks to the
|
|
105
|
+
**old** app. After the deploy swap, `/var/app/ondeck` becomes `/var/app/current`
|
|
106
|
+
(= `/var/www/html`). Writing to `/var/www/html` during container_commands writes into the
|
|
107
|
+
currently-running old app.
|
|
108
|
+
- **Only `/var/log/eb-activity.log` (and `cfn-init.log`) are captured in EB bundle snapshots.**
|
|
109
|
+
Custom log files under `/var/log/` are invisible unless you SSH the instance. Deploy scripts
|
|
110
|
+
should log to `/var/log/eb-activity.log`.
|
|
111
|
+
- **CodePipeline-dependent scripts are no-ops for apps without a pipeline.**
|
|
112
|
+
`setup_git_libraries.php` silently does nothing when the DB has no `CodePipelineEnvironments`
|
|
113
|
+
row — fatal for a brand-new app, since nothing gets cloned and the failure is silent.
|
|
114
|
+
- **Make clone failures block the deploy.** The bash clone script does `exit 1` on clone
|
|
115
|
+
failure so the deploy fails visibly, rather than succeeding and serving 500s.
|
|
116
|
+
- **PATH is unreliable in the cfn-init execution environment** — resolve binaries defensively,
|
|
117
|
+
e.g. `GIT=$(which git || echo /usr/bin/git)`.
|
|
118
|
+
- **cfn-init `files:` runs before `container_commands`** — the correct pattern for writing a
|
|
119
|
+
script and then executing it in the same config.
|
|
120
|
+
|
|
121
|
+
## Change history
|
|
122
|
+
- 2026-06-26 — Documented the AL2 → AL2023 (PHP 8.5) EB migration for Tools: package renames
|
|
123
|
+
(`libstdc++48`→`libstdc++`, `php73-ldap`→`php-ldap`, drop `libcurl`), php ini via cfn-init
|
|
124
|
+
`files:` writing `/etc/php.d/application.ini`, bash-based library clone replacing the
|
|
125
|
+
CodePipeline PHP script, duplicate-config neutralization, `ignoreErrors` on non-fatal infra
|
|
126
|
+
commands, and the ondeck-path / eb-activity.log / pipeline-noop gotchas (jcardinal)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
|
|
7
7
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
8
8
|
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
9
|
-
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php |
|
|
9
|
+
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, test/@dave/test_invoice_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
10
10
|
| [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php |
|
|
11
11
|
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
12
12
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-26
|
|
10
10
|
owners: [dfranks]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Component/Forecast/SaleImport/SaleImport.php
|
|
@@ -17,9 +17,14 @@ files:
|
|
|
17
17
|
- worker2/Worker/Netsuite/CashRefund.php
|
|
18
18
|
- worker2/Worker/Netsuite/Opportunity.php
|
|
19
19
|
- worker2/Worker/Netsuite/SalesOrder.php
|
|
20
|
+
- test/@dave/test_invoice_lifecycle.php
|
|
21
|
+
- test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js
|
|
22
|
+
- test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js
|
|
20
23
|
related:
|
|
21
24
|
- ../architecture.md
|
|
22
25
|
- ../../worker2/architecture.md
|
|
26
|
+
- ../../worker2/features/netsuite-salesorder-open-orders-sync.md
|
|
27
|
+
- ./netsuite-rest-client.md
|
|
23
28
|
---
|
|
24
29
|
|
|
25
30
|
## Summary
|
|
@@ -144,7 +149,51 @@ events as specific **subtypes** (`inventoryItem`, `nonInventoryResaleItem`, `kit
|
|
|
144
149
|
would be needed only for a future real-time item webhook, **not** for this Sales importer
|
|
145
150
|
(which keeps items fresh via the hourly item pull cron plus the inline self-heal).
|
|
146
151
|
|
|
152
|
+
## Local testing harness (`test/@dave/test_invoice_lifecycle.php`)
|
|
153
|
+
A CLI that boots worker2/_underscore (chdir to `worker2/` then `require index.php`) and drives a
|
|
154
|
+
**full NetSuite invoice lifecycle** through the live `_underscore` REST client
|
|
155
|
+
(`_Component_Api_Netsuite`) to exercise this importer end-to-end against a local Forecast mirror.
|
|
156
|
+
Actions: `check | create | get | update | delete | sync | recent | amq | deployments | locations`.
|
|
157
|
+
|
|
158
|
+
- **Fixture:** customer **58** ("8 Test Company") + item **103741** ("Test Other Charge for Sale",
|
|
159
|
+
maps to local `Forecast.Items` id 25), **rate 0** → a **$0 invoice that still yields one Sales
|
|
160
|
+
row** (the engine does **not** skip a $0 line — revenue `0.00` is written; same as the cron's
|
|
161
|
+
Sales section). Confirms create→insert, edit→update (tracked-column change-detection), and
|
|
162
|
+
delete→`removeAll` all land in local `Forecast.Sales`.
|
|
163
|
+
- It carries its **own** `createInvoiceRecord()` that POSTs via `_ApiRequest` and parses the
|
|
164
|
+
`Location` header with a **correct** regex (delimiter `~`) — a deliberate workaround for the live
|
|
165
|
+
`_Component_Api_Netsuite::createRecord()` `#`-delimiter bug (see the
|
|
166
|
+
[REST client doc](./netsuite-rest-client.md)); the framework was **not** edited this session.
|
|
167
|
+
- `amq` action SuiteQLs the AMQ custom record; `deployments` SuiteQLs `scriptdeployment`+`script`
|
|
168
|
+
to show which enqueuer fires per record type (used to find the dual-deployment trap below).
|
|
169
|
+
|
|
147
170
|
## Gotchas / known issues
|
|
171
|
+
- **Testing locally pollutes PROD unless you UNDEPLOY the prod AMQ enqueuer first.** Each sale
|
|
172
|
+
transaction type has **TWO** NetSuite UE enqueuer deployments: **"AMQ — Enqueuer"**
|
|
173
|
+
(`customscript_ue_amq_enqueue`, **RELEASED** → posts to **prod** `webhook.togahub.com`) and
|
|
174
|
+
**"AMQ development script"** (`customscript2619` → ngrok via `DEV_OVERRIDE`/`debugWrap`). A
|
|
175
|
+
**RELEASED** deployment fires for **all** actors **including the REST/M2M integration user**, so a
|
|
176
|
+
REST-driven test invoice hits **prod too**. Observed first run: test-invoice edits flowed to prod
|
|
177
|
+
and a $0 row was written to **prod** `Forecast.Sales` — by the **legacy 5-min pull cron**, NOT the
|
|
178
|
+
webhook (prod worker2 has no invoice handler deployed yet, so prod `Netsuite/Invoice/*` jobs
|
|
179
|
+
watchdog-timeout at 300s, `isSuccess=0`). **To isolate a local test, undeploy the prod enqueuer**
|
|
180
|
+
for the record types under test (invoice/cashSale/cashRefund/creditMemo/journalEntry); the second
|
|
181
|
+
full create→update→delete run was then cleanly isolated (zero prod `Logs.Webhook`/`Forecast.Sales`
|
|
182
|
+
for the test id). The **dev** enqueuer's `debugWrap` envelope
|
|
183
|
+
`{action,parameters:{payload,headers}}` is required because a local worker2 has no
|
|
184
|
+
`WebhookIngestion` Lambda to wrap the raw body.
|
|
185
|
+
- **No local app logs for debug-path webhooks — verify arrival via the ngrok inspector, and decode
|
|
186
|
+
the body.** The legacy `{action,parameters}` debug path writes **no** `WorkerJobs`/`Logs.Webhook`
|
|
187
|
+
locally. Confirm inbound arrival at the ngrok request inspector
|
|
188
|
+
`http://127.0.0.1:4040/api/requests/http` — but its **raw body is base64**, so decode the body
|
|
189
|
+
(after the `\r\n\r\n`) before grepping for `internalId`/`eventType`. The inspector buffer is
|
|
190
|
+
**ephemeral** (rotates/clears) — watch it live while triggering.
|
|
191
|
+
- **Sales create webhook races NetSuite REST read-after-write lag** — the handler GETs the invoice
|
|
192
|
+
before its line sublist materializes → **0 lines → 0 rows**. Same gotcha as the SalesOrder
|
|
193
|
+
open-orders sync (see that doc's read-after-write-lag gotcha); it self-heals on the next edit
|
|
194
|
+
webhook or the daily discrepancy-fix.
|
|
195
|
+
- **Local _underscore boots on PHP 8.0.30 (xampp8)** — the floor enforced in `_underscore.php` is
|
|
196
|
+
**8.0.0**, not 8.1.
|
|
148
197
|
- **`_Component_*` classes must live under the `_underscore` framework root, never a project
|
|
149
198
|
repo.** The autoloader rejects a `_Component_*`/`_Model_*` loaded from a project path
|
|
150
199
|
(e.g. `./` under worker2) with "namespace … has not been defined" — at **class-load
|
|
@@ -154,6 +203,19 @@ would be needed only for a future real-time item webhook, **not** for this Sales
|
|
|
154
203
|
- The cron's sign handling is not portable here — see Sign convention.
|
|
155
204
|
|
|
156
205
|
## Change history
|
|
206
|
+
- 2026-06-26 — **Verified the full create→update→delete webhook lifecycle end-to-end on a local box**
|
|
207
|
+
(ngrok tunnel → local worker2 → local `Forecast.Sales`), driving a real **$0** NetSuite invoice
|
|
208
|
+
through create→insert, edit→update (tracked-column change-detection), delete→`removeAll`; confirmed
|
|
209
|
+
a $0 line is **not** skipped (one revenue-`0.00` row). Built the reusable harness
|
|
210
|
+
`test/@dave/test_invoice_lifecycle.php` (boots worker2/_underscore, drives the lifecycle via
|
|
211
|
+
`_Component_Api_Netsuite`; `amq`/`deployments` SuiteQL actions). Recorded durable testing gotchas:
|
|
212
|
+
the **dual AMQ enqueuer** RELEASED-fires-for-the-REST-user prod-pollution trap (undeploy the prod
|
|
213
|
+
enqueuer to isolate; prod's $0 row came from the legacy 5-min pull cron, not a prod webhook —
|
|
214
|
+
prod has no invoice handler so its `Netsuite/Invoice/*` jobs watchdog-timeout `isSuccess=0`); no
|
|
215
|
+
local app logs on the debug `{action,parameters}` path (verify via the ngrok inspector at
|
|
216
|
+
`:4040`, body is base64, buffer ephemeral); the Sales create webhook shares the SalesOrder
|
|
217
|
+
read-after-write line-sublist race; and local _underscore's PHP floor is 8.0.0. No production code
|
|
218
|
+
change. (dfranks)
|
|
157
219
|
- 2026-06-25 — **Verified `sync()` end-to-end across all four sale types** (prod NS reads, local Forecast
|
|
158
220
|
writes; 40+ records spanning invoice/cashSale/creditMemo/cashRefund): revenue reconciled **to the penny**
|
|
159
221
|
against an independent SuiteQL `SUM(-foreignamount)` oracle on every record; the uniform sign factor held
|
|
@@ -57,10 +57,12 @@ through `_ApiRequest` directly (mirroring `send()`'s auth/endpoint/header setup)
|
|
|
57
57
|
> reads the class-`#` as a premature closing delimiter and the whole pattern **always throws
|
|
58
58
|
> `preg_match(): Unknown modifier ']'`**. This fires *after* the record is already created, so the
|
|
59
59
|
> create succeeds in NetSuite but `createRecord()` raises and the caller never gets the new id —
|
|
60
|
-
> it breaks the entire outbound create/push path
|
|
60
|
+
> it breaks the entire outbound create/push path **for any record type** (observed on `journalentry`
|
|
61
|
+
> and again on `invoice` create). **Fix direction:** change the delimiter so `#`
|
|
61
62
|
> isn't both delimiter and class member, e.g. `~/(\d+)(?:[?#]|$)~`. Interim workaround used in
|
|
62
|
-
> probes
|
|
63
|
-
>
|
|
63
|
+
> probes and in `test/@dave/test_invoice_lifecycle.php`: parse the new internalId off the `Location`
|
|
64
|
+
> value carried in the thrown exception message (the lifecycle harness ships its own correct
|
|
65
|
+
> `~`-delimited parse to sidestep this without editing the framework). Confirmed live 2026-06-26.
|
|
64
66
|
|
|
65
67
|
### Update — reuse `send('PATCH', $route, $body)`
|
|
66
68
|
|
package/knowledge/INDEX.md
CHANGED
|
@@ -12,7 +12,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
12
12
|
- **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
|
|
13
13
|
- **test** (Test) — 11 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
|
14
14
|
- **toga** (TOGa) — 2 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
|
|
15
|
-
- **tools** (Tools) —
|
|
15
|
+
- **tools** (Tools) — 5 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
|
|
16
16
|
|
|
17
17
|
## 2.0 framework
|
|
18
18
|
|
package/package.json
CHANGED