toga-ai 1.0.836 → 1.0.837
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/tools/INDEX.md +1 -1
- package/knowledge/1.0/apps/tools/features/talos-pricing-ui.md +34 -77
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +65 -0
- package/knowledge/2.0/apps/talos/INDEX.md +2 -2
- package/knowledge/2.0/apps/talos/architecture.md +50 -129
- package/knowledge/2.0/apps/talos/features/pricing-cogs-model.md +102 -143
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -1
- package/knowledge/2.0/apps/worker2/features/talos-pricing-automation.md +58 -106
- package/knowledge/clients/compass-usa/profile.md +7 -0
- package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +12 -14
- package/knowledge/clients/prudential/profile.md +12 -1
- package/package.json +1 -1
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
| [Tools Persona-Gated Navigation (App_Nav)](features/persona-gated-navigation.md) | `App_Nav` is the Tools app's two-level, **persona-gated** navigation. |
|
|
18
18
|
| [Tools SAML SSO Consumer & Persona-Gated Auth (App_Auth)](features/saml-sso-auth.md) | `App_Auth` is the Tools app's authentication layer: it consumes the SAML gateway `?saml=` handoff (see the 2.0 SAML downstream integration contract), establishe |
|
|
19
19
|
| [Talos Knowledge Base Admin UI (KB Documents + Vocabulary)](features/talos-kb-documents-admin.md) | > **PER-AI-MODEL, DATA-DRIVEN SCOPING (2026-07-29).** The KB-documents and Vocabulary admin > UIs were refactored from a single hard-coded **"development-team"* |
|
|
20
|
-
| [Talos Pricing UI (Contracts, Pricing Dashboard, Usage, Settings + Estimator)](features/talos-pricing-ui.md) | The 1.0 (tools
|
|
20
|
+
| [Talos Pricing UI (Contracts, Pricing Dashboard, Usage, Settings + Estimator)](features/talos-pricing-ui.md) | The 1.0 (tools) face of the Talos Pricing Platform — Contracts / Pricing Dashboard / Usage / Pricing Settings pages + the estimate engine, reading the Team DB v |
|
|
21
21
|
| [App-Wide Colour Theme (light / dark / auto)](features/theme-light-dark.md) | A **light / dark / auto** colour theme for the *entire* Tools app, built as a single semantic-token layer (`assets/css/theme.css`) rather than per-page edits. |
|
|
22
22
|
| [Deploying Tools to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)](workflows/deploy-to-elastic-beanstalk-al2023.md) | How the **Tools** 1.0 app boots on Elastic Beanstalk running `PHP 8.5 on 64bit Amazon Linux 2023/4.13.1 (aarch64)`. |
|
|
@@ -6,11 +6,12 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-09-17
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- tools/_/app/nav.php
|
|
13
13
|
- tools/_/app/talos/estimator.php
|
|
14
|
+
- tools/_/app/talos/estimator.parity.test.php
|
|
14
15
|
- tools/_/app/talos/usage.php
|
|
15
16
|
- tools/mvc/talos/contracts/get.php
|
|
16
17
|
- tools/mvc/talos/contracts/post.php
|
|
@@ -29,100 +30,56 @@ related:
|
|
|
29
30
|
- ../../../2.0/apps/worker2/features/talos-pricing-automation.md
|
|
30
31
|
---
|
|
31
32
|
|
|
33
|
+
The 1.0 (tools) face of the Talos Pricing Platform — Contracts / Pricing Dashboard / Usage / Pricing Settings pages + the estimate engine, reading the Team DB via `db_team`; open for the contract lifecycle, worker-owned read-only rows, or the fetchOne/PHP 8.5 traps.
|
|
34
|
+
|
|
32
35
|
## Summary
|
|
33
36
|
|
|
34
|
-
The 1.0 (tools app) face of the **Talos Pricing Platform** — a "Talos Pricing" nav folder
|
|
35
|
-
with four pages plus the estimate engine. Restructured 2026-08-03 to
|
|
36
|
-
**Contracts / Pricing Dashboard / Usage / Pricing Settings**. All pages read the **Team DB**
|
|
37
|
-
through the `db_team` connection (`[database_team]` in `config.*.ini`).
|
|
37
|
+
The 1.0 (tools app) face of the **Talos Pricing Platform** — a "Talos Pricing" nav folder with four pages plus the estimate engine. Restructured 2026-08-03 to **Contracts / Pricing Dashboard / Usage / Pricing Settings**. All pages read the **Team DB** through the `db_team` connection (`[database_team]` in `config.*.ini`).
|
|
38
38
|
|
|
39
39
|
## How it works
|
|
40
40
|
|
|
41
41
|
### Contracts (`mvc/talos/contracts/{get,post}.php`) — replaces `onboarding/*`
|
|
42
|
-
The old Onboarding page could only **INSERT** (hardcoded `status='ONBOARDED'`, no edit, no
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **
|
|
47
|
-
`ON DELETE CASCADE`, so deleting the client would destroy the agreed rate schedule and the
|
|
48
|
-
margin history with it. Signed contracts **churn**; they do not delete.
|
|
49
|
-
- **Post-signature edits append to `TalosContractAmendments`** rather than mutating the
|
|
50
|
-
signed terms.
|
|
51
|
-
- Includes an **Actual-vs-Estimate** comparison with variance chips, a month-by-month history
|
|
52
|
-
table plus cost/margin chart, and a one-click **"apply recommended org fee"**.
|
|
42
|
+
The old Onboarding page could only **INSERT** (hardcoded `status='ONBOARDED'`, no edit, no delete), which is why nothing ever reached the tables. Contracts is a real lifecycle, reusing the existing status enum: **PROSPECT** = draft, **ACTIVE** = signed, **CHURNED** = ended.
|
|
43
|
+
- **A signed contract can never be hard-deleted.** `TalosPricingBands` is `ON DELETE CASCADE`, so deleting the client would destroy the agreed rate schedule and the margin history with it. Signed contracts **churn**; they do not delete.
|
|
44
|
+
- **Post-signature edits append to `TalosContractAmendments`** rather than mutating the signed terms.
|
|
45
|
+
- Includes an **Actual-vs-Estimate** comparison with variance chips, a month-by-month history table plus cost/margin chart, and a one-click **"apply recommended org fee"**.
|
|
46
|
+
- **Pricing model toggle + contract terms (2026-09-17).** The page now sets `pricingModel` (`PER_USER` / `PER_INTERACTION`), the fixed 1/3/5-year term dropdown (`contractTermMonths`), the per-interaction inputs (`contractedInteractionCount`, voice-only `avgMinutesPerInteraction`, `perInteractionFee`), and the two one-time contract fees (`implementationFee`, `trainingFee`). Model design, term ramp, fee amortization, and the recurring-vs-blended margin split are all in [pricing-cogs-model](../../../2.0/apps/talos/features/pricing-cogs-model.md) — not restated here.
|
|
53
47
|
|
|
54
48
|
### Pricing Dashboard (`pricing/get.php`) — rebuilt for sales leadership
|
|
55
|
-
Portfolio KPIs, a 12-month blended-margin trend with the target band shaded, an exception
|
|
56
|
-
list with deterministic explanations, and licensed-vs-active utilisation.
|
|
49
|
+
Portfolio KPIs, a 12-month blended-margin trend with the target band shaded, an exception list with deterministic explanations, and licensed-vs-active utilisation.
|
|
57
50
|
|
|
58
51
|
### Usage (`usage/get.php`, `_/app/talos/usage.php`)
|
|
59
|
-
Adds `costIntensity()` and a **Cost intensity** panel (tool-mix driven cost).
|
|
60
|
-
|
|
61
|
-
**Fixed:** `clientOptions()` read `TalosClients WHERE status='ONBOARDED'` from an
|
|
62
|
-
always-empty table, so the client selector rendered **empty**. It now calls
|
|
63
|
-
`GET /api/usage/clients` with a Team-DB fallback.
|
|
52
|
+
Adds `costIntensity()` and a **Cost intensity** panel (tool-mix driven cost). **Fixed:** `clientOptions()` read `TalosClients WHERE status='ONBOARDED'` from an always-empty table, so the client selector rendered **empty**. It now calls `GET /api/usage/clients` with a Team-DB fallback.
|
|
64
53
|
|
|
65
54
|
### Pricing Settings (`settings/{get,post}.php`) — replaces `factors/*`
|
|
66
|
-
Technical-only (persona-narrowed on the action, per `mvc-data-access-patterns`). **Measured
|
|
67
|
-
(derived) cost-factor rows are worker-owned and read-only**, enforced server-side with
|
|
68
|
-
`UPDATE ... AND isDerived = 0`. Policy rows are editable and carry full-sentence labels.
|
|
55
|
+
Technical-only (persona-narrowed on the action, per `mvc-data-access-patterns`). **Measured (derived) cost-factor rows are worker-owned and read-only**, enforced server-side with `UPDATE ... AND isDerived = 0`. Policy rows are editable and carry full-sentence labels.
|
|
69
56
|
|
|
70
57
|
### Removed
|
|
71
|
-
- `mvc/talos/benchmarks/get.php` — read the never-populated `TalosUsageMonthly` and
|
|
72
|
-
duplicated a worse subset of the Usage page.
|
|
58
|
+
- `mvc/talos/benchmarks/get.php` — read the never-populated `TalosUsageMonthly` and duplicated a worse subset of the Usage page.
|
|
73
59
|
- `mvc/talos/onboarding/*` and `mvc/talos/factors/*` — superseded above.
|
|
74
60
|
|
|
75
61
|
### Estimator (`App_Talos_Estimator`, `_/app/talos/estimator.php`)
|
|
76
|
-
Now driven by the **token unit price** and **workload profiles** instead of the feature
|
|
77
|
-
checklist and per-client calibration factor (methodology in `pricing-cogs-model`). Service
|
|
78
|
-
offering (CHAT / VOICE_TO_VOICE / NATURAL_VOICE) describes **modality only** and says nothing
|
|
79
|
-
about cost; cost is driven by tool-call **intensity** via the profile
|
|
80
|
-
(retrieval / general / analytics).
|
|
62
|
+
Now driven by the **token unit price** and **workload profiles** instead of the feature checklist and per-client calibration factor (methodology in `pricing-cogs-model`). Service offering (CHAT / VOICE_TO_VOICE / NATURAL_VOICE) describes **modality only** and says nothing about cost; cost is driven by tool-call **intensity** via the profile (retrieval / general / analytics). Per-interaction model adds `costPerInteraction`, `estimateByInteraction`, `recommendedPricePerInteraction`, `recommendedOrgFeeInteraction`, `orgFeeOptionsInteraction`. Constants live in Team-DB `TalosCostFactors` (shared with worker2); a golden-master parity test (`estimator.parity.test.php` ↔ worker2 `PricingParityTest.php`, 11/11 pass) guards drift — see [pricing-cogs-model](../../../2.0/apps/talos/features/pricing-cogs-model.md).
|
|
81
63
|
|
|
82
64
|
## Gotchas
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
`(string)` before indexing. See `mvc-data-access-patterns`.
|
|
91
|
-
- **`.tool--wide` did nothing.** `assets/css/style.css` documented the class as escaping the
|
|
92
|
-
960px `.app-content` cap "via `:has()`", but that rule was never written. Added
|
|
93
|
-
`.app-content:has(.tool--wide) { max-width: 100% }` — which also makes the pre-existing
|
|
94
|
-
`mvc/errors` pages genuinely full-width for the first time.
|
|
95
|
-
- **Put Talos page CSS in `style.css`, not a page-local `<style>` block.** The Contracts page
|
|
96
|
-
**returns early** for list mode, so a `<style>` block placed after that return applied to
|
|
97
|
-
the calculator only and the list rendered with no CSS at all.
|
|
98
|
-
- **Voice multipliers (0.25 / 0.50) are UNVALIDATED** — supplied second-hand, never measured,
|
|
99
|
-
and possibly inverted. Flagged in the UI. **Do not quote voice from them.**
|
|
65
|
+
- **`App_Database::fetchOne()` is not an existence test** — it 500s on zero rows. See [mvc-data-access-patterns](./mvc-data-access-patterns.md); the original `onboarding/post.php` carried this exact bug and would have fataled on the first save anyone attempted, which is good evidence nobody ever completed an onboarding through it.
|
|
66
|
+
- **This app runs PHP 8.5 and escalates deprecations to fatals** — `$arr[$row['nullableCol']]` is fatal even inside `isset()`. Local dev on PHP 8.2 does **not** reproduce it. Cast to `(string)` before indexing. See `mvc-data-access-patterns`.
|
|
67
|
+
- **`.tool--wide` did nothing.** `assets/css/style.css` documented the class as escaping the 960px `.app-content` cap "via `:has()`", but that rule was never written. Added `.app-content:has(.tool--wide) { max-width: 100% }` — which also makes the pre-existing `mvc/errors` pages genuinely full-width for the first time.
|
|
68
|
+
- **Put Talos page CSS in `style.css`, not a page-local `<style>` block.** The Contracts page **returns early** for list mode, so a `<style>` block placed after that return applied to the calculator only and the list rendered with no CSS at all.
|
|
69
|
+
- **Voice multipliers (0.25 / 0.50) are UNVALIDATED** — supplied second-hand, never measured, and possibly inverted. Flagged in the UI. **Do not quote voice from them.** Superseded for per-interaction voice by the `voiceCostPerMinute` factor (see pricing-cogs-model).
|
|
70
|
+
- **`pricingModel` is an enum/FIELD_LIST — validate it in PHP before save.** MySQL silently stores **NULL** for an unknown enum value, so `contracts/post.php` must check the incoming value against the allowed set (`PER_USER`/`PER_INTERACTION`) before saving, and re-validate the locked stored value on a signed row. (General 2.0 rule: `security.md` "Enum columns need validating in PHP".)
|
|
71
|
+
- **Security — two HIGH fixed on the contract page (cso, 2026-09-17):** (a) a signed contract could **flip its `pricingModel` on resubmit** to dodge the other model's minimum-field validation — the "locked at signing" override must run **before** per-model validation; (b) `clientIdentifier` (the AWS cost/usage join key) was only readonly client-side — it **must be re-pinned server-side** once signed, or a resubmit can misattribute a tenant.
|
|
100
72
|
- **`db_team`** is read/write on Contracts and Settings, read-only on the dashboards.
|
|
101
|
-
- **Chart.js colours must come from theme tokens, and a live theme switch needs
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`Chart.defaults` change does not reach charts that already exist. Chart.js otherwise defaults
|
|
106
|
-
axis/tick text to a dark colour that is unreadable in dark mode. See
|
|
107
|
-
[App-Wide Colour Theme](./theme-light-dark.md).
|
|
108
|
-
- **These pages keep an explicit `--c-bs-*` token group** because they were authored against
|
|
109
|
-
Bootstrap's palette (`#0d6efd`, `#6c757d`, `#495057`, `#dc3545`, `#198754`…), which is close
|
|
110
|
-
to but not the TOGA brand palette — folding them into `--c-primary` visibly restyles them.
|
|
111
|
-
- **Open (dark mode):** the error toast measures ~2.4:1 (white on `--c-danger`) and likely
|
|
112
|
-
wants a fixed dark fill; the Pricing Dashboard target-band dataset never had a
|
|
113
|
-
`backgroundColor` (pre-existing authoring slip) so the band shades rather than tints; the
|
|
114
|
-
three chart pages cannot be statically proven light-identical and still need a browser pass.
|
|
115
|
-
|
|
116
|
-
## Security
|
|
117
|
-
- `config.*.ini` carries committed plaintext secrets (team-accepted, see architecture Known
|
|
118
|
-
issues) — **all still owe rotation**. Config keys referenced by name only
|
|
119
|
-
(`[api] talos_backend_url` / `talos_backend_key`); no values recorded. Do not add secrets.
|
|
73
|
+
- **Chart.js colours must come from theme tokens, and a live theme switch needs `chart.update()`.** These pages now read series/axis/tick/gridline colours via `TogaTheme.color('--c-…')` inside a `paint()` registered with `TogaTheme.onChange(paint)`, then call `chart.update()` — `update()` clears Chart.js's resolver cache, so without it a `Chart.defaults` change does not reach charts that already exist. Chart.js otherwise defaults axis/tick text to a dark colour that is unreadable in dark mode. See [App-Wide Colour Theme](./theme-light-dark.md).
|
|
74
|
+
- **These pages keep an explicit `--c-bs-*` token group** because they were authored against Bootstrap's palette (`#0d6efd`, `#6c757d`, `#495057`, `#dc3545`, `#198754`…), which is close to but not the TOGA brand palette — folding them into `--c-primary` visibly restyles them.
|
|
75
|
+
- **Open (dark mode):** the error toast measures ~2.4:1 (white on `--c-danger`) and likely wants a fixed dark fill; the Pricing Dashboard target-band dataset never had a `backgroundColor` (pre-existing authoring slip) so the band shades rather than tints; the three chart pages cannot be statically proven light-identical and still need a browser pass.
|
|
76
|
+
- **Security:** `config.*.ini` carries committed plaintext secrets (team-accepted, see architecture Known issues) — **all still owe rotation**. Config keys referenced by name only (`[api] talos_backend_url` / `talos_backend_key`); no values recorded. Do not add secrets.
|
|
120
77
|
|
|
121
|
-
##
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
78
|
+
## Related
|
|
79
|
+
- [App-Wide Colour Theme](./theme-light-dark.md)
|
|
80
|
+
- [Persona-gated navigation](./persona-gated-navigation.md)
|
|
81
|
+
- [Tools MVC — data-access patterns](./mvc-data-access-patterns.md)
|
|
82
|
+
- [Tools architecture](../architecture.md)
|
|
83
|
+
- [Talos architecture (2.0)](../../../2.0/apps/talos/architecture.md)
|
|
84
|
+
- [Pricing COGS model (2.0)](../../../2.0/apps/talos/features/pricing-cogs-model.md)
|
|
85
|
+
- [Talos pricing automation (worker2)](../../../2.0/apps/worker2/features/talos-pricing-automation.md)
|
|
@@ -100,6 +100,24 @@ its IDLE write. **There is no timeout, memory cap, or resource limit anywhere in
|
|
|
100
100
|
in prod* (see below), never *tune the window size down*. (Corollary: a window that has grown back to
|
|
101
101
|
the cap is positive evidence the section is completing.)
|
|
102
102
|
|
|
103
|
+
#### Reading `NETSUITE_EXECUTION_MODE_<SECTION>` at a glance (2026-09-17)
|
|
104
|
+
|
|
105
|
+
The value is `<seconds>-<STATE>`. Three readings, and only the first is a real freeze:
|
|
106
|
+
|
|
107
|
+
- **`1-RUNNING`** (window collapsed ÷3 to 1s) = throwing on **every** run = real freeze. Go read the
|
|
108
|
+
error (`Logs.Event` by clientId → file:line → `Logs_<Client>.Api` on the transactionId).
|
|
109
|
+
- **A large-window `*-RUNNING`** (e.g. `864000-RUNNING`, `288000-RUNNING`) = healthy mid-run **or** a
|
|
110
|
+
deliberate pause/backfill — **NOT** stuck. Do not "fix" it.
|
|
111
|
+
- **`IDLE` + cursor frozen exactly at a reset value + NO error events = the section toggle is off**
|
|
112
|
+
(`IS_ENABLED_INTEGRATION_<SECTION> = false` in the wrapper). Resetting the cursor does **nothing**
|
|
113
|
+
while the toggle is off. Worked example: NYCHH SALES_ORDERS looked "stuck" after a cursor reset to
|
|
114
|
+
2020-01-01, but `sync_togasupply_hh.php:33` had `IS_ENABLED_INTEGRATION_SALES_ORDERS = false`;
|
|
115
|
+
re-enabling it (line 33 `true`) made the cursor advance immediately.
|
|
116
|
+
|
|
117
|
+
Before calling any section stuck, **check for error events first.** The developer routinely rewinds
|
|
118
|
+
cursors (and re-enables feeds) after fixes and to prioritize data, so a cursor sitting years back
|
|
119
|
+
(e.g. NYCHH IF/receipts rewound to 2023) can be an intentional backfill, not a fault.
|
|
120
|
+
|
|
103
121
|
**Where the error is: prod `Logs.Issue` / `Logs.Event`, NOT Sentry.** Read worker errors from the
|
|
104
122
|
production log DB by default — you do not need a developer to point you there. The base `Logs`
|
|
105
123
|
schema (prod-logs cluster) holds `Issue` (deduped, with `errorMessage` + `trace`) and `Event` (each
|
|
@@ -1681,6 +1699,39 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
1681
1699
|
SalesOrders" branch, classify the order authoritatively (`App_Api_Netsuite_Rest::fetchSalesOrderByIdFull`
|
|
1682
1700
|
+ `self::isTransferOrder`) and **return early** when it is a transfer order, gated on
|
|
1683
1701
|
`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS`. Existing TO-billed invoices were deleted by the developer.
|
|
1702
|
+
- **⚠ The invoice's billed-SO lookup must be PER-CLIENT scoped — the `==1` guard false-throws for a
|
|
1703
|
+
client that keeps two SalesOrders per NetSuite id (fixed + deployed 2026-09-17, Compass).**
|
|
1704
|
+
`syncInvoiceFromNetsuite` (`library/app/api/toga2.php` ~L3326-3339) looked up the billed sales order
|
|
1705
|
+
by `c_netsuiteInternalSalesOrderId` **only** and required `meta->totalRecordCount == 1`. Compass keeps
|
|
1706
|
+
**two** `SalesOrders` rows for one NetSuite id — the Office-Depot-created `customerId=1` order and the
|
|
1707
|
+
NetSuite-synced `customerId=3` order — so the lookup returned 2, failed the `==1` check, and the
|
|
1708
|
+
fail-loud dependency guard threw *"refusing to import invoice … its billed sales order … is not
|
|
1709
|
+
imported in toga"* (prod `Logs.Issue` 792, clientId 2, INVOICES stuck `96000-RUNNING`, cursor
|
|
1710
|
+
~2026-08-20) even though the correct order (id 97932, `customerId=3`, `locationId=615286`) was present.
|
|
1711
|
+
**Fix:** add a per-client scope block to this lookup **mirroring** `syncSalesOrderFromNetsuite` (~L974-987)
|
|
1712
|
+
and `syncItemFulfillmentFromNetsuite` (~L5197-5204) — Compass adds `SalesOrders.locationId=615286` +
|
|
1713
|
+
`customerId=3`; Prudential adds the `Customers` join + `CLIENT_UUID_PRUDENTIAL_CUSTOMER`; no other client
|
|
1714
|
+
changed, no else/default, and the guard still throws for a genuinely-missing SO. PHP 7.2-safe
|
|
1715
|
+
(`$options['join'] ?? []`), signature unchanged.
|
|
1716
|
+
- **⚠ OPEN follow-up (own ticket): six OTHER clients also keep two SalesOrders per NetSuite id, and the
|
|
1717
|
+
fix does NOT scope them.** A read-only sweep found the same dual-SO pattern in **Growrk (2789), Canon
|
|
1718
|
+
(2890), Endeavorhealth (4828), Spglobal (2893), Browardsheriff (160), Masonite (17)** (Nychh/Elite/Quad/Sbasite
|
|
1719
|
+
= 0; Compass 28377). Dormant today — only Compass is actually throwing the invoice guard (0 "refusing to
|
|
1720
|
+
import invoice" events for any other client in the last 5 days) — but if any of those six ever bills an
|
|
1721
|
+
invoice against a duplicated SO it will false-throw the same way, and **their duplicates have a DIFFERENT
|
|
1722
|
+
cause than Compass's Office-Depot dual-source.** Also a data-quality question: why do six non-Compass
|
|
1723
|
+
clients have duplicate SalesOrders rows at all.
|
|
1724
|
+
- **⚠ ITEM_RECEIPTS auto-creates a warehouse Location — the client's `LocationTypes` MUST have a
|
|
1725
|
+
"Warehouse" row or it EV-12-freezes (fixed 2026-09-17, Prudential).** `syncItemReceiptFromNetsuite`
|
|
1726
|
+
(`library/app/api/toga2.php`) `POST /v2/locations` with `locationType.uuid = WAREHOUSE_LOCATION_TYPE_UUID
|
|
1727
|
+
= 78c24f51-b33b-438b-e3f2-b6232d9ecb75` (:3997). `Client_Prudential.LocationTypes` had only a "Shipping"
|
|
1728
|
+
row — no "Warehouse" — so the reference didn't resolve → **EV-12** (prod `Logs.Issue` 757, clientId 5,
|
|
1729
|
+
ITEM_RECEIPTS `1-RUNNING`, cursor stuck 2026-09-14 15:12:43; confirmed via `Logs_Prudential.Api` `POST
|
|
1730
|
+
/v2/locations` 400). **Fix:** guarded/re-runnable INSERT of the Warehouse `LocationTypes` row using the
|
|
1731
|
+
**same literal uuid every client uses** (the documented shared-lookup-uuid exception to "uuids must be
|
|
1732
|
+
random"; `Client_Nychh` has it as id 2) —
|
|
1733
|
+
`dbchanges2/Client_Prudential/2026-09-17 - SeedWarehouseLocationType.sql`. **The live `LocationTypes`
|
|
1734
|
+
column is `name`, NOT the older `type`.** The section self-heals once the row exists (no manual mode reset).
|
|
1684
1735
|
- **⚠ ROOT CAUSE of the duplicate `Items`: the inventory-adjustment item-create OMITTED the catalog
|
|
1685
1736
|
(fixed 2026-08-31).** `getCreateItem` (`toga2.php:6233`) and the fulfillment create (~4951) stamp
|
|
1686
1737
|
`'catalog' => ['name' => 'Agilant']`; `syncInventoryAdjustmentFromNetsuite`'s item-create did **not** →
|
|
@@ -1773,6 +1824,20 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
1773
1824
|
[NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
|
|
1774
1825
|
|
|
1775
1826
|
## Change history
|
|
1827
|
+
- 2026-09-17 — **Cross-client sync-freeze debugging (Compass / Prudential / NYCHH / Elite).**
|
|
1828
|
+
(1) **Compass INVOICES** unfrozen (`96000-RUNNING`, `Logs.Issue` 792): `syncInvoiceFromNetsuite`'s
|
|
1829
|
+
billed-SO lookup keyed on `c_netsuiteInternalSalesOrderId` with a `==1` guard, but Compass keeps two
|
|
1830
|
+
`SalesOrders` per NetSuite id (ODP `customerId=1` + synced `customerId=3`) → lookup returned 2 →
|
|
1831
|
+
false-throw. Added a per-client scope block mirroring the SO/IF paths (Compass `locationId=615286`+`customerId=3`;
|
|
1832
|
+
Prudential `Customers` join). php-reviewer + cso clean, deployed. **Open follow-up (own ticket):** six other
|
|
1833
|
+
clients (Growrk/Canon/Endeavorhealth/Spglobal/Browardsheriff/Masonite) have the same dual-SO pattern from a
|
|
1834
|
+
different cause and are NOT scoped by this fix — dormant today. (2) **Prudential ITEM_RECEIPTS** unfrozen
|
|
1835
|
+
(`Logs.Issue` 757, EV-12): the receipt sync auto-creates a warehouse Location, but `Client_Prudential.LocationTypes`
|
|
1836
|
+
had no "Warehouse" row → seeded it via `dbchanges2/Client_Prudential/2026-09-17 - SeedWarehouseLocationType.sql`
|
|
1837
|
+
(shared-lookup-uuid; live column is `name`), deployed, section self-healed. (3) Recorded the
|
|
1838
|
+
**execution-mode reading key** (`1-RUNNING` = real freeze; large-window `*-RUNNING` = healthy/paused;
|
|
1839
|
+
`IDLE`+frozen-at-reset+no-errors = toggle off) — NYCHH SALES_ORDERS looked stuck but was toggled off at
|
|
1840
|
+
`sync_togasupply_hh.php:33`; re-enabling advanced the cursor. (jcardinal)
|
|
1776
1841
|
- 2026-09-17 — Four importer changes, all found on Elite. (1) New
|
|
1777
1842
|
`App_Api_Toga2::retireStaleSalesOrderForTransferOrder()` retires the stale `SalesOrder` left
|
|
1778
1843
|
behind when an order is reclassified as a TransferOrder — 218 Elite duplicates were
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary |
|
|
4
4
|
|-----|---------|
|
|
5
|
-
| [TOGa IQ (talos) Architecture](architecture.md) |
|
|
5
|
+
| [TOGa IQ (talos) Architecture](architecture.md) | Whole-repo map of TOGa IQ / Aegra (Python AI agent platform); open to understand subsystems, multi-tenancy, or where a component lives. |
|
|
6
6
|
| [aegra-api — Agent Protocol HTTP + Execution Pipeline](features/aegra-api.md) | `aegra-api` is the **FastAPI Agent Protocol server** at the heart of TOGa IQ. |
|
|
7
7
|
| [TOGa IQ Chat Frontend (Next.js) — streaming stack & Aegra wiring](features/chat-frontend.md) | The repo **`agilantsolutions/talos`** is the TOGa IQ **chat front-end** — the browser client that talks to the Aegra Agent-Protocol backend. |
|
|
8
8
|
| [Deployment — Docker, Compose, Entrypoint, External PG/Redis](features/deployment.md) | TOGa IQ ships as a **single container** (`aegra` service) wrapping the `aegra-api` FastAPI server. |
|
|
9
9
|
| [MCP Servers — clickup-mcp and toga-db-mcp](features/mcp-servers.md) | Two internal **FastMCP** servers exposed over **HTTP** with API-key auth and PM2 process management: - **`clickup-mcp`** — ClickUp workspace surface (spaces / f |
|
|
10
10
|
| [Observability — Langfuse, OTEL, Prometheus, OneUptime](features/observability.md) | TOGa IQ uses **two complementary tracing planes** plus optional Prometheus metrics and external uptime monitoring: - **Langfuse (native v3 SDK)** — LLM-shaped t |
|
|
11
|
-
| [Pricing & COGS Model (Talos / TOGa IQ Pricing Calculator)](features/pricing-cogs-model.md) |
|
|
11
|
+
| [Pricing & COGS Model (Talos / TOGa IQ Pricing Calculator)](features/pricing-cogs-model.md) | Cost basis + pricing methodology for selling Talos chat and voice; open to re-price Talos or understand COGS drivers, token rates, and the band/org-fee model. |
|
|
12
12
|
| [talos_agent — LangGraph ReAct Agent with Plan, BLP, MCP, Canvas](features/talos-agent.md) | `talos_agent` is the **reference LangGraph agent** shipped under `agents/talos_agent/`. |
|
|
@@ -6,7 +6,7 @@ project: TOGa IQ
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-09-17
|
|
10
10
|
owners: [akhokhani, jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- talos/libs/aegra-api/src/aegra_api/main.py
|
|
@@ -30,33 +30,17 @@ related:
|
|
|
30
30
|
- ../../1.0/apps/tools/features/talos-pricing-ui.md
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
Whole-repo map of TOGa IQ / Aegra (Python AI agent platform); open to understand subsystems, multi-tenancy, or where a component lives.
|
|
34
|
+
|
|
33
35
|
## Summary
|
|
34
36
|
|
|
35
|
-
**TOGa IQ**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
read-only via the `toga-db-mcp` server. It lives under `2.0/apps/talos/` because
|
|
43
|
-
its consumers are 2.0-era TOGa apps (TOGa Hub, TOGa View) and TogaHub auth.
|
|
44
|
-
|
|
45
|
-
> **Repo scope — this doc covers the BACKEND ONLY, which is not in the `talos` repo.**
|
|
46
|
-
> Verified 2026-08-28 (`git ls-tree` across all 16 `origin/*` refs): no branch of
|
|
47
|
-
> `agilantsolutions/talos` contains `libs/aegra-api`, `agents/`, `mcp-servers/`, or
|
|
48
|
-
> `docker-compose.yml` — every branch is the chat **front-end**, documented in
|
|
49
|
-
> [features/chat-frontend.md](features/chat-frontend.md). The Aegra backend described below lives
|
|
50
|
-
> in a separate, currently unregistered repo, so the `files:` paths above do not resolve.
|
|
51
|
-
|
|
52
|
-
**Critical rules:** Clients speak the **Agent Protocol** — never invent a custom wire shape; SDK and
|
|
53
|
-
LangGraph-Studio compatibility depend on it. **Tenant provisioning is explicit with no auto-create**,
|
|
54
|
-
so an unregistered `org_id` gets a clean **403 on every call even with a valid token** — provision
|
|
55
|
-
the tenant before debugging the caller. `AUTH_TYPE` ∈ {`togahub`, `entra`, `noop`} and **`noop` must
|
|
56
|
-
never ship to prod**. `toga-db-mcp` is the **only** sanctioned path into TOGa MySQL (read-only,
|
|
57
|
-
`LIMIT` 1–1000, enforced inside the MCP rather than the agent), and the Fernet `MCP_ENCRYPTION_KEY`
|
|
58
|
-
is a single **global** key shared by every tenant — not per-tenant — so treat rotating it as a
|
|
59
|
-
cross-tenant event.
|
|
37
|
+
**TOGa IQ** = TOGA's AI agent platform. On-disk repo **`talos`** (the "talos backend"). Python/FastAPI implementation of the **Agent Protocol** spec, multi-tenant, with a LangGraph **ReAct agent** as reference agent and internal **MCP servers** for ClickUp and TOGa MySQL clusters.
|
|
38
|
+
|
|
39
|
+
No PHP dependency (unlike `_underscore`, `api2`, `worker2`). Self-contained Python product; consumes TOGa data read-only via `toga-db-mcp`. Lives under `2.0/apps/talos/` because its consumers are 2.0-era apps (TOGa Hub, TOGa View) and TogaHub auth.
|
|
40
|
+
|
|
41
|
+
**Repo scope — this doc is BACKEND ONLY, which is NOT in the `talos` repo.** Verified 2026-08-28 (`git ls-tree` across all 16 `origin/*` refs): no branch of `agilantsolutions/talos` contains `libs/aegra-api`, `agents/`, `mcp-servers/`, or `docker-compose.yml` — every branch is the chat **front-end** ([features/chat-frontend.md](features/chat-frontend.md)). The Aegra backend below lives in a separate, currently unregistered repo, so the `files:` paths do not resolve.
|
|
42
|
+
|
|
43
|
+
**Critical rules:** Clients speak the **Agent Protocol** — never invent a custom wire shape; SDK and LangGraph-Studio compatibility depend on it. **Tenant provisioning is explicit, no auto-create**, so an unregistered `org_id` gets a clean **403 on every call even with a valid token** — provision the tenant before debugging the caller. `AUTH_TYPE` ∈ {`togahub`, `entra`, `noop`} and **`noop` must never ship to prod**. `toga-db-mcp` is the **only** sanctioned path into TOGa MySQL (read-only, `LIMIT` 1–1000, enforced inside the MCP not the agent), and the Fernet `MCP_ENCRYPTION_KEY` is a single **global** key shared by every tenant — treat rotating it as a cross-tenant event.
|
|
60
44
|
|
|
61
45
|
## Top-level layout
|
|
62
46
|
|
|
@@ -82,136 +66,73 @@ talos/
|
|
|
82
66
|
|
|
83
67
|
## Five subsystems
|
|
84
68
|
|
|
85
|
-
1. **HTTP / Agent Protocol** — `libs/aegra-api`. FastAPI
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
mode uses an in-memory queue + asyncio. Prod uses a Redis pub/sub broker +
|
|
91
|
-
Redis BLPOP worker queue with lease-based crash recovery. SSE replay is
|
|
92
|
-
backed by an in-memory ring + Postgres `run_events` table. See
|
|
93
|
-
`features/aegra-api.md`.
|
|
94
|
-
3. **Reference agent (LangGraph)** — `agents/talos_agent`. A ReAct graph with
|
|
95
|
-
optional planner gate, tool router, BLP semantic context selection,
|
|
96
|
-
running-summary memory, canvas artifacts, code-interpreter offload, and MCP
|
|
97
|
-
passthrough. See `features/talos-agent.md`.
|
|
98
|
-
4. **MCP fleet** — `clickup-mcp` and `toga-db-mcp`. Both FastMCP servers
|
|
99
|
-
exposed over HTTP, API-key gated, PM2-managed, with OneUptime heartbeats.
|
|
100
|
-
`toga-db-mcp` is the canonical read-only data plane across 4 prod clusters
|
|
101
|
-
+ legacy + 16 non-prod environments (see `clusters.yaml`). See
|
|
102
|
-
`features/mcp-servers.md`.
|
|
103
|
-
5. **Observability** — Langfuse (native v3 SDK) for LLM traces, OTEL (Phoenix /
|
|
104
|
-
Langfuse OTLP / generic OTLP / console) for spans, optional Prometheus
|
|
105
|
-
`/metrics`, plus OneUptime for uptime + heartbeats on MCP servers. See
|
|
106
|
-
`features/observability.md`.
|
|
69
|
+
1. **HTTP / Agent Protocol** — `libs/aegra-api`. FastAPI exposes ~40 routers: Assistants, Threads, Runs, Crons, Store, Files/Folders, Knowledge Bases, MCP, Sandboxes, Teams, BLP, admin/tenants. Pluggable auth via `AUTH_TYPE` ∈ {`togahub`, `entra`, `noop`}. See `features/aegra-api.md`.
|
|
70
|
+
2. **Execution pipeline** — Broker → Executor → LangGraph → Streaming. Local = in-memory queue + asyncio. Prod = Redis pub/sub broker + Redis BLPOP worker queue with lease-based crash recovery. SSE replay backed by in-memory ring + Postgres `run_events` table. See `features/aegra-api.md`.
|
|
71
|
+
3. **Reference agent (LangGraph)** — `agents/talos_agent`. ReAct graph with optional planner gate, tool router, BLP semantic context selection, running-summary memory, canvas artifacts, code-interpreter offload, MCP passthrough. See `features/talos-agent.md`.
|
|
72
|
+
4. **MCP fleet** — `clickup-mcp` and `toga-db-mcp`. Both FastMCP servers over HTTP, API-key gated, PM2-managed, OneUptime heartbeats. `toga-db-mcp` = canonical read-only data plane across 4 prod clusters + legacy + 16 non-prod environments (see `clusters.yaml`). See `features/mcp-servers.md`.
|
|
73
|
+
5. **Observability** — Langfuse (native v3 SDK) for LLM traces, OTEL (Phoenix / Langfuse OTLP / generic OTLP / console) for spans, optional Prometheus `/metrics`, OneUptime for uptime + MCP heartbeats. See `features/observability.md`.
|
|
107
74
|
|
|
108
75
|
## Multi-tenancy
|
|
109
76
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
service operates in **zero-overhead single-DB mode**.
|
|
116
|
-
|
|
117
|
-
At startup the lifespan handler migrates every registered tenant DB and seeds
|
|
118
|
-
base infrastructure before the health-check loop opens — slow first boot but
|
|
119
|
-
guarantees readiness.
|
|
77
|
+
- **Control plane DB** (`core/control_plane_db.py`, `control_plane_orm.py`) holds organization → tenant-DB mapping.
|
|
78
|
+
- `core/tenant_router.py` keeps an LRU cache (max 100) of per-tenant `DatabaseManager` instances keyed by `org_id`.
|
|
79
|
+
- Unregistered tenant → HTTP 403. Provisioning is explicit (no auto-create).
|
|
80
|
+
- `CONTROL_PLANE_DATABASE_URL` unset → **zero-overhead single-DB mode**.
|
|
81
|
+
- Startup lifespan migrates every registered tenant DB and seeds base infrastructure before the health-check loop opens — slow first boot but guarantees readiness.
|
|
120
82
|
|
|
121
83
|
## Auth
|
|
122
84
|
|
|
123
|
-
`AUTH_TYPE` selects
|
|
124
|
-
|
|
85
|
+
`AUTH_TYPE` selects backend:
|
|
125
86
|
- **togahub** — validates JWTs against TogaHub (`core/togahub_auth.py`).
|
|
126
87
|
- **entra** — Microsoft Entra ID JWT validation (`core/entra_auth.py`).
|
|
127
88
|
- **noop** — local-dev bypass; **never** ship to prod.
|
|
128
89
|
|
|
129
|
-
Auth is
|
|
130
|
-
`core/auth_deps.py`), not middleware — so OpenAPI sees `401` correctly and
|
|
131
|
-
public routes (`/health`, `/ready`, `/live`, `/info`, `/docs`) cleanly skip it.
|
|
132
|
-
API-key auth (`core/api_key_auth.py`) supplements user auth for service-to-service.
|
|
90
|
+
Auth is a FastAPI **dependency** (`require_auth` in `core/auth_deps.py`), not middleware — so OpenAPI sees `401` correctly and public routes (`/health`, `/ready`, `/live`, `/info`, `/docs`) cleanly skip it. API-key auth (`core/api_key_auth.py`) supplements user auth for service-to-service.
|
|
133
91
|
|
|
134
92
|
## Run lifecycle (mental model)
|
|
135
93
|
|
|
136
|
-
|
|
137
|
-
materializes the run, the **broker** publishes a `run_created` event, the
|
|
138
|
-
**executor** (local or worker) picks it up, `run_executor.py` calls
|
|
139
|
-
`langgraph_service` to compile the graph for this tenant/assistant, streams
|
|
140
|
-
events through `graph_streaming.py` into the broker, and the streaming endpoint
|
|
141
|
-
(`streaming_service.py`) replays from the broker buffer + Postgres event store
|
|
142
|
-
to the SSE consumer with 15s keepalive comments. Interrupts (HITL) surface as
|
|
143
|
-
`interrupt` events; resume re-enters via `Command(resume=…)`.
|
|
94
|
+
Client POSTs to `/threads/{id}/runs` (or stateless `/runs`). `run_preparation` materializes the run → **broker** publishes `run_created` → **executor** (local or worker) picks it up → `run_executor.py` calls `langgraph_service` to compile the graph for this tenant/assistant → streams events through `graph_streaming.py` into the broker → streaming endpoint (`streaming_service.py`) replays from broker buffer + Postgres event store to the SSE consumer with 15s keepalive comments. Interrupts (HITL) surface as `interrupt` events; resume re-enters via `Command(resume=…)`.
|
|
144
95
|
|
|
145
96
|
## Storage
|
|
146
97
|
|
|
147
|
-
- **Postgres** (with `pgvector`) — primary store: assistants, threads, runs,
|
|
148
|
-
|
|
149
|
-
- **Redis** — broker pub/sub + replay buffer, worker BLPOP queue, sequence
|
|
150
|
-
counters, optional rate-limit windows.
|
|
98
|
+
- **Postgres** (with `pgvector`) — primary store: assistants, threads, runs, checkpoints, `run_events`, `store` namespace KV + embeddings, knowledge bases.
|
|
99
|
+
- **Redis** — broker pub/sub + replay buffer, worker BLPOP queue, sequence counters, optional rate-limit windows.
|
|
151
100
|
- **S3** — file uploads (presigned URL flow via `api/files.py`).
|
|
152
101
|
- **Bedrock AgentCore sandbox** — code-interpreter session + large-output offload.
|
|
153
102
|
|
|
154
103
|
## Encryption
|
|
155
104
|
|
|
156
|
-
|
|
157
|
-
before DB storage (`core/encryption.py` → `encrypt_headers` / `decrypt_headers`).
|
|
158
|
-
**Not per-tenant** — all tenants share one key. Document *where* a credential
|
|
159
|
-
lives (env var name, config key) but never the value itself.
|
|
105
|
+
Single global `MCP_ENCRYPTION_KEY` (Fernet) encrypts MCP server headers before DB storage (`core/encryption.py` → `encrypt_headers` / `decrypt_headers`). **Not per-tenant** — all tenants share one key. Document *where* a credential lives (env var name, config key) but never the value.
|
|
160
106
|
|
|
161
107
|
## Deployment
|
|
162
108
|
|
|
163
|
-
- **Container** — multi-stage `python:3.11-slim-bookworm` build with `uv`,
|
|
164
|
-
|
|
165
|
-
- **
|
|
166
|
-
`alembic upgrade head && uvicorn aegra_api.main:app --host 0.0.0.0 --port 8000 --workers 2`.
|
|
167
|
-
- **Compose** — single `aegra` service; **PostgreSQL and Redis are
|
|
168
|
-
user-managed** (external). Health check hits `/live`. See
|
|
169
|
-
`features/deployment.md`.
|
|
109
|
+
- **Container** — multi-stage `python:3.11-slim-bookworm` build with `uv`, WeasyPrint runtime deps for HTML→PDF. Non-root `app:app`.
|
|
110
|
+
- **Entrypoint** — loads `.env.${ENV_NAME}` then runs `alembic upgrade head && uvicorn aegra_api.main:app --host 0.0.0.0 --port 8000 --workers 2`.
|
|
111
|
+
- **Compose** — single `aegra` service; **PostgreSQL and Redis are user-managed** (external). Health check hits `/live`. See `features/deployment.md`.
|
|
170
112
|
|
|
171
113
|
## Talos Pricing Platform (cross-framework)
|
|
172
114
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
- **System of record — Team DB (9 `Talos*` tables, dbchanges2):** TalosClients (contract
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- **Sales/admin UI — tools (1.0):** onboarding (locks band schedule), dashboard, benchmarks,
|
|
185
|
-
technical-only Cost Factors editor + `App_Talos_Estimator`. Reads Team DB via `db_team`.
|
|
186
|
-
- **Automation — worker2 (2.0):** monthly crons import AWS actuals, calibrate Langfuse→AWS,
|
|
187
|
-
recompute margins/recommendations, send the leadership report. Uses `_underscore::DB_TEAM`.
|
|
188
|
-
|
|
189
|
-
**Data flow:** Langfuse usage (structure) + AWS Cost Explorer (dollars) → calibrate per client →
|
|
190
|
-
margin vs. locked band → org-fee recommendation after N consecutive out-of-band months.
|
|
191
|
-
|
|
192
|
-
**Critical rules:** Team-DB fact tables are upsert-keyed on `(clientIdentifier, periodMonth
|
|
193
|
-
[, toolCategory])` — never INSERT-only. The Core tenant link is an indexed column, not an FK.
|
|
194
|
-
Always use Langfuse's cache-corrected cost (it undercounts cache badly).
|
|
115
|
+
Database-backed pricing/margin system replacing the Excel calculator. Three tiers:
|
|
116
|
+
|
|
117
|
+
- **System of record — Team DB (9 `Talos*` tables, dbchanges2):** TalosClients (contract header — now carries `pricingModel` PER_USER|PER_INTERACTION + contract-term + one-time fee columns), TalosPricingBands (locked per-user band schedule — **per-user model only**), TalosCostFactors (editable key/value cost constants — the "technical tab"; also holds `voiceCostPerMinute` + the `termDiscount.*` ramp), TalosUsageMonthly + TalosUsageFeatureMonthly (Langfuse import targets), TalosAwsActualsMonthly (Cost Explorer), TalosClientUserCounts (headcount for band-breach), TalosCalibrationMonthly + TalosFeeRecommendations (cron-derived). Keyed by `clientIdentifier` (= Langfuse `org:<slug>` tag and AWS cost tag) + `periodMonth` (1st-of-month DATE), UNIQUE on those for idempotent upsert. Tenant link to `Core.Clients` is a plain **indexed column, not a hard FK** (Team DB may be a separate instance). Conventions: id+uuid, dtCreated/dtUpdated, InnoDB, utf8mb4_0900_ai_ci.
|
|
118
|
+
- **Sales/admin UI — tools (1.0):** onboarding (locks band schedule), dashboard, benchmarks, technical-only Cost Factors editor + `App_Talos_Estimator`. Reads Team DB via `db_team`.
|
|
119
|
+
|
|
120
|
+
**Two revenue models (2026-09-17):** per-user (org fee + locked per-user band schedule) and per-interaction (org fee + single locked price per conversation, no bands). Both use the org fee as the sole monthly margin lever. Contract-term discount ramp (1/3/5-yr, % off year 1) applies to both, only when a term is set. Design in features/pricing-cogs-model.md.
|
|
121
|
+
- **Automation — worker2 (2.0):** monthly crons import AWS actuals, calibrate Langfuse→AWS, recompute margins/recommendations, send the leadership report. Uses `_underscore::DB_TEAM`.
|
|
122
|
+
|
|
123
|
+
**Data flow:** Langfuse usage (structure) + AWS Cost Explorer (dollars) → calibrate per client → margin vs. locked band → org-fee recommendation after N consecutive out-of-band months.
|
|
124
|
+
|
|
125
|
+
**Critical rules:** Team-DB fact tables are upsert-keyed on `(clientIdentifier, periodMonth [, toolCategory])` — never INSERT-only. The Core tenant link is an indexed column, not an FK. Always use Langfuse's cache-corrected cost (it undercounts cache badly). The **recurring** margin (org fee + usage/interaction revenue) is the ONLY input to the fee-change recommendation + streak; the **blended** margin (adds amortized one-time fees: implementation + training) is display-only — never feed amortized fees into the streak. The term ramp applies only to contracts with a set term (no retroactive discount).
|
|
195
126
|
|
|
196
127
|
## Key decisions
|
|
197
128
|
|
|
198
|
-
- **Agent Protocol over a custom shape** — clients (TOGa Hub, TOGa View, third-party)
|
|
199
|
-
|
|
200
|
-
- **
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
## Change history
|
|
210
|
-
- 2026-08-28 — Scoped this doc to the Aegra backend and recorded that it is **not** the `talos` repo
|
|
211
|
-
(verified across all 16 origin refs); the `talos` repo is the chat front-end, now documented in
|
|
212
|
-
`features/chat-frontend.md`. Dropped `"language": "python"` from the `talos` `registry.json` entry
|
|
213
|
-
so the front-end repo stops loading `2.0/standards/python.md`. **Deferred half — once the Aegra
|
|
214
|
-
backend repo's name is known, register it and set `"language": "python"` on THAT entry**; until
|
|
215
|
-
then no repo loads the Python standard. Added a backend-scoped `Critical rules:` line to the
|
|
216
|
-
Summary (the pre-existing one under Talos Pricing Platform covers pricing only). (apeterson)
|
|
217
|
-
- 2026-06-16 — Initial architecture doc for talos / TOGa IQ under 2.0/apps. (akhokhani)
|
|
129
|
+
- **Agent Protocol over a custom shape** — clients (TOGa Hub, TOGa View, third-party) share one wire format; LangGraph Studio works out of the box.
|
|
130
|
+
- **Pluggable executors** — `LocalExecutor` for dev/CI, `WorkerExecutor` with Redis lease + reaper for prod horizontal scaling.
|
|
131
|
+
- **Native Langfuse + OTEL side-by-side** — Langfuse for LLM-shaped traces (token/cost), OTEL for everything else; the OTEL Langfuse target is skipped when native mode is on to avoid duplicate spans.
|
|
132
|
+
- **MCP for data access** — `toga-db-mcp` is the *only* sanctioned path into TOGa MySQL for the agent; query enforcement (read-only, LIMIT 1–1000) lives inside the MCP, not the agent.
|
|
133
|
+
|
|
134
|
+
## Gotchas
|
|
135
|
+
|
|
136
|
+
- **`files:` paths don't resolve** — the Aegra backend lives in a separate unregistered repo (see repo-scope note in Summary); file-based knowledge search cannot bridge from Aegra code to this doc.
|
|
137
|
+
- **Slow first boot** — lifespan migrates every registered tenant DB before opening the health check; plan for >30s with many tenants.
|
|
138
|
+
- **`MCP_ENCRYPTION_KEY` is global** — rotating requires decrypt-then-re-encrypt for all `mcp_servers` rows across every tenant.
|