toga-ai 1.0.179 → 1.0.181
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/worker/INDEX.md +1 -1
- package/knowledge/1.0/apps/worker/features/forecast2-netsuite-reconciliation.md +28 -1
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -1
- package/knowledge/2.0/apps/worker2/features/netsuite-salesorder-open-orders-sync.md +45 -1
- package/knowledge/2.0/apps/worker2/features/wje-freshservice-sync.md +4 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/standalone/apps/forward/INDEX.md +2 -1
- package/knowledge/standalone/apps/forward/architecture.md +4 -0
- package/knowledge/standalone/apps/forward/features/design-demo-admin.md +148 -0
- package/knowledge/standalone/apps/forward/features/static-demo-hosting.md +13 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
|
|
6
6
|
| [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
|
|
7
7
|
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
|
|
8
|
-
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/reconcile_netsuite_totals.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
8
|
+
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/reconcile_netsuite_totals.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
9
9
|
| [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 |
|
|
10
10
|
| [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 |
|
|
11
11
|
| [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 |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-23
|
|
10
10
|
owners: [dfranks]
|
|
11
11
|
files:
|
|
12
12
|
- test/@dave/reconcile_netsuite_totals.php
|
|
@@ -17,6 +17,7 @@ files:
|
|
|
17
17
|
- test/@dave/probe_sales_gap_direct.php
|
|
18
18
|
- test/@dave/probe_missing_oo_timing.php
|
|
19
19
|
- test/@dave/probe_missing_oo_createdby.php
|
|
20
|
+
- test/@dave/probe_drift_so_dates.php
|
|
20
21
|
- worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
|
|
21
22
|
related:
|
|
22
23
|
- ../architecture.md
|
|
@@ -67,6 +68,13 @@ by reconciling a chosen tranDate range directly against NetSuite.
|
|
|
67
68
|
- **Chunking + keepalive.** trueup_sales chunks by `--chunk-days` (default 7) and commits per
|
|
68
69
|
chunk so a crash resumes. Both ping/reconnect `db_forecast2` before DB work (Aurora drops idle
|
|
69
70
|
links during long NS calls).
|
|
71
|
+
- **`probe_drift_so_dates.php` (read-only drift classifier).** For a list of NS SO internalIds it
|
|
72
|
+
reports, per order: NS `trandate` / `lastmodifieddate` (ET) / status / total; NS current open
|
|
73
|
+
product-line count + open $; and the matching `Forecast.OpenOrderItems` row count + $ (direct mysqli
|
|
74
|
+
to the core2 `Forecast` DB, column `netsuiteSalesOrderInternalId`). Bootstraps the 2.0
|
|
75
|
+
`_Component_Api_Netsuite` adapter via the worker2 chdir+require `index.php` pattern. Use it to
|
|
76
|
+
**classify a drift set as whole-missing vs. present-but-different**, and to scope a
|
|
77
|
+
`Logs.Webhook`/`WorkerJobs` search window from the lastmodified timestamps.
|
|
70
78
|
|
|
71
79
|
## Data model
|
|
72
80
|
|
|
@@ -139,6 +147,18 @@ None — Forecast2 is a single shared dataset.
|
|
|
139
147
|
`probe_missing_oo_createdby.php` (creator/context — integration/Web-Services-origin orders cluster
|
|
140
148
|
here) + a `Core.WorkerJobs` lookup by `JSON_EXTRACT(parameters,'$.internalId')` (no row → bucket A;
|
|
141
149
|
rows all `isSuccess=1` yet zero OOI rows → bucket B).
|
|
150
|
+
- **Open-orders drift is THREE distinct categories — diagnose per category, don't assume.**
|
|
151
|
+
Drift between NS open SOs and `Forecast.OpenOrderItems` maps to `trueup_open_orders.php`'s output
|
|
152
|
+
buckets:
|
|
153
|
+
- **(a) WHOLE-ORDER-MISSING** — NS has open lines, FC has **0 rows** → the **"Insert"** bucket.
|
|
154
|
+
**Dominant by dollars** (a single stale partially-fulfilled order can be **>$1M**).
|
|
155
|
+
- **(b) LINE-LEVEL value drift** — order present in FC but a line's revenue/qty changed → the
|
|
156
|
+
**"Update"** bucket. **Largest by count** (~1,612 in a mid-2026 run).
|
|
157
|
+
- **(c) STALE FC ROWS** — FC holds lines NS no longer returns as open → the **"Delete"/stale-cleanup**
|
|
158
|
+
bucket. **Smallest** (~13–19).
|
|
159
|
+
The common "drift = dropped line items still in production" assumption is category **(c) — the
|
|
160
|
+
SMALLEST**; the dominant/highest-$ problem is **whole-order-missing (a)**. Classify the set first
|
|
161
|
+
(`probe_drift_so_dates.php`) and diagnose per category.
|
|
142
162
|
- **Legacy credit-memo wrong-sign rows.** Some `Forecast.Sales` credit-memo/cash-refund rows were
|
|
143
163
|
stored with **flipped signs** by a prior sync version (positive instead of negative), inflating
|
|
144
164
|
revenue. `trueup_sales.php` repairs them: it compares against NS by trandate, detects the sign
|
|
@@ -149,6 +169,13 @@ None — Forecast2 is a single shared dataset.
|
|
|
149
169
|
|
|
150
170
|
## Change history
|
|
151
171
|
|
|
172
|
+
- 2026-06-23 — **Framed open-orders drift as three distinct categories** mapping to
|
|
173
|
+
`trueup_open_orders`'s Insert/Update/Delete buckets — whole-order-missing (dominant by $),
|
|
174
|
+
line-level value drift (largest by count), and stale FC rows (smallest) — correcting the common
|
|
175
|
+
"dropped line items" assumption (which is the smallest category). Added the read-only drift
|
|
176
|
+
classifier `probe_drift_so_dates.php` (NS dates/status/open-$ vs `Forecast.OpenOrderItems` count/$
|
|
177
|
+
via direct core2 mysqli; bootstraps the 2.0 `_Component_Api_Netsuite` adapter) to classify a drift
|
|
178
|
+
set and scope a Logs.Webhook/WorkerJobs search window. (dfranks)
|
|
152
179
|
- 2026-06-22 — **`reconcile` Open Orders made apples-to-apples.** Headline row now compares
|
|
153
180
|
product-only on both sides (shipping is NS-only, prints as a separate `(NS-only)` info row) — kills
|
|
154
181
|
a phantom shipping-only "delta". Documented the per-order presence diff as the real sync-health
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
|
|
11
11
|
| [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
|
|
12
12
|
| [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
13
|
-
| [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
13
|
+
| [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
14
14
|
| [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
|
|
15
15
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
|
|
16
16
|
| [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-23
|
|
10
10
|
owners: ["dfranks"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Netsuite/SalesOrder.php
|
|
@@ -18,6 +18,7 @@ files:
|
|
|
18
18
|
- test/@dave/probe_so_rest_lines.php
|
|
19
19
|
- test/@dave/probe_missing_oo_timing.php
|
|
20
20
|
- test/@dave/probe_missing_oo_createdby.php
|
|
21
|
+
- test/@dave/probe_drift_so_dates.php
|
|
21
22
|
- worker/crons/toga2/forecast2/import_open_orders.php
|
|
22
23
|
- worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
|
|
23
24
|
related:
|
|
@@ -77,12 +78,35 @@ Unique key `(netsuiteSalesOrderInternalId, lineNumber)`. Columns written: `netsu
|
|
|
77
78
|
`classificationId`, `itemId` (nullable), `lineNumber`, `revenue`, `profit`. The `accountId` FK column
|
|
78
79
|
exists but is **never populated** (the legacy writer never set it — parity).
|
|
79
80
|
|
|
81
|
+
The table lives in the **`Forecast` schema on the core2 cluster** (reader
|
|
82
|
+
`reader1.core.database.togahub.com` / writer `writer.core.database.togahub.com`), ~4,347 rows. The
|
|
83
|
+
sales-order FK column is **`netsuiteSalesOrderInternalId`** (`int unsigned`) — note this is *not* the
|
|
84
|
+
same column name used by `Forecast.Sales` (see the column-name gotcha below).
|
|
85
|
+
|
|
80
86
|
## Client variations
|
|
81
87
|
|
|
82
88
|
None — uniform (platform-wide Forecast2 sync).
|
|
83
89
|
|
|
84
90
|
## Gotchas / known issues
|
|
85
91
|
|
|
92
|
+
- **Wrong FK column name → MySQL 1054 that masquerades as "order missing".** `OpenOrderItems`'s
|
|
93
|
+
sales-order key is **`netsuiteSalesOrderInternalId`**. Do **NOT** query it with
|
|
94
|
+
`netsuiteTransactionInternalId` — that is the **`Forecast.Sales`** column. On `OpenOrderItems` it
|
|
95
|
+
raises **MySQL ERROR 1054 "Unknown column"**, which (if the query error is swallowed) degrades into a
|
|
96
|
+
silent **false-negative "order missing"** reading. Verify the schema before concluding an order is
|
|
97
|
+
absent from Forecast.
|
|
98
|
+
- **A successful `WorkerJobs` row does NOT prove rows were written — two silent zero-write paths.** An
|
|
99
|
+
`isSuccess=1` / `failureReason = NULL` job is **not** evidence that any `OpenOrderItems` rows persisted.
|
|
100
|
+
`importOpenOrder` has two success-with-zero-write paths:
|
|
101
|
+
- **(a) STATUS GATE** — if NetSuite `status->refName` is not in `OPEN_STATUSES` (`Pending Fulfillment`,
|
|
102
|
+
`Partially Fulfilled`, `Pending Billing/Partially Fulfilled`, `Pending Billing`) or is missing, it
|
|
103
|
+
calls `removeAll()` (deletes all FC rows) and returns a success string.
|
|
104
|
+
- **(b) ALL-LINES-FILTERED** — every line is skipped (item group / excluded EWR item id /
|
|
105
|
+
`revenue == 0 && profit == 0`), leaving `lineRows` empty, so `syncOpenLines` takes its delete-all
|
|
106
|
+
branch and returns `"synced (0 open lines)"`.
|
|
107
|
+
Only an **unknown item** throws (`isSuccess=0`). The **only** thing that detects silent zero-rows is a
|
|
108
|
+
**post-write verification** (count persisted vs. intended); a handler lacking that verification cannot
|
|
109
|
+
self-detect this state.
|
|
86
110
|
- **Async-recalc race → a transiently-empty REST read PURGES a genuinely-open order (silent data loss).**
|
|
87
111
|
The open-status gate is not the only way rows get deleted: on an **open** order, if the REST GET
|
|
88
112
|
momentarily returns **zero importable open lines**, `syncOpenLines` runs its reconcile-delete with an
|
|
@@ -142,6 +166,17 @@ None — uniform (platform-wide Forecast2 sync).
|
|
|
142
166
|
up after `MAX_IMPORT_ATTEMPTS`). If every `WorkerJobs` row for the id has a **NULL `attempt`** and
|
|
143
167
|
there are **no `OPEN_ORDER_IMPORT` breadcrumbs** for it, the self-heal/defer path isn't executing
|
|
144
168
|
for that order — so a bucket-B miss has no retry and freezes at zero rows.
|
|
169
|
+
**Full 3-table diagnostic ladder for a single SO (all read-only):**
|
|
170
|
+
1. **`Logs.Webhook`** (logs cluster `reader1.logs.database.togahub.com`, schema `Logs`) — confirms
|
|
171
|
+
NetSuite's AMQ push actually **delivered** the `salesOrder` event to `webhook.togahub.com/netsuite`.
|
|
172
|
+
Filter `route LIKE '%netsuite%'` and REGEXP the internalId in `requestPayload`.
|
|
173
|
+
2. **`Core.WorkerJobs`** (core2, schema `Core`) — the **processing outcome**.
|
|
174
|
+
`action LIKE 'Netsuite/SalesOrder/%'`, `JSON_EXTRACT(parameters,'$.internalId')`.
|
|
175
|
+
3. **`Logs.Api` with `source = 'OPEN_ORDER_IMPORT'`** — the **import breadcrumb**. `responseCode`:
|
|
176
|
+
**200** persisted (== intended) / **409** silent zero-rows / **202** deferred / **204** removeAll /
|
|
177
|
+
**500** gave up.
|
|
178
|
+
**SIGNAL:** zero `OPEN_ORDER_IMPORT` rows in `Logs.Api` despite a successful `WorkerJobs` row ⇒ the
|
|
179
|
+
running handler **predates the breadcrumb instrumentation**.
|
|
145
180
|
- **REST shape ≠ SOAP shape.** The cron reads the SOAP-shim shape; this handler reads the REST record
|
|
146
181
|
(`status->refName`, line `quantityBilled`, `class->refName`, `entity->id`, `salesRep->id`,
|
|
147
182
|
`shippingCost`). Verified against live orders via `test/@dave/probe_salesorder_rest_shape.php`.
|
|
@@ -196,6 +231,15 @@ and are a candidate to extract into a shared `_Component_Forecast_Db` before the
|
|
|
196
231
|
|
|
197
232
|
## Change history
|
|
198
233
|
|
|
234
|
+
- 2026-06-23 — **Documented the OpenOrderItems schema/FK column, two silent zero-write paths, and the
|
|
235
|
+
full 3-table diagnostic ladder.** Recorded that the table lives in `Forecast` on core2 (~4,347 rows)
|
|
236
|
+
with FK `netsuiteSalesOrderInternalId`, and the **1054 false-negative** trap from querying it with the
|
|
237
|
+
`Forecast.Sales` column `netsuiteTransactionInternalId`. Added that an `isSuccess=1` job proves nothing
|
|
238
|
+
about rows written — STATUS GATE and ALL-LINES-FILTERED both succeed with zero writes, and only a
|
|
239
|
+
post-write count detects it. Extended the WorkerJobs/breadcrumb note into the full read-only ladder
|
|
240
|
+
(`Logs.Webhook` delivery → `Core.WorkerJobs` outcome → `Logs.Api` `OPEN_ORDER_IMPORT` breadcrumb), with
|
|
241
|
+
the signal that zero breadcrumbs despite a successful job ⇒ handler predates the instrumentation. Added
|
|
242
|
+
`probe_drift_so_dates.php`. (dfranks)
|
|
199
243
|
- 2026-06-22 — **Separated "missing open order" into two distinct buckets** with a discriminating
|
|
200
244
|
diagnostic (`Core.WorkerJobs` lookup by `parameters.internalId`): **(A) webhook never enqueued** (no
|
|
201
245
|
`WorkerJobs` row — NetSuite-side enqueuer not firing for integration/Web-Services/backend-mass-edit
|
|
@@ -141,3 +141,7 @@ parallel but separate `_Worker_Elite` handler — see related doc.
|
|
|
141
141
|
- `2.0/apps/worker2/features/elite-freshservice-sync.md` — sister Freshservice client.
|
|
142
142
|
- `2.0/apps/worker2/features/startech-webhook-handler.md` — the multi-client webhook pattern WJE predates.
|
|
143
143
|
- `1.0/apps/library/features/toga2-api-client-and-bridge.md` — `App_Api_Toga2::syncWithTogadesk`.
|
|
144
|
+
|
|
145
|
+
## Change history
|
|
146
|
+
|
|
147
|
+
- 2026-06-23 (jcardinal) — Added missing Change history section for schema compliance (no content change).
|
package/knowledge/INDEX.md
CHANGED
|
@@ -33,7 +33,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
33
33
|
## standalone framework
|
|
34
34
|
|
|
35
35
|
- **togatech** (TOGA Technology Website) — 2 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
|
|
36
|
-
- **forward** (Forwarder) —
|
|
36
|
+
- **forward** (Forwarder) — 4 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
|
|
37
37
|
|
|
38
38
|
## Clients
|
|
39
39
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
-
| [Forwarder Architecture](architecture.md) | Forwarder is a tiny **standalone PHP application** that powers TOGA's short, branded redirect domains. | forward/forward.ini, forward/index.php, forward/.htaccess, forward/.platform/httpd/conf.d/rewritemap.conf, forward/.ebextensions/rewritemap.config, forward/composer.json |
|
|
5
|
+
| [Forwarder Architecture](architecture.md) | Forwarder is a tiny **standalone PHP application** that powers TOGA's short, branded redirect domains. | forward/forward.ini, forward/index.php, forward/.htaccess, forward/.platform/httpd/conf.d/rewritemap.conf, forward/.ebextensions/rewritemap.config, forward/design/index.php, forward/composer.json |
|
|
6
|
+
| [Design Demo Admin Tool](features/design-demo-admin.md) | A standalone, dependency-free PHP tool served at `https://demo.togatech.com/design` that lets the **design team** publish self-contained "Claude Design" HTML ex | forward/design/index.php, forward/design/.config.example.php, forward/.gitignore, forward/.htaccess, forward/.ebextensions/php-uploads.config |
|
|
6
7
|
| [Encrypted-Link Handler](features/encrypted-link-handler.md) | A `index.php` feature in [Forwarder](../architecture.md) for redirect domains whose URL path carries an **encrypted token** that must be decoded before redirect | forward/index.php |
|
|
7
8
|
| [Static Demo Hosting](features/static-demo-hosting.md) | A lightweight way to host self-contained static HTML pages (demos, exported designs, download landing pages) on the Forwarder app under a clean URL — e.g. | forward/.htaccess, forward/togadesk/index.html |
|
|
@@ -14,6 +14,7 @@ files:
|
|
|
14
14
|
- forward/.htaccess
|
|
15
15
|
- forward/.platform/httpd/conf.d/rewritemap.conf
|
|
16
16
|
- forward/.ebextensions/rewritemap.config
|
|
17
|
+
- forward/design/index.php
|
|
17
18
|
- forward/composer.json
|
|
18
19
|
related: []
|
|
19
20
|
---
|
|
@@ -75,6 +76,7 @@ The `.htaccess` at the repo root drives a plain-text lookup table:
|
|
|
75
76
|
(e.g. `sos.*` → `/sos/`), it redirects to `/<subdomain>/` and serves that dir's
|
|
76
77
|
`index.html`. Used for the bundled `sos/` Splashtop SOS download page.
|
|
77
78
|
- **404** — otherwise returns a styled `404` echoing the unmatched `host + uri`.
|
|
79
|
+
- **Design Demo Admin tool** — `/design` serves `design/index.php`, a self-serve UI for the design team to publish and version Claude Design HTML exports via the GitHub API. It runs as a normal directory request (not through the redirect map). See the [Design Demo Admin](features/design-demo-admin.md) feature doc.
|
|
78
80
|
|
|
79
81
|
## Deployment (Elastic Beanstalk / Apache)
|
|
80
82
|
|
|
@@ -90,6 +92,7 @@ The `.htaccess` at the repo root drives a plain-text lookup table:
|
|
|
90
92
|
- `composer.json` declares a `Togatech\Forward\` PSR-4 autoload over `src/` but has **no
|
|
91
93
|
dependencies** and no `src/` is shipped today — the app is effectively a single
|
|
92
94
|
`index.php` plus the Apache config.
|
|
95
|
+
- **`DirectoryIndex index.html index.php`** in `.htaccess` makes `/design/` load `design/index.php` and lets nested static-demo version paths `/<project>/<vN>/` serve via Apache's normal directory index. The single-segment static-demo rewrite rule does not match two-segment paths, and the `index.php` map-miss fallback does not fire for real directories — so versioned demo URLs resolve as directory-index lookups.
|
|
93
96
|
|
|
94
97
|
## The `decrypt()` scheme
|
|
95
98
|
|
|
@@ -119,3 +122,4 @@ Treat this as obfuscation only. Anything genuinely sensitive must not rely on it
|
|
|
119
122
|
|
|
120
123
|
- 2026-06-18 (jcardinal) — Initial architecture documentation; registered `forward`
|
|
121
124
|
(Forwarder, standalone) in the knowledge base.
|
|
125
|
+
- 2026-06-23 (jcardinal) — Added the `/design` Design Demo Admin route and a `DirectoryIndex index.html index.php` directive in `.htaccess` (enables `/design/` and nested `/<project>/<vN>/` demo paths).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Design Demo Admin Tool
|
|
3
|
+
framework: "standalone"
|
|
4
|
+
repo: forward
|
|
5
|
+
project: Forwarder
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-23
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- forward/design/index.php
|
|
13
|
+
- forward/design/.config.example.php
|
|
14
|
+
- forward/.gitignore
|
|
15
|
+
- forward/.htaccess
|
|
16
|
+
- forward/.ebextensions/php-uploads.config
|
|
17
|
+
related:
|
|
18
|
+
- standalone/apps/forward/features/static-demo-hosting.md
|
|
19
|
+
- standalone/apps/forward/architecture.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
A standalone, dependency-free PHP tool served at `https://demo.togatech.com/design` that
|
|
25
|
+
lets the **design team** publish self-contained "Claude Design" HTML exports to the
|
|
26
|
+
`forward` repo as **versioned demos** — no developer, no git, no local checkout required.
|
|
27
|
+
It sits on top of [Static Demo Hosting](static-demo-hosting.md): static hosting serves the
|
|
28
|
+
files; this tool is the self-serve UI that writes and versions them.
|
|
29
|
+
|
|
30
|
+
The tool does **all** repo access over the **GitHub REST API** — never the local
|
|
31
|
+
filesystem — because in production it runs on the Elastic Beanstalk host, not a git
|
|
32
|
+
checkout. Publishing pushes a commit to `_main`, which triggers the existing EB
|
|
33
|
+
auto-deploy ("live in a few minutes").
|
|
34
|
+
|
|
35
|
+
## Key files / entry points
|
|
36
|
+
|
|
37
|
+
- `forward/design/index.php` — the entire tool: a single-file PHP backend + HTML/JS UI.
|
|
38
|
+
`declare(strict_types=1)`. Served at `/design` (was `/admin`; renamed this session).
|
|
39
|
+
- `forward/design/.config.example.php` — template for local dev config (the real
|
|
40
|
+
`.config.php` is git-ignored).
|
|
41
|
+
- `forward/.gitignore` — ignores `design/.config.php` so a local token is never tracked.
|
|
42
|
+
- `forward/.htaccess` — `DirectoryIndex index.html index.php` so `/design/` loads
|
|
43
|
+
`index.php`, and so nested `/<project>/<vN>/` paths serve via Apache's directory index.
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
### GitHub API access (no filesystem)
|
|
48
|
+
|
|
49
|
+
- **Reads / listing:** GitHub **Contents API**.
|
|
50
|
+
- **Writes:** GitHub **Git Data API** — blob → tree → commit → update-ref — so a publish
|
|
51
|
+
is an **atomic multi-file commit** (the version's files + the regenerated stub +
|
|
52
|
+
`project.json` all land in one commit).
|
|
53
|
+
- **Auth:** token from `getenv('FORWARD_GITHUB_TOKEN')` (an Elastic Beanstalk environment
|
|
54
|
+
property in prod) or an uncommitted `design/.config.php` for local dev. See gotchas — the
|
|
55
|
+
token must **never** live in the tracked `index.php`.
|
|
56
|
+
|
|
57
|
+
### Versioning model (decided this session)
|
|
58
|
+
|
|
59
|
+
- **Sequential `/v1`, `/v2`, `/v3` URL folders** — not semver, not date-based.
|
|
60
|
+
- Each project has a **`project.json` manifest** at its folder root:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"title": "...", "latest": "v3", "hidden": false,
|
|
64
|
+
"versions": [
|
|
65
|
+
{ "id": "v3", "label": "...", "notes": "...", "author": "...",
|
|
66
|
+
"date": "YYYY-MM-DD", "hidden": false }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
Human detail (label / author / notes) lives in the **manifest**, not the URL.
|
|
71
|
+
- **"latest" pointer:** the bare `/<project>` serves an auto-generated `index.html` **stub**
|
|
72
|
+
that meta-refresh / JS-redirects (relative `./vN/`) to the newest **visible** version.
|
|
73
|
+
The stub is regenerated on every publish/unlist.
|
|
74
|
+
- A directory is treated as a **managed demo iff it contains `project.json`** — this is how
|
|
75
|
+
the tool distinguishes demos from infrastructure directories.
|
|
76
|
+
|
|
77
|
+
### Actions
|
|
78
|
+
|
|
79
|
+
- **Publish a version** — uploads a self-contained HTML export as the next `vN/index.html`,
|
|
80
|
+
appends a version entry to `project.json`, regenerates the stub, commits.
|
|
81
|
+
- **Adopt** — converts a legacy `index.html`-only demo folder (e.g. `togadesk`) into a
|
|
82
|
+
managed demo: moves the existing page to `v1/index.html`, writes `project.json`,
|
|
83
|
+
regenerates the stub.
|
|
84
|
+
- **Remove = unlist only** — sets a `hidden` flag in `project.json`; files are **never
|
|
85
|
+
deleted** (explicit owner decision).
|
|
86
|
+
- **Copy Claude Design prompt** — one-click copy of the standard self-contained-HTML export
|
|
87
|
+
prompt (the same prompt documented in [static-demo-hosting](static-demo-hosting.md)).
|
|
88
|
+
|
|
89
|
+
### Naming & validation
|
|
90
|
+
|
|
91
|
+
- Folder names auto-normalized to **lowercase kebab-case**, validated against a kebab regex
|
|
92
|
+
plus a reserved/excluded list.
|
|
93
|
+
- **`EXCLUDED_FOLDERS`** constant (`design`, `sos`) + `RESERVED_NAMES` are never listed or
|
|
94
|
+
managed (owner-requested; expected to grow over time).
|
|
95
|
+
|
|
96
|
+
### Hardening
|
|
97
|
+
|
|
98
|
+
Input length caps; version-id regex guard; HTML content sniff on upload; security headers;
|
|
99
|
+
no client-facing leakage of GitHub/curl error detail; `declare(strict_types=1)`.
|
|
100
|
+
|
|
101
|
+
## Access control
|
|
102
|
+
|
|
103
|
+
**No authentication** — explicit owner product decision. The `/design` URL is unlisted;
|
|
104
|
+
anyone who reaches it can publish or unlist. Do not treat the tool as protected.
|
|
105
|
+
|
|
106
|
+
## Data model
|
|
107
|
+
|
|
108
|
+
`project.json` per managed demo (see Versioning model above). No database.
|
|
109
|
+
|
|
110
|
+
## Client variations
|
|
111
|
+
|
|
112
|
+
None — internal/shared design-team tool.
|
|
113
|
+
|
|
114
|
+
## Gotchas / known issues
|
|
115
|
+
|
|
116
|
+
- **Contents API inlines only ≤1 MB.** The GitHub Contents API returns an **empty**
|
|
117
|
+
`content` field for files over ~1 MB. Design exports are routinely multi-MB (the
|
|
118
|
+
`togadesk` demo is ~4.2 MB), so reading them via the base64 Contents response silently
|
|
119
|
+
returned empty content — adopt committed a **0-byte `v1/index.html`** (blank page). Fix:
|
|
120
|
+
`ghReadFile()` uses the **raw media type** (`Accept: application/vnd.github.raw`), which
|
|
121
|
+
streams files up to 100 MB.
|
|
122
|
+
- **Never hardcode the GitHub token in the tracked `index.php`.** `design/index.php` is
|
|
123
|
+
git-**tracked**; only `design/.config.php` is git-ignored. Hardcoding a `ghp_` token in
|
|
124
|
+
`index.php` leaked a live token into pushed `_main` history of `agilantsolutions/forward`.
|
|
125
|
+
Use `getenv('FORWARD_GITHUB_TOKEN')` (EB env property) or the uncommitted `.config.php`. A
|
|
126
|
+
leaked token must be **revoked/rotated** — a later commit does not remove it from history.
|
|
127
|
+
Prefer a **fine-grained PAT** scoped to only the `forward` repo's Contents R/W.
|
|
128
|
+
- **Raise PHP upload limits for multi-MB designs (AL1 / PHP 7.3 specifics).** Default
|
|
129
|
+
`upload_max_filesize` (~2 MB) rejects real multi-MB design exports. The deploy platform
|
|
130
|
+
is **PHP 7.3 on 64-bit Amazon Linux 1**, where these values are **not** settable via the
|
|
131
|
+
`aws:elasticbeanstalk:container:php:phpini` option namespace. Instead a
|
|
132
|
+
`.ebextensions/php-uploads.config` drops a custom ini at
|
|
133
|
+
`/etc/php-7.3.d/99-design-uploads.ini` setting `upload_max_filesize=25M`,
|
|
134
|
+
`post_max_size=30M`, `memory_limit=256M`, `max_execution_time=60`.
|
|
135
|
+
- **Publishing pushes to `_main`** and triggers EB auto-deploy — changes go live in a few
|
|
136
|
+
minutes; there is no staging step.
|
|
137
|
+
|
|
138
|
+
## Change history
|
|
139
|
+
|
|
140
|
+
- 2026-06-23 — Built the Design Demo Admin tool: GitHub-API-only publishing, sequential
|
|
141
|
+
`/vN` versioning with a `project.json` manifest + auto-generated latest-pointer stub,
|
|
142
|
+
adopt/unlist actions, `/design` route. Fixed the >1 MB Contents-API empty-content bug
|
|
143
|
+
(raw media type) and the tracked-token leak (env/`.config.php`). (jcardinal)
|
|
144
|
+
|
|
145
|
+
## Related docs
|
|
146
|
+
|
|
147
|
+
- [Static Demo Hosting](static-demo-hosting.md)
|
|
148
|
+
- [Forwarder Architecture](../architecture.md)
|
|
@@ -6,13 +6,14 @@ project: Forwarder
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-23
|
|
10
10
|
owners: ["jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- forward/.htaccess
|
|
13
13
|
- forward/togadesk/index.html
|
|
14
14
|
related:
|
|
15
15
|
- standalone/apps/forward/architecture.md
|
|
16
|
+
- standalone/apps/forward/features/design-demo-admin.md
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
## Summary
|
|
@@ -55,6 +56,14 @@ RewriteRule ^([^/]+)/?$ /$1/index.html [L]
|
|
|
55
56
|
deploy the EB app → `demo.togatech.com/<name>` works immediately. No code or `.htaccess`
|
|
56
57
|
change is needed for each new demo.
|
|
57
58
|
|
|
59
|
+
**Self-serve + versioning.** The design team no longer has to commit by hand: the
|
|
60
|
+
[Design Demo Admin tool](design-demo-admin.md) at `/design` publishes exports via the
|
|
61
|
+
GitHub API and adds **sequential `/vN/` versioning** on top of this static layout (a
|
|
62
|
+
`project.json` manifest plus an auto-generated latest-pointer stub at the bare
|
|
63
|
+
`/<project>`). The nested `/<project>/<vN>/` URLs are served by Apache's normal directory
|
|
64
|
+
index (a `DirectoryIndex index.html index.php` line in `.htaccess`); the single-segment
|
|
65
|
+
static-demo rewrite rule above does not match two segments.
|
|
66
|
+
|
|
58
67
|
This is distinct from the older subdomain-based **local directory handler** in `index.php`
|
|
59
68
|
(`sos.*` → `/sos/`), which runs only on a map miss and issues a redirect. The static-demo
|
|
60
69
|
rule is path-based, redirect-free, and runs in Apache before PHP. See the
|
|
@@ -106,6 +115,9 @@ None — uniform, shared/internal.
|
|
|
106
115
|
|
|
107
116
|
## Change history
|
|
108
117
|
|
|
118
|
+
- 2026-06-23 — Added self-serve publishing + sequential `/vN/` versioning on top of static
|
|
119
|
+
hosting via the new [Design Demo Admin tool](design-demo-admin.md); nested version paths
|
|
120
|
+
served by a `DirectoryIndex index.html index.php` line in `.htaccess` (jcardinal).
|
|
109
121
|
- 2026-06-18 — Added static demo hosting: `.htaccess` rule + `togadesk/` demo; documented
|
|
110
122
|
the Claude Design export prompt (jcardinal).
|
|
111
123
|
|
package/package.json
CHANGED