toga-ai 1.0.782 → 1.0.784

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.
Files changed (134) hide show
  1. package/knowledge/1.0/apps/dbchanges/INDEX.md +3 -3
  2. package/knowledge/1.0/apps/library/INDEX.md +23 -22
  3. package/knowledge/1.0/apps/library/features/cron-execution-monitoring.md +31 -1
  4. package/knowledge/1.0/apps/library/features/error-capture-1-0.md +68 -1
  5. package/knowledge/1.0/apps/library/features/netsuite-item-assettype-sync.md +202 -0
  6. package/knowledge/1.0/apps/library/features/netsuite-item-isfulfillable-sync.md +28 -2
  7. package/knowledge/1.0/apps/library/features/toga2-api-client-and-bridge.md +9 -1
  8. package/knowledge/1.0/apps/test/INDEX.md +18 -18
  9. package/knowledge/1.0/apps/toga/INDEX.md +4 -4
  10. package/knowledge/1.0/apps/togadesk/INDEX.md +14 -14
  11. package/knowledge/1.0/apps/togaview/INDEX.md +9 -9
  12. package/knowledge/1.0/apps/tools/INDEX.md +20 -20
  13. package/knowledge/1.0/apps/tools/features/talos-kb-documents-admin.md +0 -3
  14. package/knowledge/1.0/apps/tools/features/theme-light-dark.md +0 -2
  15. package/knowledge/1.0/apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md +0 -2
  16. package/knowledge/1.0/apps/walmarttechservices/INDEX.md +3 -3
  17. package/knowledge/1.0/apps/webhook/INDEX.md +3 -3
  18. package/knowledge/1.0/apps/worker/INDEX.md +21 -21
  19. package/knowledge/1.0/apps/worker/features/compass-ma-sales-order-exception-report.md +36 -5
  20. package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +181 -1
  21. package/knowledge/2.0/apps/_underscore/INDEX.md +59 -59
  22. package/knowledge/2.0/apps/_underscore/features/acl-permission-chain.md +70 -7
  23. package/knowledge/2.0/apps/_underscore/features/error-reporting-issue-event.md +86 -2
  24. package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +0 -2
  25. package/knowledge/2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md +0 -4
  26. package/knowledge/2.0/apps/_underscore/features/model-magic-field-access.md +8 -1
  27. package/knowledge/2.0/apps/_underscore/features/sales-order-denial-reason.md +0 -1
  28. package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +0 -29
  29. package/knowledge/2.0/apps/_underscore/features/tracking-number-bridges.md +0 -3
  30. package/knowledge/2.0/apps/ai-bdr/INDEX.md +15 -15
  31. package/knowledge/2.0/apps/ai-bdr/architecture.md +0 -2
  32. package/knowledge/2.0/apps/ai-bdr/features/live-call-status.md +0 -2
  33. package/knowledge/2.0/apps/ai-bdr/features/security-landing-page.md +0 -4
  34. package/knowledge/2.0/apps/ai-bdr/features/vapi-integration.md +0 -4
  35. package/knowledge/2.0/apps/ai-bdr/features/web-funnel-app.md +0 -7
  36. package/knowledge/2.0/apps/api2/INDEX.md +27 -27
  37. package/knowledge/2.0/apps/api2/features/api-payload-interceptors.md +0 -2
  38. package/knowledge/2.0/apps/api2/features/language-translation-layer.md +0 -7
  39. package/knowledge/2.0/apps/api2/features/nested-relationship-writes.md +115 -2
  40. package/knowledge/2.0/apps/api2/features/v2-rest-query-contract.md +28 -1
  41. package/knowledge/2.0/apps/dbchanges2/INDEX.md +12 -12
  42. package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +0 -48
  43. package/knowledge/2.0/apps/dbchanges2/workflows/client-schema-drift-audit.md +130 -2
  44. package/knowledge/2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md +43 -10
  45. package/knowledge/2.0/apps/saml/INDEX.md +8 -7
  46. package/knowledge/2.0/apps/saml/features/healthcheck-endpoint.md +133 -0
  47. package/knowledge/2.0/apps/talos/INDEX.md +10 -10
  48. package/knowledge/2.0/apps/talos/architecture.md +0 -3
  49. package/knowledge/2.0/apps/talos/features/aegra-api.md +0 -6
  50. package/knowledge/2.0/apps/talos/features/talos-agent.md +0 -3
  51. package/knowledge/2.0/apps/talos-backend/INDEX.md +3 -3
  52. package/knowledge/2.0/apps/toga-blox/INDEX.md +16 -16
  53. package/knowledge/2.0/apps/toga-blox/features/table.md +29 -2
  54. package/knowledge/2.0/apps/toga-blox/features/talos-assistant.md +0 -1
  55. package/knowledge/2.0/apps/toga2-commerce/INDEX.md +20 -20
  56. package/knowledge/2.0/apps/toga2-commerce/features/client-fields.md +29 -2
  57. package/knowledge/2.0/apps/toga2-commerce/features/inactive-item-purchase-gating.md +0 -2
  58. package/knowledge/2.0/apps/toga2-commerce/workflows/amplify-build-and-deploy.md +95 -2
  59. package/knowledge/2.0/apps/toga2-commerce/workflows/cypress-testing.md +0 -2
  60. package/knowledge/2.0/apps/toga2-hub/INDEX.md +4 -4
  61. package/knowledge/2.0/apps/toga2-supply/INDEX.md +10 -10
  62. package/knowledge/2.0/apps/toga2-supply/features/fulfill-and-ship.md +0 -2
  63. package/knowledge/2.0/apps/toga2-supply/workflows/client-host-scoping.md +0 -1
  64. package/knowledge/2.0/apps/toga2-view/INDEX.md +11 -11
  65. package/knowledge/2.0/apps/toga2-view/architecture.md +0 -1
  66. package/knowledge/2.0/apps/toga25-supply/INDEX.md +20 -20
  67. package/knowledge/2.0/apps/toga25-supply/features/record-modals-and-nested-tables.md +0 -4
  68. package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +30 -4
  69. package/knowledge/2.0/apps/toga25-supply/features/talos-integration.md +0 -7
  70. package/knowledge/2.0/apps/toga25-supply/features/transfer-orders-page.md +0 -5
  71. package/knowledge/2.0/apps/toga25-supply/workflows/cypress-testing.md +0 -2
  72. package/knowledge/2.0/apps/voice-to-voice/INDEX.md +6 -6
  73. package/knowledge/2.0/apps/worker2/INDEX.md +60 -56
  74. package/knowledge/2.0/apps/worker2/features/clickup-general-automation.md +75 -0
  75. package/knowledge/2.0/apps/worker2/features/netsuite-item-client-routing.md +132 -0
  76. package/knowledge/2.0/apps/worker2/features/netsuite-opportunity-sync.md +0 -1
  77. package/knowledge/2.0/apps/worker2/features/netsuite-supporting-record-webhook-importer.md +61 -14
  78. package/knowledge/2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md +65 -11
  79. package/knowledge/2.0/apps/worker2/features/oneuptime-worker2-monitoring.md +70 -4
  80. package/knowledge/2.0/apps/worker2/features/qa-qc-review-pipeline.md +92 -0
  81. package/knowledge/2.0/apps/worker2/features/sso-stability-monitor.md +102 -0
  82. package/knowledge/2.0/apps/worker2/features/talos-transcript-ingestion.md +0 -15
  83. package/knowledge/CONVENTIONS.md +51 -3
  84. package/knowledge/INDEX.md +3 -3
  85. package/knowledge/clients/adyen/INDEX.md +3 -3
  86. package/knowledge/clients/adyen/profile.md +87 -1
  87. package/knowledge/clients/aig/INDEX.md +5 -5
  88. package/knowledge/clients/canon/INDEX.md +3 -3
  89. package/knowledge/clients/compass-canada/INDEX.md +9 -9
  90. package/knowledge/clients/compass-canada/features/french-order-email-localization.md +0 -5
  91. package/knowledge/clients/compass-usa/INDEX.md +35 -34
  92. package/knowledge/clients/compass-usa/features/oneuptime-ma-refresh-order-monitor.md +150 -0
  93. package/knowledge/clients/compass-usa/profile.md +1 -0
  94. package/knowledge/clients/compass-usa/workflows/odp-order-pipeline-to-netsuite.md +12 -2
  95. package/knowledge/clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md +15 -1
  96. package/knowledge/clients/elite/INDEX.md +9 -9
  97. package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +241 -20
  98. package/knowledge/clients/elite/features/supply2-scope.md +0 -5
  99. package/knowledge/clients/elite/features/supply2-tableview-config-drift.md +74 -2
  100. package/knowledge/clients/elite/profile.md +22 -2
  101. package/knowledge/clients/endeavor-health/INDEX.md +3 -3
  102. package/knowledge/clients/fordham/INDEX.md +3 -3
  103. package/knowledge/clients/growrk/INDEX.md +6 -6
  104. package/knowledge/clients/growrk/features/transfer-order-flow.md +44 -3
  105. package/knowledge/clients/northwell/INDEX.md +4 -4
  106. package/knowledge/clients/nycdoe/INDEX.md +6 -6
  107. package/knowledge/clients/nycdoe/features/servicenow-integration.md +0 -2
  108. package/knowledge/clients/nychh/INDEX.md +9 -9
  109. package/knowledge/clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md +20 -1
  110. package/knowledge/clients/nychh/features/netsuite-transfer-order-import.md +53 -1
  111. package/knowledge/clients/nychh/features/transfer-order-netsuite-push.md +38 -3
  112. package/knowledge/clients/nychh/profile.md +25 -4
  113. package/knowledge/clients/office-depot/INDEX.md +5 -5
  114. package/knowledge/clients/pcmaticb2b/INDEX.md +6 -6
  115. package/knowledge/clients/prudential/INDEX.md +14 -14
  116. package/knowledge/clients/quad/INDEX.md +7 -7
  117. package/knowledge/clients/rate/INDEX.md +14 -14
  118. package/knowledge/clients/rate/features/subscription-cancellation.md +0 -2
  119. package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +0 -1
  120. package/knowledge/clients/rumcsi/INDEX.md +3 -3
  121. package/knowledge/clients/spglobal/INDEX.md +3 -3
  122. package/knowledge/clients/staples/INDEX.md +4 -4
  123. package/knowledge/clients/tow-foundation/INDEX.md +4 -4
  124. package/knowledge/clients/true/INDEX.md +4 -4
  125. package/knowledge/clients/walmart/INDEX.md +5 -5
  126. package/knowledge/clients/wje/INDEX.md +3 -3
  127. package/knowledge/standalone/apps/claude/INDEX.md +7 -7
  128. package/knowledge/standalone/apps/forward/INDEX.md +5 -5
  129. package/knowledge/standalone/apps/togatech/INDEX.md +7 -7
  130. package/knowledge/standalone/apps/togatech/features/seo-aeo-geo-prerender.md +0 -1
  131. package/knowledge/standalone/apps/websocket/INDEX.md +4 -4
  132. package/knowledge.js +85 -18
  133. package/package.json +1 -1
  134. package/skills/kickoff/SKILL.md +11 -7
@@ -1,5 +1,5 @@
1
1
  # dbchanges (Database Changes) — 1.0 knowledge
2
2
 
3
- | Doc | Summary | Files |
4
- |-----|---------|-------|
5
- | [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. | dbchanges/index.php, dbchanges/Core/, dbchanges/TOGaDeskSupport/, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. |
@@ -1,24 +1,25 @@
1
1
  # library (Library) — 1.0 knowledge
2
2
 
3
- | Doc | Summary | Files |
4
- |-----|---------|-------|
5
- | [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. | library/_.php, library/app/, library/browser/ |
6
- | [Address Validation Gateway (App_Api_OfficeDepot::validateAddress + USPS fallback)](features/address-validation-gateway.md) | `App_Api_OfficeDepot::validateAddress()` is the shared 1.0 (`App_`) address-validation gateway. | library/app/api/officedepot.php, library/app/api/usps.php |
7
- | [Where a new App_ class goes — the app/ folder IS a behavioral contract](features/app-class-placement-base-contracts.md) | In `library/app/`, choosing a folder is **not** a filing decision — the autoloader maps `App_<Folder>_<File>` to `app/<folder>/<file>.php`, and each folder's ba | library/app/model.php, library/app/client.php, library/app/api.php, library/app/api/servicerequest.php, library/app/api/volt.php, library/app/api/carrier/fedex.php |
8
- | [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability | library/app/sso.php, library/sso/togahub_private_key.key |
9
- | [Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)](features/cron-execution-monitoring.md) | `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out l | library/app/framework.php |
10
- | [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | Two "View Recommended Services" buttons exist in the TOGa Refresh 2026 SR view: 1. | library/app/model/toga/diagnostic.php, library/app/model/servicerequest.php |
11
- | [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 |
12
- | [App_Email Queued Sending & Attachments (Common.EmailsQueued)](features/email-queue-attachments.md) | `App_Email::send()` can either send **inline** (PHPMailer talks to SES right there) or **queue** the message: `base64(serialize($this))` is inserted into `Commo | library/app/email.php, library/phpmailer/class.phpmailer.php, worker/crons/notifications/infrastructure/send_emails.php, worker/crons/notifications/covid/send_covid_pending_vaccination_approval.php, togadesk/desk/includes/functions.php |
13
- | [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 |
14
- | [App_Email Side Effects & Test Mode (how to send a real email that writes nothing)](features/email-test-mode-and-write-free-sends.md) | `App_Email::send()` is **not** side-effect free. | library/app/email.php, library/app/model/logs/email.php, library/app/framework.php |
15
- | [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | The 1.0 side of the platform error-reporting pipeline (TRUE-78188). | library/app/error/capture.php, library/app/error.php, library/app/exception/business.php, library/app/api/toga2.php, library/app/cloud.php, worker/config.worker.ini, worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php |
16
- | [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — and why its \"Error Type\" is not a diagnosis](features/http-500-error-monitor.md) | `App_SystemMonitor_500Error` (`library/app/systemmonitor/500error.php`, title **"HTTP 500 Error Alert"**) is the 1.0 system monitor that watches **`Logs.Api` fo | library/app/systemmonitor/500error.php, worker/crons/infrastructure/system_monitors.php, api2/Controller/Index.php, _underscore/Error.php |
17
- | [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 |
18
- | [NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)](features/netsuite-filecabinet-restlet.md) | How 1.0 pulls **File Cabinet binary content** (invoice PDFs) out of NetSuite over REST. | library/app/api/netsuite/rest.php, worker/crons/toga2/netsuite/netsuite_restlet_filecontent.js, worker/config.worker.ini |
19
- | [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 |
20
- | [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 |
21
- | [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, library/app/netsuite.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
22
- | [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 |
23
- | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. | library/app/api/toga2.php, library/app/api/startechticket.php, worker/crons/toga2/startech/common_import_supporting_records.php |
24
- | [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross | library/app/api/toga2.php, worker/crons/toga2/aig/sync_togasupply_aig.php, worker/crons/toga2/wje/sync_togasupply_wje.php, test/@Mark/AIG/test_multi_email.php |
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [Library (1.0 Framework) Architecture](architecture.md) | `library` is the shared library repository for **all 1.0 (legacy) applications** — the `App_` framework. |
6
+ | [Address Validation Gateway (App_Api_OfficeDepot::validateAddress + USPS fallback)](features/address-validation-gateway.md) | `App_Api_OfficeDepot::validateAddress()` is the shared 1.0 (`App_`) address-validation gateway. |
7
+ | [Where a new App_ class goes — the app/ folder IS a behavioral contract](features/app-class-placement-base-contracts.md) | In `library/app/`, choosing a folder is **not** a filing decision — the autoloader maps `App_<Folder>_<File>` to `app/<folder>/<file>.php`, and each folder's ba |
8
+ | [App_Sso — Reusable 1.0 SSO Initiation (SP-initiated SAML via saml.togahub.com)](features/app-sso-initiation.md) | `App_Sso` (`library/app/sso.php`) is the **1.0 port of the 2.0 SAML gateway's SP-initiated SSO initiation**, packaged as a reusable, framework-level capability |
9
+ | [Cron Execution Monitoring (App_Framework check-in/out → CronJobExecutions)](features/cron-execution-monitoring.md) | `App_Framework::cronInitialization()` / `App_Framework::cronFinished()` (in `library/app/framework.php`) give every 1.0 (`App_`) cron job a check-in/check-out l |
10
+ | [Diagnostic Dialog — View Recommended Services Routing](features/diagnostic-dialog-view-recommended-services.md) | Two "View Recommended Services" buttons exist in the TOGa Refresh 2026 SR view: 1. |
11
+ | [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. |
12
+ | [App_Email Queued Sending & Attachments (Common.EmailsQueued)](features/email-queue-attachments.md) | `App_Email::send()` can either send **inline** (PHPMailer talks to SES right there) or **queue** the message: `base64(serialize($this))` is inserted into `Commo |
13
+ | [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. |
14
+ | [App_Email Side Effects & Test Mode (how to send a real email that writes nothing)](features/email-test-mode-and-write-free-sends.md) | `App_Email::send()` is **not** side-effect free. |
15
+ | [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | The 1.0 side of the platform error-reporting pipeline (TRUE-78188). |
16
+ | [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — and why its \"Error Type\" is not a diagnosis](features/http-500-error-monitor.md) | `App_SystemMonitor_500Error` (`library/app/systemmonitor/500error.php`, title **"HTTP 500 Error Alert"**) is the 1.0 system monitor that watches **`Logs.Api` fo |
17
+ | [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 |
18
+ | [NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)](features/netsuite-filecabinet-restlet.md) | How 1.0 pulls **File Cabinet binary content** (invoice PDFs) out of NetSuite over REST. |
19
+ | [assetType from NetSuite itemtype during Item Sync (opt-in per client)](features/netsuite-item-assettype-sync.md) | `getCreateItem()` never sent `assetType`, so **every item the NetSuite importer created had `Items.assetTypeId = NULL`** — for every client, since the importer |
20
+ | [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 |
21
+ | [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 |
22
+ | [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. |
23
+ | [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 |
24
+ | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. |
25
+ | [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross |
@@ -6,12 +6,14 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-25
9
+ updated: 2026-09-08
10
10
  owners: [dfranks, bala]
11
11
  files:
12
12
  - library/app/framework.php
13
13
  related:
14
14
  - ../../worker/architecture.md
15
+ - ./email-queue-attachments.md
16
+ - ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
15
17
  - ../../worker/workflows/tracing-a-worker-cron-run-in-production.md
16
18
  ---
17
19
 
@@ -51,6 +53,28 @@ row in `db_log` and of the per-job Sentry check-in monitors.
51
53
  (`db_common`), *not* by POSTing from 1.0 to a 2.0 API endpoint. Per Jeff Cardinal, 1.0 code
52
54
  must not POST to 2.0 code; the direct shared-DB write is the sanctioned path (TRUE-78182).
53
55
 
56
+ ## For an emailing cron, this table is the ONLY proof of a run
57
+
58
+ There is **no sent-email log for a 1.0 `App_Email` / `App_Email_Agilant` ops email**, so
59
+ `CronJobExecutions` is the only per-run evidence a 1.0 emailing cron leaves:
60
+
61
+ - `Logs_<Client>.Email` holds **2.0 application email only**. Verified 2026-09-08: zero rows in
62
+ `Logs_Compass.Email` for the Compass Refresh Exception Report, a 1.0 cron email that has been
63
+ sending for months.
64
+ - `Common.EmailsQueued` is a **queue drained on send**, holding a serialized object — not a log.
65
+ See [App_Email Queued Sending & Attachments](./email-queue-attachments.md).
66
+
67
+ Two consequences:
68
+
69
+ 1. **A cron whose only output is a "something is wrong" email is effectively unmonitored** — a
70
+ silent day and a dead cron look identical. Monitor the **backlog the cron drains**, not the
71
+ email. See
72
+ [OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md).
73
+ 2. **worker2 (2.0) has no proven connection to the legacy `Common` DB**, so a 2.0 OneUptime
74
+ monitor cannot read `CronJobExecutions` today. A generic "this 1.0 cron has not checked in"
75
+ monitor is therefore blocked on that connection, not on the data — the columns needed
76
+ (`dtCheckIn`, `job`, `instanceId`) are already there.
77
+
54
78
  ## Gotchas
55
79
 
56
80
  - **⚠ A run skipped by the overlap guard leaves NO ROW — absence is ambiguous.**
@@ -76,6 +100,12 @@ row in `db_log` and of the per-job Sentry check-in monitors.
76
100
  logic. (Fixed 2026-07-06: consolidated back to one `db_common` INSERT/UPDATE pair.)
77
101
 
78
102
  ## Change history
103
+ - 2026-09-08 — Recorded that `CronJobExecutions` is the **only** per-run proof for a 1.0
104
+ *emailing* cron, because there is no sent-email log: `Logs_<Client>.Email` is 2.0-only (zero
105
+ rows for a 1.0 report that has emailed for months) and `Common.EmailsQueued` is a queue drained
106
+ on send. Hence the rule to monitor the backlog rather than the email, and the note that worker2
107
+ has no proven legacy-`Common` connection, so a 2.0 "cron has not checked in" monitor is not
108
+ possible yet. No code change. (bala)
79
109
  - 2026-08-25 — Corrected the schema note (prod `CronJobExecutions` **does** carry a `note` TEXT
80
110
  column, unused by `App_Framework`, usable for temporary cron tracing) and separated it from the
81
111
  `note = 'Started execution'` row `cronInitialization()` writes to `Log` on `db_log`. Recorded
@@ -6,12 +6,14 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-17
9
+ updated: 2026-09-08
10
10
  owners: ["jcardinal", "mhammontree"]
11
11
  files:
12
12
  - library/app/error/capture.php
13
13
  - library/app/error.php
14
14
  - library/app/exception/business.php
15
+ - library/app/exception/api.php
16
+ - library/app/api/netsuite/rest.php
15
17
  - library/app/api/toga2.php
16
18
  - library/app/cloud.php
17
19
  - worker/config.worker.ini
@@ -78,6 +80,50 @@ whichever occurrence happened to be seen first — i.e. one arbitrary order numb
78
80
 
79
81
  Compass USA's sales-order → MITS transmit rejection was the first business-exception use case.
80
82
 
83
+ ### `App_Exception_Api` — one Issue per outbound-API outage across both frameworks (added 2026-09-08)
84
+
85
+ The 1.0 twin of 2.0's `_Exception_Api`. Read the
86
+ [2.0 section](../../../2.0/apps/_underscore/features/error-reporting-issue-event.md#outbound-third-party-api-errors-fingerprint-on-servicestatusoperation-not-the-trace-added-2026-09-08)
87
+ for the full rationale — this covers only the 1.0 specifics.
88
+
89
+ - **`library/app/exception/api.php`** — `class App_Exception_Api extends Exception`, carrying
90
+ `service` / `httpStatus` / `operation`. **PHP 7.2, so untyped properties with `@var` docblocks**
91
+ (typed properties fail to parse in `library/` — same trap as `App_Exception_Business`). Extends
92
+ plain `Exception`, so it stays HTTP 500 + creates a `Logs.Issue`.
93
+ - **`App_Error_Capture` gains the same `instanceof` fingerprint branch** →
94
+ `buildApiFingerprint()`, keyed on `service + httpStatus + operation`, with the fingerprint text
95
+ using the **FIXED literal `'apiError'` prefix** — byte-identical to 2.0's, so one
96
+ service+status+operation is **one Issue whichever framework threw it**. Do not derive the prefix
97
+ from the class name (`App_Exception_Api` ≠ `_Exception_Api`), and do not reformat the string on
98
+ either side.
99
+ - **A new `firstApplicationArea()` helper was added here.** The 1.0 twin previously lacked it — the
100
+ DB fingerprint branch is 2.0-only, so 1.0 had no such helper before this. `buildApiFingerprint()`
101
+ caps `operation` at 200 chars, matching 2.0.
102
+
103
+ #### The NetSuite client is where granularity is decided — `library/app/api/netsuite/rest.php`
104
+
105
+ A private `buildApiException(message, method, url, statusCode)` was added, and the **3 HTTP-status
106
+ throw sites** (OAuth token, API request, RESTlet call) switched from plain `\Exception` to it. The
107
+ **3 config/parse-error throws** in the same file (no HTTP status) were left as plain `\Exception`
108
+ on purpose.
109
+
110
+ - **5xx** (NetSuite-side outage) → `operation = 'SERVER_ERROR'` (constant), so the whole outage
111
+ folds into ONE Issue regardless of endpoint or client.
112
+ - **4xx** (request-specific) → `operation = "<METHOD> <path>"`, so a genuine per-endpoint problem
113
+ keeps its own Issue. Because every `list*` call goes through `suiteqlListAll()` hitting the one
114
+ endpoint `POST /query/v1/suiteql`, all the `list*` 400s collapse together on their own.
115
+ - **cso hardening (in `buildApiException`):** id-like path segments are masked
116
+ (`preg_replace('#/\d+(?=/|$)#', '/{id}')`); an empty parsed path uses a fixed `UNKNOWN_PATH`
117
+ placeholder — **never the raw `$url`**, which would carry the query string back into the shared
118
+ fingerprint. Net guarantee: only `service` + `httpStatus` + `operation` (all controlled values)
119
+ reach `Logs.IssueFingerprint` — the vendor message body never does.
120
+
121
+ > **OPEN (pre-existing, not fixed here):** the assembled failure message still carries
122
+ > `print_r($response)` (the full vendor body, which can hold one tenant's PII) into
123
+ > `Issue.subject` / `Event.errorMessage` on the shared `Logs` cluster. Unchanged from the old plain
124
+ > throw; never touches the fingerprint. Worth a future ticket to cap/redact — same shape as the
125
+ > raw-SQL-in-subject open item on the 2.0 doc.
126
+
81
127
  ### Client attribution — resolved from the API client uuid at `App_Api_Toga2::authenticate()`
82
128
 
83
129
  `App_Error::setCurrentClientId()` exists in 1.0 for parity with 2.0's ambient current-client, but
@@ -177,6 +223,10 @@ exists in `api2`/`worker2`. So 1.0 reads the AL1 container config at
177
223
 
178
224
  ## Gotchas / known issues
179
225
 
226
+ - **A `*/` inside PHP docblock prose silently closes the comment early.** Writing something like
227
+ `list*/fetch*` in a `/** … */` block ends the docblock at the `*/`, and `php -l` then reports a
228
+ confusing `syntax error, unexpected token` on a later line. Hit while documenting the NetSuite
229
+ throw sites. Avoid the literal `*/` in docblock text.
180
230
  - **⚠ A grouped `Logs.Issue` row can show a STALE error id — read the live api2 transaction log for
181
231
  the current error.** Two facts combine to mislead: (1) `Event.errorMessage` is **`varchar(255)`**,
182
232
  so a long API response is **truncated** — often before the trailing error id; and (2)
@@ -229,6 +279,23 @@ exists in `api2`/`worker2`. So 1.0 reads the AL1 container config at
229
279
 
230
280
  ## Change history
231
281
 
282
+ - 2026-09-08 — **Added the 1.0 twin of the outbound-API fingerprint fix.** NetSuite failures were
283
+ **over-splitting** into ~40 Issues per outage (top frames differ per `list*`/`fetch*` caller and
284
+ per-client cron; prod `Logs.Issue` 678–692 = one HTTP 500 OAuth outage, 699–731 = one HTTP 400
285
+ wave). New `App_Exception_Api` (`library/app/exception/api.php`, extends plain `Exception`,
286
+ **PHP 7.2 untyped `@var` props**; `service`/`httpStatus`/`operation`), an `instanceof`
287
+ `buildApiFingerprint()` branch in `App_Error_Capture` keyed on service+status+operation with a
288
+ **FIXED literal `'apiError'` prefix** byte-identical to 2.0's (so one outage = one Issue across
289
+ both frameworks), and a new `firstApplicationArea()` helper (1.0 lacked it — the DB branch is
290
+ 2.0-only). `library/app/api/netsuite/rest.php` gained a private `buildApiException()` and its
291
+ 3 HTTP-status throw sites moved off plain `\Exception` (the 3 config/parse throws stayed plain on
292
+ purpose); granularity is set there — 5xx → constant `operation='SERVER_ERROR'`, 4xx →
293
+ `"<METHOD> <path>"` (all `list*` go through one `suiteql` endpoint so their 400s collapse). cso
294
+ hardening: id-like path segments masked to `/{id}`, empty path → `UNKNOWN_PATH` (never the raw
295
+ URL), `operation` capped at 200 chars — only controlled values reach the fingerprint. Deploy
296
+ `_underscore`, then `library`, then redeploy `worker`; not yet proven against a live failure.
297
+ **OPEN (pre-existing):** the message still carries `print_r($response)` (vendor body / PII) into
298
+ `Issue.subject`/`Event.errorMessage` — never touches the fingerprint. (jcardinal)
232
299
  - 2026-08-17 — Recorded a diagnostic trap: a grouped `Logs.Issue` row can show a **stale error id**
233
300
  because `Event.errorMessage` is `varchar(255)` (truncates a long API response, often before the
234
301
  error id) and `Issue.subject`/`errorMessage` keep the **first-occurrence** text — observed issue
@@ -0,0 +1,202 @@
1
+ ---
2
+ title: assetType from NetSuite itemtype during Item Sync (opt-in per client)
3
+ framework: "1.0"
4
+ repo: library
5
+ project: Library
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-09-04
10
+ owners: [rgirish]
11
+ files:
12
+ - library/app/api/toga2.php
13
+ - library/app/api/netsuite/rest.php
14
+ - worker/crons/toga2/netsuite/common_sync_togasupply.php
15
+ - worker/crons/toga2/netsuite/sync_togasupply_elite.php
16
+ related:
17
+ - netsuite-item-isfulfillable-sync.md
18
+ - netsuite-suiteql-rest-shim.md
19
+ - toga2-api-client-and-bridge.md
20
+ - ../../worker/features/netsuite-togasupply-per-client-sync.md
21
+ - ../../../2.0/apps/api2/features/nested-relationship-writes.md
22
+ - ../../../2.0/apps/_underscore/features/acl-permission-chain.md
23
+ - ../../../clients/elite/features/netsuite-togasupply-sync.md
24
+ ---
25
+
26
+ ## Summary
27
+
28
+ `getCreateItem()` never sent `assetType`, so **every item the NetSuite importer created had
29
+ `Items.assetTypeId = NULL`** — for every client, since the importer began. The string `assetType`
30
+ appeared nowhere in `library/app/api/toga2.php` nor in `worker/crons/toga2/netsuite/*`.
31
+
32
+ Measured on production `Client_Elite.Items`: **113 rows, 110 with `assetTypeId = NULL`**. The only
33
+ 3 non-NULL rows (`Laptop`, `Test`, `Test 2`) have `c_netsuiteInternalItemId = NULL` and
34
+ `inventoryType = HYBRID` — hand-made test rows, not imported ones.
35
+
36
+ This doc covers the fix: NetSuite's `item.itemtype` is now mapped to an `AssetTypes.uuid` and
37
+ stamped on the item. It is **opt-in per client** and deliberately narrow, because two platform
38
+ facts (below) make the obvious implementation break item imports outright.
39
+
40
+ Sibling doc: [isFulfillable](netsuite-item-isfulfillable-sync.md) — the other NetSuite flag stamped
41
+ by the same function. **The two are not symmetric** and must not be implemented the same way; see
42
+ [Why this is not modeled on isFulfillable](#why-this-is-not-modeled-on-isfulfillable).
43
+
44
+ ## ⚠⚠ TWO blockers — read before adding this for another client
45
+
46
+ Both were found under CTO review of a first proposal that looked obviously correct. Either one
47
+ turns "the asset type is not set" into **"the item create fails outright."**
48
+
49
+ ### 1. `Items.assetTypeId` is not writable by the API role in most tenants
50
+
51
+ api2 **aborts the entire record write** when a payload names a field the authenticating role cannot
52
+ write. The NetSuite sync authenticates as **roleId 3 (API)**. So simply adding `assetType` to the
53
+ payload does not merely fail to set the asset type — it **fails the item create**.
54
+
55
+ Verified on production `Client_Elite.AclFieldPermissions`:
56
+
57
+ | recordFieldId | Field | Roles granted |
58
+ |---|---|---|
59
+ | **269** | `Items.assetTypeId` | roleId **1 (Base) only** |
60
+ | 107 | `Items.manufacturerId` | roleId **1 and 3 (API)** |
61
+
62
+ **A `dbchanges2/Client_<Tenant>/` grant must land BEFORE the code deploy.** Elite's is
63
+ `dbchanges2/Client_Elite/2026-09-04a - EliteItemAssetTypeApiWritePermission.sql`. Mechanics:
64
+ [ACL permission chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md).
65
+
66
+ ### 2. `assetTypeId`'s childPolicy is `MATCH`, not `MATCH_UPSERT` — so get-or-create does NOT work
67
+
68
+ `getCreateManufacturer()` sits in the **same function** and does a get-or-create by name. **That
69
+ pattern does not transfer.** Manufacturers auto-create precisely because their field is
70
+ `MATCH_UPSERT`; `assetTypeId` is plain `MATCH`.
71
+
72
+ Verified on production `Core.RecordFields`: id **269** (`assetTypeId`) = `MATCH`; id **107**
73
+ (`manufacturerId`) = `MATCH_UPSERT`.
74
+
75
+ With `MATCH`, sending `assetType: {name: 'Services'}` for a name that has no row is a
76
+ **field-reference error that fails the record** — it does not create the row. Correct approach:
77
+ **resolve name → uuid up front (`GET /asset-types`) and send `{uuid}`.** Policy semantics:
78
+ [nested-relationship writes](../../../2.0/apps/api2/features/nested-relationship-writes.md).
79
+
80
+ > **General lesson:** never reason by analogy from a sibling nested write in the same function.
81
+ > Check that field's own `Core.RecordFields.childPolicy` first. Two fields on the same record
82
+ > routinely differ.
83
+
84
+ ## Key files / entry points
85
+
86
+ - `library/app/api/netsuite/rest.php` — `fetchItemFlagsByIds()` now selects `itemtype` in its
87
+ SuiteQL and exposes it as `->itemType`. **Free**: that query already selected from `item` once
88
+ per 500-id batch, so no extra round-trip. Returns `null` for an empty itemtype.
89
+ - `library/app/api/toga2.php`
90
+ - `getAssetTypeUuidForNetsuiteItemType()` (~6358) — new private helper. Resolves a NetSuite
91
+ itemtype to an `AssetTypes.uuid` through the launcher's opt-in map. Returns `null` when the
92
+ client has not opted in or the itemtype is unmapped; **throws** when a configured mapping names
93
+ an asset type the client does not have.
94
+ - `getCreateItem()` (~6474) — sends `assetType: {uuid}` on create, and **fills it on existing
95
+ items only when currently NULL**.
96
+ - `worker/crons/toga2/netsuite/common_sync_togasupply.php` (~298) — the bulk `/items` GET now
97
+ requests `assetType => ['uuid']`. **Without this the fill-NULL-only check has nothing to read and
98
+ would re-PUT every item every run** (the same trap `isFulfillable` fell into — see its doc).
99
+ - `worker/crons/toga2/netsuite/sync_togasupply_elite.php` — the only launcher that opts in today.
100
+
101
+ ## How it works
102
+
103
+ 1. `fetchItemFlagsByIds()` returns `->itemType` alongside the serialized / fulfillable flags, one
104
+ SuiteQL query per 500 items.
105
+ 2. `getCreateItem()` calls `getAssetTypeUuidForNetsuiteItemType($itemFlags->itemType, …)`.
106
+ 3. The helper reads the launcher constant `NETSUITE_ITEM_TYPE_TO_ASSET_TYPE_NAME`
107
+ (`itemtype => AssetTypes.name`). **If the constant is not defined, it returns `null`** and the
108
+ `assetType` key is **omitted from the payload entirely**.
109
+ 4. On first need it `GET /asset-types` once and builds a `strtoupper(name) => uuid` map, skipping
110
+ rows with a NULL/blank name. **The cache is keyed by client uuid** — one worker process can
111
+ handle several clients in a run.
112
+ 5. Name not found → **throw**, naming the itemtype, the mapped name, and the fix. Fail loud, per the
113
+ team rule for import/sync/cron code.
114
+ 6. Create → `assetType: {uuid}` in the POST. Existing item → set it **only if
115
+ `$existingItem->assetType` is NULL**.
116
+
117
+ ## Design rules (and why each one)
118
+
119
+ | Rule | Why |
120
+ |---|---|
121
+ | **Opt-in via a launcher constant** | `getCreateItem()` is shared by ~20 client sync launchers. Undefined constant ⇒ the payload key is absent ⇒ the other ~19 clients are **byte-for-byte unaffected**. |
122
+ | **Link by uuid, never by name** | `assetTypeId` is `MATCH` — a name for a missing row fails the whole record (blocker 2). |
123
+ | **Never auto-create an `AssetTypes` row** | `AssetTypes` is free text with per-client `AUTO_INCREMENT` ids and **no slug or code**, so nothing marks a row as sync-made vs. operator-made. Verified: `Client_Elite.AssetTypes` has 2 rows (1=`Laptop`, 2=`Services`); `Client_Compass.AssetTypes` has **38 hand-curated rows** (`ACCESSORY`, `EQUIPMENT`, `FEE`, `CONSULTING`, `WARRANTY`, `HP LAPTOP ACCESSORIES`, … plus an **empty-name row at id 23**) and **no `Services` row at all**. Auto-creating would pollute that taxonomy. |
124
+ | **Fail loudly on a missing asset type** | A silent skip leaves a client half-stamped with no signal. |
125
+ | **Fill-NULL-only on existing items** | `assetTypeId` is shared, **hand-curated per-client** data — unlike `isFulfillable`, which is a boolean NetSuite owns. Also means a manual TOGa correction of a NetSuite-mis-typed item **survives later syncs**. |
126
+ | **Cache keyed by client uuid** | One worker process can service several clients. |
127
+
128
+ ## Why this is not modeled on isFulfillable
129
+
130
+ [isFulfillable](netsuite-item-isfulfillable-sync.md) **refreshes on every difference** — and that
131
+ doc's own ⚠ CRITICAL section records the cost: the refresh **reverts any local override**, so a
132
+ hand fix silently disappears within a day.
133
+
134
+ `assetType` deliberately does the opposite (**fill-NULL-only**). NetSuite does not own this field;
135
+ the client's operators do. Do not "make it consistent" with `isFulfillable` — the asymmetry is the
136
+ design.
137
+
138
+ ## NetSuite `itemtype` does NOT cleanly identify services
139
+
140
+ `item.itemtype` **is** populated (account-wide: InvtPart 51336, NonInvtPart 2652, Group 585,
141
+ Service 472, Kit 50, OthCharge 24, Discount 17, Expense 6, Description 3). But **`NonInvtPart` is
142
+ mixed** — it holds real services. For Elite (113 items: 65 InvtPart, 11 Service, 17 NonInvtPart),
143
+ `itemtype = 'Service'` alone marks only **11 of ~28** real services; the rest sit in `NonInvtPart`
144
+ (`SVC-FS-DEPLOY`, `SVC-FS-SHIPPING-*`, `SVC-CI-RETAINER-RS`, `SVC-TS-Removal`, `Project - Cabling`).
145
+
146
+ So a usable mapping must include the non-inventory types, not just `Service`. Elite's map is in
147
+ [Elite's sync doc](../../../clients/elite/features/netsuite-togasupply-sync.md).
148
+
149
+ **Residual gap: NetSuite data hygiene, not a code problem.** Three Elite items are typed
150
+ `InvtPart` in NetSuite though they are services (`SVC-TS-ELITE-HDONBOARDING`, `CONFIG/INSTALL`,
151
+ `CONF-RM-INSTALL-SUPP`). **Decided: this is a client data-hygiene ask, NOT a code special case** —
152
+ a part-number allowlist inside a 20-client shared import is unmaintainable. Fill-NULL-only means a
153
+ manual TOGa correction sticks.
154
+
155
+ ## Gotchas / known issues
156
+
157
+ - **⚠ Existing items stay NULL until a cursor reset.** `getCreateItem()` only runs when a
158
+ transaction line references an item, so already-imported items are **not** back-stamped by
159
+ turning this on. Elite's 110 NULL items stay NULL until a cursor rollback makes the sync re-walk
160
+ transactions that reference them. Cursors live in the **client** database `Parameters` table
161
+ (key `NETSUITE_LAST_SYNC_CURSOR_*`, value `"<lastmodifieddate>|<netsuiteInternalId>"`), read via
162
+ `GET /parameters`. See [per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md).
163
+ - **⚠ Lumping `Discount` / `Expense` / `Description` into a "Services" asset type is semantically
164
+ loose.** Harmless while `assetTypeId` only hides rows on a page — a **latent bug if it ever drives
165
+ billing or reporting.** Revisit the map before wiring `assetTypeId` into either.
166
+ - **An empty-name `AssetTypes` row exists in the wild** (`Client_Compass` id 23). The name→uuid
167
+ cache skips NULL/blank names so it can never be matched by accident.
168
+ - **⚠ PRE-EXISTING BUG, deliberately NOT fixed — needs its own ticket.** The fulfillability refresh
169
+ (`toga2.php` ~6547) reads `$existingItem->isFulfillable`, but the bulk `GET /items` lookup in
170
+ `common_sync_togasupply.php` **never requested that field**. So `$currentFulfillable` is always
171
+ `null` and the sync almost certainly **PUTs `isFulfillable` on every item, every run, for every
172
+ client**. Left untouched on purpose: fixing it changes behavior for all ~20 clients and was out of
173
+ scope. This is the exact trap `assetType` avoids by requesting `assetType => ['uuid']` in the same
174
+ lookup.
175
+
176
+ ## Change history
177
+
178
+ - 2026-09-04 — **Built assetType stamping from NetSuite `itemtype`.** Root-caused that
179
+ `getCreateItem()` never sent `assetType`, so every imported item had `assetTypeId = NULL` (prod
180
+ `Client_Elite`: 110 of 113). Added `itemtype` to `fetchItemFlagsByIds()`'s existing SuiteQL
181
+ (free), the `getAssetTypeUuidForNetsuiteItemType()` helper, `assetType => ['uuid']` to the bulk
182
+ `/items` GET, and the create/fill paths. Recorded the **two blockers** that break the naive
183
+ version — `Items.assetTypeId` (recordField 269) is granted to **roleId 1 only** while the sync
184
+ authenticates as roleId 3, and api2 fails the **whole** record write on a non-writable field; and
185
+ 269's childPolicy is **`MATCH`**, not the `MATCH_UPSERT` that makes the sibling
186
+ `getCreateManufacturer()` get-or-create work, so a name-only nested object is a hard failure.
187
+ Design: opt-in per launcher, link by uuid, never auto-create an `AssetTypes` row, fail loud on a
188
+ missing one, **fill-NULL-only** (the deliberate opposite of `isFulfillable`'s revert-everything
189
+ refresh). Also collapsed the existing-item branch's **three separate PUTs to the same
190
+ `/items/<uuid>`** into one, and recorded the pre-existing `isFulfillable`-never-requested bug as a
191
+ separate ticket. (rgirish)
192
+
193
+ ## Related docs
194
+
195
+ - [isFulfillable from NetSuite during Item Sync](netsuite-item-isfulfillable-sync.md) — the sibling
196
+ flag on the same function, with the opposite refresh policy.
197
+ - [Nested-relationship writes](../../../2.0/apps/api2/features/nested-relationship-writes.md) —
198
+ `MATCH` vs `MATCH_UPSERT` and why link-by-uuid is the only safe form.
199
+ - [ACL permission chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md) — the
200
+ field-write grant this feature needs per tenant.
201
+ - [Elite NetSuite → TOGa Supply sync](../../../clients/elite/features/netsuite-togasupply-sync.md)
202
+ — the only client opted in, and its itemtype map.
@@ -6,14 +6,15 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-12
10
- owners: [bala]
9
+ updated: 2026-09-04
10
+ owners: [bala, rgirish]
11
11
  files:
12
12
  - library/app/netsuite.php
13
13
  - library/app/api/toga2.php
14
14
  - worker/crons/toga2/netsuite/common_sync_togasupply.php
15
15
  - worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php
16
16
  related:
17
+ - netsuite-item-assettype-sync.md
17
18
  - toga2-api-client-and-bridge.md
18
19
  - ../../worker/features/netsuite-togasupply-per-client-sync.md
19
20
  - ../../worker/workflows/isfulfillable-multi-client-backfill.md
@@ -116,6 +117,22 @@ sweep that started 13:45 and set every item to `1`.
116
117
  each verified to trace through an Agilant source.
117
118
 
118
119
  ## Gotchas / known issues
120
+ - **⚠⚠ The refresh's diff is BROKEN — `isFulfillable` is never read back, so it is PUT on every
121
+ item, every run, for every client.** The refresh at `toga2.php` ~6547 compares NetSuite's value
122
+ against `$existingItem->isFulfillable`, but the bulk `GET /items` lookup in
123
+ `common_sync_togasupply.php` **never requested that field** — so `$currentFulfillable` is always
124
+ `null` and the diff can never match. The 2026-07-28 entry below claims the field was added to that
125
+ field list; **it is not there** (verified 2026-09-04). Net effect: the "diff-only PUT keeps
126
+ re-syncs no-op" guarantee does not hold. **Not fixed — needs its own ticket**, because a fix
127
+ changes write volume for all ~20 clients. The sibling
128
+ [assetType feature](netsuite-item-assettype-sync.md) avoids this by requesting
129
+ `assetType => ['uuid']` in the same lookup.
130
+ - **The existing-item branch now sends ONE PUT, not three.** It previously issued up to three
131
+ separate PUTs to the same `/items/<uuid>` with identical options (`inventoryType`,
132
+ `isFulfillable`, and now `assetType`). One payload is collected and a single PUT is sent when
133
+ non-empty. Side effect worth knowing: `inventoryType` was previously never written back to the
134
+ in-run lookup, so a repeat line for the same part number re-PUT it — all three fields now update
135
+ the lookup.
119
136
  - **Value stamped only on the Agilant source item here** — the client-facing copy is set by the 2.0
120
137
  interceptor. If the interceptor rows aren't deployed in the target env, api2 **403s the whole item
121
138
  write** on the unknown `isFulfillable` field (see the Phase-2 doc's deploy gotchas).
@@ -125,6 +142,15 @@ sweep that started 13:45 and set every item to `1`.
125
142
  in `Client_Compass.Apis` (name `Agilant`) — never reproduce the secret value.
126
143
 
127
144
  ## Change history
145
+ - 2026-09-04 — **Two corrections found while building the sibling
146
+ [assetType stamping](netsuite-item-assettype-sync.md) on the same function.** (1) The
147
+ existing-item **refresh diff never works**: the bulk `GET /items` field list in
148
+ `common_sync_togasupply.php` does **not** request `isFulfillable` (contrary to the 2026-07-28
149
+ entry below), so `$currentFulfillable` is always `null` and the flag is almost certainly PUT on
150
+ every item, every run, for every client. Recorded as a separate ticket — not fixed, because it
151
+ changes write volume for ~20 clients. (2) The existing-item branch's up-to-**three** PUTs to the
152
+ same `/items/<uuid>` were collapsed into a **single** PUT, and `inventoryType` is now written back
153
+ to the in-run lookup (it previously was not, so a repeat part number re-PUT it). (rgirish)
128
154
  - 2026-08-12 — Prod investigation (no code change): recorded that the **existing-item refresh
129
155
  reverts any local override** (NetSuite returns `T` for services; audit-log proof on Compass items
130
156
  2382/2384/2385, stamped NULL→1 on 2026-08-04), so an override must be enforced by a write-time
@@ -6,7 +6,7 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-02
9
+ updated: 2026-09-08
10
10
  owners: [jcardinal, mhammontree, bala]
11
11
  files:
12
12
  - library/app/api/toga2.php
@@ -413,6 +413,14 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
413
413
 
414
414
  ## Change history
415
415
 
416
+ - 2026-09-08 - Added `updateTransferOrderStageToClosed(&$nsOrder, array &$clientConfiguration): bool`
417
+ and the constant `TRANSFER_ORDER_STATUS__CLOSED = 'closed'` (also now used by the `case 'Closed'` in
418
+ the transfer-order status switch). It is a **stage-only** writer: it never touches line items, so it
419
+ cannot reproduce the URI-length / HTTP 414 blowup that the importer's `Closed` skip exists to avoid,
420
+ and it never creates a transfer order we did not import. It exists because the sync loop's
421
+ `status === 'Closed'` `continue` sits before the transfer-order branch, leaving 18 NYCHH transfer
422
+ orders on `Pending`. Written, **not committed or deployed**. Mechanism and the rejected alternative:
423
+ [per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md). (bala)
416
424
  - 2026-09-02 — `syncPurchaseOrderFromNetsuite`'s cleanup DELETE ("remove dropped items") was a **bare
417
425
  throwing** DELETE that froze the whole PURCHASE_ORDERS section whenever a NetSuite-dropped PO line
418
426
  still had a RESTRICT child (item receipt `ItemReceiptItems.purchaseOrderItemId`, or an SO/TO link).
@@ -1,20 +1,20 @@
1
1
  # test (Test) — 1.0 knowledge
2
2
 
3
- | Doc | Summary | Files |
4
- |-----|---------|-------|
5
- | [Test (test) Architecture](architecture.md) | `test` (project **Test**) is a **repository of ad-hoc developer scripts** — not a deployed application. | test/team/ |
6
- | [2.0 Deployment — Per-Client SQL Generator](features/2-0-deployment-client-sql.md) | `team/2.0 deployment/generate_client_sql.php` fans a single SQL change-set out across **all 2.0 client databases**. | test/team/2.0 deployment/generate_client_sql.php, test/team/2.0 deployment/Clients_Db.txt, test/team/2.0 deployment/Client.sql |
7
- | [Active Directory Authentication Test](features/active-directory-auth-test.md) | `team/active_directory_authentication_test.php` is an interactive CLI tool to test **Active Directory authentication**. | test/team/active_directory_authentication_test.php |
8
- | [Compass retrofix2 — Retroactive Data-Fix SQL Generator](features/compass-retrofix2-sql-generator.md) | `@jeff/compass/retrofix2.php` is a standalone **1.0 `App_`** script that retroactively repairs historical Compass USA (`Client_Compass`, 2.0) order data left in | test/@jeff/compass/retrofix2.php |
9
- | [Create Elastic Beanstalk Environment (script)](features/create-elastic-beanstalk.md) | `team/aws/create_elastic_beanstalk.php` is a **standalone** (no `App_` framework) constants-driven PHP generator. | test/team/aws/create_elastic_beanstalk.php |
10
- | [Developer Generators (password, UUID)](features/dev-generators.md) | Two tiny **1.0 `App_` framework** convenience scripts for everyday developer needs. | test/team/generate_password.php, test/team/uuid.php |
11
- | [Forecast vs NetSuite Discrepancy Analysis](features/forecast-netsuite-discrepancy-analysis.md) | `team/forecast-netsuite/discrepancy_analysis.php` detects discrepancies between our **Forecast database** and **NetSuite** (the source of truth for all sales da | test/team/forecast-netsuite/discrepancy_analysis.php |
12
- | [GitHub Audit Script (team/github_audit.php)](features/github-audit-script.md) | `team/github_audit.php` is a **standalone** (no `App_` framework) browser/CLI script that reports lines added/removed, commits, unique authors, and active repos | test/team/github_audit.php |
13
- | [@goagilant.com → @togatech.com Email-Domain Migration (1.0 + 2.0)](features/goagilant-to-togatech-email-migration.md) | Reference + technique for migrating the company email domain `@goagilant.com` → `@togatech.com` across **both** platforms. | migrate_goagilant_to_togatech_2026-06-26.sql, migrate_goagilant_to_togatech_LEGACY_2026-06-26.sql |
14
- | [Static (no-DB) Regression Harness for 1.0 Logic + Source Drift Guard](features/static-no-db-regression-harness.md) | 1.0 has **no PHPUnit**, and most of its business logic sits inside methods that also write SQL, so "just call it" means standing up a client database. | test/@Mark/AIG/test_multi_email.php, test/@Mark/TRUE-80952/test_queued_email_attachments.php, library/app/database.php, library/app/api/toga2.php |
15
- | [TableView Builder (2.0 TableViews SQL generator)](features/tableview-builder.md) | `team/tableViewBuilder/` generates SQL `INSERT` statements for the **2.0 `TableViews`**, `TableViewFields`, and `TableViewJoins` tables from a plain SQL `SELECT | test/team/tableViewBuilder/TableViewGenerator.php, test/team/tableViewBuilder/index.php, test/team/tableViewBuilder/Instructions.md |
16
- | [Talos Knowledge Base Pipeline (Uploader + Processor)](features/talos-kb-pipeline.md) | `team/talos/` holds the two-script web tooling that feeds the **TOGa Talos** (TOGa IQ) AI knowledge bases. | test/team/talos/kb_uploader.php, test/team/talos/kb_processor.php, test/team/talos/kb_processor.ini |
17
- | [TOGa 2.0 Client Onboarding SQL Generator](features/toga2-client-onboarding-sql.md) | > **Superseded by the browser wizard.** The generation logic here was extracted into the reusable > `OnboardingSqlGenerator` class and wrapped in a local browse | test/team/generate_toga2_onboarding_sql.php |
18
- | [TOGa 2.0 Client Onboarding Wizard (local tool)](features/toga2-onboarding-wizard.md) | A **local browser wizard** (`test/team/onboarding/`) that automates 2.0 client onboarding end to end: it (1) gathers developer input and **generates all onboard | test/team/onboarding/index.php, test/team/onboarding/classes/OnboardingSqlGenerator.php, test/team/onboarding/classes/DbchangesConsolidator.php, test/team/onboarding/README.md |
19
- | [TOGa 2.0 User Cross-Client Access SQL Generator](features/toga2-user-cross-client-access-sql.md) | `team/generate_toga2_user_access_sql.php` generates SQL to grant an existing 2.0 user from a **home client** access to a **cross client**. | test/team/generate_toga2_user_access_sql.php |
20
- | [URL & Domain Markdown Document Builder](features/url-domain-markdown-document.md) | `team/build_url_domain_markdown_document.php` generates a **markdown document of our URLs and domains** by pulling environments and domains from the 2.0 platfor | test/team/build_url_domain_markdown_document.php |
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [Test (test) Architecture](architecture.md) | `test` (project **Test**) is a **repository of ad-hoc developer scripts** — not a deployed application. |
6
+ | [2.0 Deployment — Per-Client SQL Generator](features/2-0-deployment-client-sql.md) | `team/2.0 deployment/generate_client_sql.php` fans a single SQL change-set out across **all 2.0 client databases**. |
7
+ | [Active Directory Authentication Test](features/active-directory-auth-test.md) | `team/active_directory_authentication_test.php` is an interactive CLI tool to test **Active Directory authentication**. |
8
+ | [Compass retrofix2 — Retroactive Data-Fix SQL Generator](features/compass-retrofix2-sql-generator.md) | `@jeff/compass/retrofix2.php` is a standalone **1.0 `App_`** script that retroactively repairs historical Compass USA (`Client_Compass`, 2.0) order data left in |
9
+ | [Create Elastic Beanstalk Environment (script)](features/create-elastic-beanstalk.md) | `team/aws/create_elastic_beanstalk.php` is a **standalone** (no `App_` framework) constants-driven PHP generator. |
10
+ | [Developer Generators (password, UUID)](features/dev-generators.md) | Two tiny **1.0 `App_` framework** convenience scripts for everyday developer needs. |
11
+ | [Forecast vs NetSuite Discrepancy Analysis](features/forecast-netsuite-discrepancy-analysis.md) | `team/forecast-netsuite/discrepancy_analysis.php` detects discrepancies between our **Forecast database** and **NetSuite** (the source of truth for all sales da |
12
+ | [GitHub Audit Script (team/github_audit.php)](features/github-audit-script.md) | `team/github_audit.php` is a **standalone** (no `App_` framework) browser/CLI script that reports lines added/removed, commits, unique authors, and active repos |
13
+ | [@goagilant.com → @togatech.com Email-Domain Migration (1.0 + 2.0)](features/goagilant-to-togatech-email-migration.md) | Reference + technique for migrating the company email domain `@goagilant.com` → `@togatech.com` across **both** platforms. |
14
+ | [Static (no-DB) Regression Harness for 1.0 Logic + Source Drift Guard](features/static-no-db-regression-harness.md) | 1.0 has **no PHPUnit**, and most of its business logic sits inside methods that also write SQL, so "just call it" means standing up a client database. |
15
+ | [TableView Builder (2.0 TableViews SQL generator)](features/tableview-builder.md) | `team/tableViewBuilder/` generates SQL `INSERT` statements for the **2.0 `TableViews`**, `TableViewFields`, and `TableViewJoins` tables from a plain SQL `SELECT |
16
+ | [Talos Knowledge Base Pipeline (Uploader + Processor)](features/talos-kb-pipeline.md) | `team/talos/` holds the two-script web tooling that feeds the **TOGa Talos** (TOGa IQ) AI knowledge bases. |
17
+ | [TOGa 2.0 Client Onboarding SQL Generator](features/toga2-client-onboarding-sql.md) | > **Superseded by the browser wizard.** The generation logic here was extracted into the reusable > `OnboardingSqlGenerator` class and wrapped in a local browse |
18
+ | [TOGa 2.0 Client Onboarding Wizard (local tool)](features/toga2-onboarding-wizard.md) | A **local browser wizard** (`test/team/onboarding/`) that automates 2.0 client onboarding end to end: it (1) gathers developer input and **generates all onboard |
19
+ | [TOGa 2.0 User Cross-Client Access SQL Generator](features/toga2-user-cross-client-access-sql.md) | `team/generate_toga2_user_access_sql.php` generates SQL to grant an existing 2.0 user from a **home client** access to a **cross client**. |
20
+ | [URL & Domain Markdown Document Builder](features/url-domain-markdown-document.md) | `team/build_url_domain_markdown_document.php` generates a **markdown document of our URLs and domains** by pulling environments and domains from the 2.0 platfor |
@@ -1,6 +1,6 @@
1
1
  # toga (TOGa) — 1.0 knowledge
2
2
 
3
- | Doc | Summary | Files |
4
- |-----|---------|-------|
5
- | [Bundle Confirmation — Cart Preservation When Adding Add-On Services](features/bundleconfirmation-cart-preservation.md) | Fix: adding any add-on service (Data Transfer, Promotional Bundle, New PC Services, etc.) was silently removing the 1-year tech support SKU from the cart. | toga/app/togarefresh2026/servicerequests/bundleconfirmation.php |
6
- | [ODP Customer Search — Loyalty Number (merchantId) Persistence](features/odp-customer-search-loyalty-persistence.md) | Fix: the ODP loyalty number (`memberId`) stopped persisting to `Customers.merchantId` in the `TOGA_ODP` legacy database — the last non-null value was **2023-11- | toga/app/togarefresh2026/customersearch/view.php, library/app/model/customer.php |
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [Bundle Confirmation — Cart Preservation When Adding Add-On Services](features/bundleconfirmation-cart-preservation.md) | Fix: adding any add-on service (Data Transfer, Promotional Bundle, New PC Services, etc.) was silently removing the 1-year tech support SKU from the cart. |
6
+ | [ODP Customer Search — Loyalty Number (merchantId) Persistence](features/odp-customer-search-loyalty-persistence.md) | Fix: the ODP loyalty number (`memberId`) stopped persisting to `Customers.merchantId` in the `TOGA_ODP` legacy database — the last non-null value was **2023-11- |