toga-ai 1.0.222 → 1.0.224
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/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/architecture.md +4 -3
- package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +29 -6
- package/knowledge/2.0/apps/api2/features/surface-meta-option.md +12 -1
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +21 -7
- package/knowledge/2.0/apps/talos/INDEX.md +1 -0
- package/knowledge/2.0/apps/talos/features/pricing-cogs-model.md +153 -0
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +47 -14
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
13
13
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
|
|
14
14
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
|
|
15
|
-
| [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
|
|
15
|
+
| [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
|
|
16
16
|
| [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | api2/Component/Api/V2/V2.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Client_Prudential/2026-06-15 - ItemFulfillmentTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Quad/2026-06-18a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19b - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql, dbchanges2/Client_Quad/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
|
|
17
17
|
| [Units for Items for Purchase Orders — Data Structure](features/units-for-items-for-purchase-orders.md) | Describes how unit (serialized inventory) data is linked to sales-order and purchase-order line items behind the `units-for-items-for-purchase-orders` TableView | |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-29
|
|
10
10
|
owners: ["jcardinal", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/_underscore.php
|
|
@@ -71,7 +71,7 @@ Tier-1 trivial declarative rules (frozen `all/any/none + {field,op,value}` gramm
|
|
|
71
71
|
client-side; Tier-2 business logic computed in PHP returning booleans (lives in
|
|
72
72
|
`_Model_<Client>_*` overrides + interceptors). Config/logic boundary: **config describes,
|
|
73
73
|
PHP decides.** M2M-safe delivery via opt-in `surface=<slug>` request option → response
|
|
74
|
-
`meta.surface`, never `data`. Generic FE machinery destined for `@agilant/toga-blox`.
|
|
74
|
+
`meta.surface`, never `data`. Generic FE machinery destined for `@agilant/toga-blox`. **Surface is the enforced presentation path — no feature flag.** The `SURFACE_ENABLED`/`VITE_SURFACE_ENABLED` flag and all legacy pre-Surface fallback branches were removed; legacy per-screen FIELDS config is deleted as each screen migrates (no dual-path fallback). The meta endpoint is query-string form: `GET /v2/surfaces/meta?slug=<slug>`.
|
|
75
75
|
|
|
76
76
|
**Rejected alternatives:** a generic EAV settings cascade (the old `*Settings` tables —
|
|
77
77
|
proven near-empty in prod); raw-CSS-in-config (the old "Dummy Fields" anti-pattern).
|
|
@@ -80,7 +80,7 @@ proven near-empty in prod); raw-CSS-in-config (the old "Dummy Fields" anti-patte
|
|
|
80
80
|
allowlist that throws on unknown ops, with a CI test asserting the op-set; defensive
|
|
81
81
|
resolver with logged fallbacks; debug-bundle endpoint ships WITH the resolver; `c_longValue`
|
|
82
82
|
on overrides with strict per-attribute casting; per-surface meta() cutover with a parity
|
|
83
|
-
diff (do NOT delete meta() until every page is migrated); `BLANK_CLIENT_DATABASE` as a CI gate.
|
|
83
|
+
diff (do NOT delete meta() until every page is migrated); `BLANK_CLIENT_DATABASE` as a CI gate. Reserved Core id blocks: `Records` 333–341, `RecordFields` 2246–2433 (renumbered 2026-06-29, seed files only — a known file-vs-DB id drift in already-provisioned envs, to reconcile by re-provisioning). Anything spanning both id schemes must be id-agnostic (match by `route=`, not id).
|
|
84
84
|
|
|
85
85
|
## Naming convention & autoloader
|
|
86
86
|
|
|
@@ -324,3 +324,4 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
|
|
|
324
324
|
## Change history
|
|
325
325
|
- 2026-06-11 — Documented lazy transaction gotcha in `_Database::register()` (rgirish)
|
|
326
326
|
- 2026-06-25 — Added the Surface platform UI presentation/configuration layer (DB-driven UI config replacing `Page::meta()`, CTO-reviewed AGREE-WITH-ADJUSTMENTS) (jcardinal)
|
|
327
|
+
- 2026-06-29 — Surface made the enforced (un-flagged) presentation path; reserved id blocks renumbered to Records 333–341 / RecordFields 2246–2433 (known seed-vs-provisioned drift). (jcardinal)
|
|
@@ -6,10 +6,11 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-29
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Core/Surface.php
|
|
13
|
+
- dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
|
|
13
14
|
- _underscore/Model/Core/SurfaceElement.php
|
|
14
15
|
- _underscore/Model/Core/Action.php
|
|
15
16
|
- _underscore/Model/Core/Vocabulary.php
|
|
@@ -30,8 +31,9 @@ related:
|
|
|
30
31
|
The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a
|
|
31
32
|
cached resolver, `_Model_Core_Surface::resolve(&$api, string $slug)`, that merges base config +
|
|
32
33
|
cascade overrides + ACL + messages + theme tokens into one flat, serializable bundle. It is the
|
|
33
|
-
**replacement for `_Model_Core_Page::meta()`** and is shipped as a scripted-API method
|
|
34
|
-
`GET /v2/surfaces/
|
|
34
|
+
**replacement for `_Model_Core_Page::meta()`** and is shipped as a scripted-API method called as
|
|
35
|
+
`GET /v2/surfaces/meta?slug=<slug>` (RecordScript args come from the **query string**, not the
|
|
36
|
+
path — see gotchas). Platform/shared — not client-specific. Schema:
|
|
35
37
|
[surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md).
|
|
36
38
|
|
|
37
39
|
## The 9 models
|
|
@@ -62,8 +64,8 @@ Inputs come from `$api` (the same accessor `_Model_Client_TableView::meta` uses)
|
|
|
62
64
|
table pipeline (referenced, never duplicated).
|
|
63
65
|
7. Assemble the flat bundle. **Zero INSERTs.**
|
|
64
66
|
|
|
65
|
-
A `
|
|
66
|
-
resolver, not "later").
|
|
67
|
+
A `metaDebug()` inspector returns the resolved bundle plus the raw cascade layers (shipped with the
|
|
68
|
+
resolver, not "later"). It is **not** named `debug()` — see gotchas.
|
|
67
69
|
|
|
68
70
|
## Caching & invalidation
|
|
69
71
|
|
|
@@ -97,8 +99,29 @@ resolver, not "later").
|
|
|
97
99
|
falls back to sane defaults and never crashes a page. Strict per-`attribute` casting with logged
|
|
98
100
|
fallback on bad `SurfaceOverrides` data.
|
|
99
101
|
- **Cross-DB reads are batched** (DB_CORE vs DB_CLIENT) and FKs are soft — tolerate dangling refs.
|
|
102
|
+
- **A scripted-API method MUST NOT collide with a non-static `_Model` base method.** Naming the
|
|
103
|
+
inspector `debug()` caused a fatal *"Cannot make non static method _Model::debug() static"* — the
|
|
104
|
+
framework base `_Model` defines a NON-static `debug()` (Model.php:167) and the scripted-API
|
|
105
|
+
dispatch calls the method statically. Renamed to `metaDebug` and remapped the RecordScript
|
|
106
|
+
(`route 'debug' → phpMethod 'metaDebug'`). Vet any new scripted-API method name against the
|
|
107
|
+
`_Model` base (e.g. `debug`, and check others) before mapping it.
|
|
108
|
+
- **The endpoint is `GET /v2/surfaces/meta?slug=<slug>`, not `/v2/surfaces/<slug>/meta`.** TOGA
|
|
109
|
+
RecordScripts read their args from the **query string**; the path form makes the engine parse the
|
|
110
|
+
slug as a record uuid and return 404 EV-6. Pattern for any RecordScript:
|
|
111
|
+
`/<route>/<scriptRoute>?<arg>=...`.
|
|
112
|
+
- **Core metadata records exposed to the app must grant CORE role Public (id 1) READ.** The meta
|
|
113
|
+
RecordScript is ACL-gated on the `surfaces` Core record, and authenticated app users carry CORE
|
|
114
|
+
role Public(1); a seed that grants only Super User(3)/Base(4) returns 403 EZ-1. Grant Public(1)
|
|
115
|
+
`allowRead` on the `surfaces` record (full AclRecordPermissions → AclLogicGroups →
|
|
116
|
+
AclLogicGroupExpressions → AclRecordExpressions 'all' chain), mirroring how `apps`/`records`/
|
|
117
|
+
`record-fields` already expose Public READ. Only the `surfaces` record gets Public read — the
|
|
118
|
+
admin-CRUD sibling records stay Super-User-only (the resolver reads their tables server-side).
|
|
100
119
|
|
|
101
120
|
## Change history
|
|
102
|
-
- 2026-06-
|
|
121
|
+
- 2026-06-29 — Deploy fixes: renamed the scripted inspector `debug()`→`metaDebug()` (name collided
|
|
122
|
+
with non-static `_Model::debug()`, fatal); corrected the endpoint to query-string form
|
|
123
|
+
`GET /v2/surfaces/meta?slug=` (path form 404'd EV-6); granted CORE Public(1) READ on the `surfaces`
|
|
124
|
+
record so app users stop getting 403 EZ-1 on meta (id-agnostic migration matches by `route='surfaces'`). (jcardinal)
|
|
125
|
+
- 2026-06-25 — Built the 9 models + `resolve()`/`metaDebug()` as the cached, parameterized,
|
|
103
126
|
zero-write-on-read replacement for `_Model_Core_Page::meta()`; cache-bust interceptors on all 9
|
|
104
127
|
models (outside the transaction). meta() retained until per-page cutover with parity diff. (jcardinal)
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-29
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -42,6 +42,17 @@ traffic), behavior is **byte-for-byte unchanged** — pure data, zero extra work
|
|
|
42
42
|
the logic.
|
|
43
43
|
- **One call, no extra round-trip** — the state pass rides the existing data fetch.
|
|
44
44
|
|
|
45
|
+
## Gotchas
|
|
46
|
+
|
|
47
|
+
- **Meta endpoint 403 EZ-1 for normal app users → grant CORE Public(1) READ on the `surfaces`
|
|
48
|
+
record.** Authenticated app users carry CORE role Public (id 1); the meta RecordScript is
|
|
49
|
+
ACL-gated on the `surfaces` Core record, and a seed granting only Super User(3)/Base(4) rejects
|
|
50
|
+
them. Grant Public(1) `allowRead` on `surfaces` only (the admin-CRUD sibling records stay
|
|
51
|
+
Super-User-only — the resolver reads their tables server-side). See
|
|
52
|
+
[surface-resolver](../../_underscore/features/surface-resolver.md) gotchas for the full chain.
|
|
53
|
+
|
|
45
54
|
## Change history
|
|
55
|
+
- 2026-06-29 — Documented the meta-endpoint ACL: normal app users (CORE Public role 1) need
|
|
56
|
+
`allowRead` on the `surfaces` record or meta returns 403 EZ-1. (jcardinal)
|
|
46
57
|
- 2026-06-25 — Added the `surface=<slug>` opt-in option to the V2 engine; attaches per-record action
|
|
47
58
|
state under `meta.surface` (M2M-safe, defensive, absent = unchanged). (jcardinal)
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
|
|
6
|
-
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql |
|
|
6
|
+
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql |
|
|
7
7
|
| [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | How to manually stand up a new 2.0 client (tenant). | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
|
|
@@ -6,12 +6,14 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-29
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql
|
|
13
13
|
- dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql
|
|
14
14
|
- dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql
|
|
15
|
+
- dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql
|
|
16
|
+
- dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql
|
|
15
17
|
- dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
|
|
16
18
|
- dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql
|
|
17
19
|
- dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql
|
|
@@ -60,11 +62,13 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
|
|
|
60
62
|
**SalesOrders + login** vertical (not Tickets) as the review proof:
|
|
61
63
|
|
|
62
64
|
- `Core/2026-06-25a - SurfaceCoreTables.sql` — the 6 Core tables.
|
|
63
|
-
- `Core/2026-06-25b - SurfaceRecordsAndFields.sql` — `Core.Records`+`RecordFields` for all 9 tables (
|
|
65
|
+
- `Core/2026-06-25b - SurfaceRecordsAndFields.sql` — `Core.Records`+`RecordFields` for all 9 tables (`Records` ids **333–341**, `RecordFields` ids **2246–2433**; `recordId` via `model=` subselect). Also maps the meta `RecordScript` `route 'debug' → phpMethod 'metaDebug'` (the inspector cannot be named `debug` — see [surface-resolver](../../_underscore/features/surface-resolver.md)).
|
|
64
66
|
- `Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql` — Core seed + the `RecordScripts` resolve row.
|
|
67
|
+
- `Core/2026-06-29a - ItemsSurfaceSeed.sql` — the Items LIST screen seed: surfaces `items-list` (TABLE → existing `items` TableView id 9, recordId 21) + `items-list-actions` (BUTTON_BAR: refresh/columns/newItem) + Actions + ~11 Messages. Single DEFAULT bundle, no Client overrides.
|
|
68
|
+
- `Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql` — grants CORE role Public(1) `allowRead` on the `surfaces` record (so app users stop getting 403 EZ-1 on meta). **Id-agnostic**: matches the record by `route='surfaces'`, so it works whether the record id is 333 or the legacy 2300.
|
|
65
69
|
- `Client/2026-06-25a - SurfaceClientTables.sql` — the 3 Client tables + `INSERT IGNORE Languages('en','English')`.
|
|
66
70
|
- `Client/2026-06-25b - SurfaceClientSeed.sql` — `ThemeTokens` + `MessageTranslations(en)`.
|
|
67
|
-
- `Client/2026-06-25c - SurfaceClientAcl.sql` — full ACL chain for the 3 CLIENT-aclDatabase records.
|
|
71
|
+
- `Client/2026-06-25c - SurfaceClientAcl.sql` — full ACL chain for the 3 CLIENT-aclDatabase records (recordId refs updated to the 333-block renumber).
|
|
68
72
|
|
|
69
73
|
## Gotchas
|
|
70
74
|
|
|
@@ -79,12 +83,22 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
|
|
|
79
83
|
- **`SurfaceOverrides.value` is a stringly-typed escape hatch** — a long `CONFIG`/label override
|
|
80
84
|
would silently truncate at varchar(255); that is why `c_longValue mediumtext` exists, and the
|
|
81
85
|
resolver MUST cast strictly per `attribute` (bool/int/string/json) with a logged fallback.
|
|
82
|
-
- **Reserved id
|
|
83
|
-
|
|
86
|
+
- **Reserved id blocks (renumbered 2026-06-29):** Surface `Core.Records` ids **333–341** (9 tables),
|
|
87
|
+
`Core.RecordFields` ids **2246–2433**. (Originally seeded at 2300–2308 / 2400+, renumbered at the
|
|
88
|
+
team's request.) `RecordScripts.recordId` and the Core+Client ACL `recordId` refs were updated to
|
|
89
|
+
match. Use `model=` subselects for `recordId`, never hardcoded ids.
|
|
90
|
+
- **Known file-vs-DB id drift.** The renumber changed the **seed files only** — already-provisioned
|
|
91
|
+
envs still hold the old 2300/2400 ids. This is intentional/known, to be reconciled by
|
|
92
|
+
re-provisioning later. Anything that must work across both (e.g. the Public-READ ACL grant) must
|
|
93
|
+
be **id-agnostic** — match the record by `route='surfaces'`, not by id.
|
|
84
94
|
- **CLIENT-aclDatabase records** (`SurfaceOverrides`, `MessageTranslations`, `ThemeTokens`) need the
|
|
85
95
|
full 4-step ACL chain + field permissions in **each** client DB; resolve `roleId` by subselect.
|
|
86
96
|
|
|
87
97
|
## Change history
|
|
88
|
-
- 2026-06-
|
|
89
|
-
|
|
98
|
+
- 2026-06-29 — Renumbered the reserved seed id blocks (Records 2300-2308→**333–341**, RecordFields
|
|
99
|
+
2400+→**2246–2433**) in the seed files only — known file-vs-DB drift in provisioned envs. Added
|
|
100
|
+
the Items LIST seed (`ItemsSurfaceSeed.sql`) and the id-agnostic CORE Public(1) READ ACL grant on
|
|
101
|
+
`surfaces` (`SurfaceMetaPublicReadAcl.sql`, matches by `route='surfaces'`). (jcardinal)
|
|
102
|
+
- 2026-06-25 — Initial schema for the Surface layer: 6 Core + 3 Client tables, `Records`/`RecordFields`,
|
|
103
|
+
SalesOrders+login seed, ACL chain, and `BLANK_CLIENT_DATABASE` append. Typed
|
|
90
104
|
columns chosen over EAV (the old `*Settings` cascade was empirically near-empty in prod). (jcardinal)
|
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
| [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. | talos/docker-compose.yml, talos/deployments/docker/Dockerfile, talos/deployments/docker/entrypoint.sh |
|
|
8
8
|
| [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 | talos/mcp-servers/clickup-mcp/src, talos/mcp-servers/clickup-mcp/ecosystem.config.js, talos/mcp-servers/clickup-mcp/ecosystem.dev.config.js, talos/mcp-servers/clickup-mcp/pyproject.toml, talos/mcp-servers/clickup-mcp/.env.example, talos/mcp-servers/toga-db-mcp/src, talos/mcp-servers/toga-db-mcp/clusters.yaml, talos/mcp-servers/toga-db-mcp/ecosystem.config.js, talos/mcp-servers/toga-db-mcp/pyproject.toml, talos/mcp-servers/toga-db-mcp/.env.example |
|
|
9
9
|
| [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 | talos/libs/aegra-api/src/aegra_api/observability/__init__.py, talos/libs/aegra-api/src/aegra_api/observability/setup.py, talos/libs/aegra-api/src/aegra_api/observability/base.py, talos/libs/aegra-api/src/aegra_api/observability/langfuse_provider.py, talos/libs/aegra-api/src/aegra_api/observability/langfuse_client.py, talos/libs/aegra-api/src/aegra_api/observability/otel.py, talos/libs/aegra-api/src/aegra_api/observability/metrics.py, talos/libs/aegra-api/src/aegra_api/observability/span_enrichment.py, talos/libs/aegra-api/src/aegra_api/observability/targets |
|
|
10
|
+
| [Pricing & COGS Model (Talos / TOGa IQ Pricing Calculator)](features/pricing-cogs-model.md) | The cost basis and pricing methodology for selling Talos (TOGa IQ) chat and voice deployments. | |
|
|
10
11
|
| [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/`. | talos/agents/talos_agent/graph.py, talos/agents/talos_agent/state.py, talos/agents/talos_agent/context.py, talos/agents/talos_agent/prompts.py, talos/agents/talos_agent/tool_router.py, talos/agents/talos_agent/model_helpers.py, talos/agents/talos_agent/config/context.py, talos/agents/talos_agent/config/model_config.py, talos/agents/talos_agent/config/tool_config.py, talos/agents/talos_agent/config/plan_config.py, talos/agents/talos_agent/config/memory_config.py, talos/agents/talos_agent/config/mcp_config.py, talos/agents/talos_agent/config/blp_config.py, talos/agents/talos_agent/config/interrupt_config.py, talos/agents/talos_agent/config/artifact_config.py, talos/agents/talos_agent/plan/planner.py, talos/agents/talos_agent/plan/nodes.py, talos/agents/talos_agent/llm/system_prompt.py, talos/agents/talos_agent/llm/tool_binding.py, talos/agents/talos_agent/llm/message_ops.py, talos/agents/talos_agent/mcp/client.py, talos/agents/talos_agent/mcp/security.py, talos/agents/talos_agent/mcp/session_manager.py, talos/agents/talos_agent/blp/registry.py, talos/agents/talos_agent/blp/embedder.py, talos/agents/talos_agent/blp/nodes.py, talos/agents/talos_agent/tools/__init__.py, talos/agents/talos_agent/tools/code_interpreter.py, talos/agents/talos_agent/tools/canvas.py, talos/agents/talos_agent/tools/knowledge_base.py, talos/agents/talos_agent/tools/skill_injector.py, talos/agents/talos_agent/thread_name/nodes.py |
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Pricing & COGS Model (Talos / TOGa IQ Pricing Calculator)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: talos
|
|
5
|
+
project: TOGa IQ
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-29
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files: []
|
|
12
|
+
related:
|
|
13
|
+
- ../architecture.md
|
|
14
|
+
- aegra-api.md
|
|
15
|
+
- observability.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
The cost basis and pricing methodology for selling Talos (TOGa IQ) chat and
|
|
21
|
+
voice deployments. The deliverable is a **per-client Excel workbook — TALOS
|
|
22
|
+
Pricing Calculator v9** (replaces v8) — that lives on the team's shared AI drive
|
|
23
|
+
(under `P:\Work\AI\`), **not** in any repo. This doc captures the durable
|
|
24
|
+
knowledge: the cost drivers, measured production rates, and the pricing
|
|
25
|
+
decisions, so anyone can re-price Talos without re-deriving the model.
|
|
26
|
+
|
|
27
|
+
There is no Talos source code behind this — it is a sales/finance model. Treat
|
|
28
|
+
the numbers below as the **April 2026 baseline**; re-measure before relying on
|
|
29
|
+
them in a later period.
|
|
30
|
+
|
|
31
|
+
## Chat COGS model
|
|
32
|
+
|
|
33
|
+
Chat AI cost is driven primarily by **LLM tokens on Amazon Bedrock**.
|
|
34
|
+
|
|
35
|
+
### Bedrock token rates (per 1M tokens)
|
|
36
|
+
|
|
37
|
+
| Token class | Rate |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Fresh input | $3.00 |
|
|
40
|
+
| Cached-instruction read | $0.30 |
|
|
41
|
+
| Cache write | $3.75 |
|
|
42
|
+
| Output | $15.00 |
|
|
43
|
+
|
|
44
|
+
### Measured per-exchange production averages
|
|
45
|
+
|
|
46
|
+
Source: tenant DB, April 2026, ~3,610 exchanges.
|
|
47
|
+
|
|
48
|
+
| Metric | Tokens / exchange |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Cached instructions | 15,013 |
|
|
51
|
+
| Cache write | 2,830 |
|
|
52
|
+
| Output | 527 |
|
|
53
|
+
|
|
54
|
+
### Per-feature token adders (measured)
|
|
55
|
+
|
|
56
|
+
| Feature | Tokens per use | Sample size |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Document search | 4,747 / search | 542 searches |
|
|
59
|
+
| Web search | 765 / search | 120 searches |
|
|
60
|
+
| Code execution | 205 / run | 929 runs |
|
|
61
|
+
| App action | 1,086 / action | 71 actions |
|
|
62
|
+
|
|
63
|
+
**Assumed feature occurrences per conversation:** document search 2, web search
|
|
64
|
+
1, code execution 2, app actions 5 **per connected app**.
|
|
65
|
+
|
|
66
|
+
### Compute and fixed infrastructure
|
|
67
|
+
|
|
68
|
+
- **Code session compute** — Bedrock AgentCore ~$0.05 / session; ~80% of
|
|
69
|
+
code-enabled conversations trigger a session.
|
|
70
|
+
- **Platform / hosting** — $910 / month fixed.
|
|
71
|
+
- **Document-search index** — OpenSearch Serverless, +$350 / month, charged
|
|
72
|
+
**only when Document Search is enabled**.
|
|
73
|
+
|
|
74
|
+
### Conversation model constants (v8/v9 engine)
|
|
75
|
+
|
|
76
|
+
- System overhead: 200 tokens / conversation
|
|
77
|
+
- Base fresh: 727 tokens / exchange
|
|
78
|
+
- History accumulation factor: ×7
|
|
79
|
+
- Exchanges per conversation: 15
|
|
80
|
+
|
|
81
|
+
> **Gotcha (flagged for technical review):** history accumulates over ×7 but
|
|
82
|
+
> the per-exchange cost is multiplied by 15 exchanges. This mismatch is a known
|
|
83
|
+
> quirk, **preserved as-is in v9 for number parity with v8** — do not "fix" it
|
|
84
|
+
> silently, as it would change every quoted price. See Change history.
|
|
85
|
+
|
|
86
|
+
### Measured cost-per-exchange benchmarks by assistant type (April 2026)
|
|
87
|
+
|
|
88
|
+
| Assistant type | Cost / exchange |
|
|
89
|
+
|---|---|
|
|
90
|
+
| Contact Centre | $0.0223 |
|
|
91
|
+
| HR / Compliance | $0.0354 |
|
|
92
|
+
| General Purpose | $0.0435 |
|
|
93
|
+
| Sales / CRM | $0.0829 |
|
|
94
|
+
| Technical / Data-heavy | $0.1139 |
|
|
95
|
+
| Enterprise heavy | $0.1261 |
|
|
96
|
+
|
|
97
|
+
## Voice COGS model
|
|
98
|
+
|
|
99
|
+
Talos Voice (voice-to-voice) is priced **per minute**. It uses the AWS STT/TTS
|
|
100
|
+
approach. Per-minute drivers:
|
|
101
|
+
|
|
102
|
+
| Driver | Rate / basis |
|
|
103
|
+
|---|---|
|
|
104
|
+
| LiveKit | $50/mo plan incl. 5,000 min; $0.01/min overage |
|
|
105
|
+
| Amazon Transcribe (STT) | $0.024 / min |
|
|
106
|
+
| Amazon Polly Generative (TTS) | $30 / 1M chars at ~400 chars/min |
|
|
107
|
+
| LLM tokens | ~800 fresh, ~5,000 cached, ~300 output per minute, at the chat token rates above |
|
|
108
|
+
| S3 recording storage | $0.023 / GB-mo at ~0.5 MB/min over the retention window |
|
|
109
|
+
|
|
110
|
+
## Pricing methodology (decision)
|
|
111
|
+
|
|
112
|
+
Adopted to **reduce client-facing price fluctuation while protecting margin**:
|
|
113
|
+
|
|
114
|
+
1. **Two-part price.** Client pays a **flat monthly org fee** PLUS a **fixed
|
|
115
|
+
monthly per-user fee**. The per-user fee is a stable rate-card value and does
|
|
116
|
+
**not** change.
|
|
117
|
+
2. **Margin band.** Sales sets a min and max gross-margin band. The recommended
|
|
118
|
+
flat fee is solved so projected margin lands at the **midpoint** of the band:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
flat = COGS / (1 - mid_margin) - per_user_fee * users (floored at 0)
|
|
122
|
+
```
|
|
123
|
+
3. **Smoothing rule.** Actual COGS is tracked monthly per client. The flat fee
|
|
124
|
+
is changed **only after** actual margin sits outside the band for a
|
|
125
|
+
configurable number of **consecutive** months (**default 3**). The per-user
|
|
126
|
+
fee is **never** adjusted.
|
|
127
|
+
|
|
128
|
+
### Workbook structure (TALOS Pricing Calculator v9)
|
|
129
|
+
|
|
130
|
+
| Tab | Purpose |
|
|
131
|
+
|---|---|
|
|
132
|
+
| Sales | Rep-facing inputs + recommended flat fee + min/mid/max margin-band options |
|
|
133
|
+
| Voice | Per-minute band pricing |
|
|
134
|
+
| Technical | Single source of truth for all raw rates — sales never edits |
|
|
135
|
+
| Actuals | Monthly actual-AWS-COGS entry, margin-vs-band status, consecutive-breach flags, suggested mid-band flat fee |
|
|
136
|
+
| Engine | Hidden calculation engine |
|
|
137
|
+
|
|
138
|
+
The workbook is a **per-client** file on the shared AI drive (`P:\Work\AI\`).
|
|
139
|
+
|
|
140
|
+
## Gotchas / known issues
|
|
141
|
+
|
|
142
|
+
- **Numbers are an April 2026 baseline.** Token rates, AWS prices, and measured
|
|
143
|
+
per-exchange averages drift — re-measure from the tenant DB before quoting in
|
|
144
|
+
a later period.
|
|
145
|
+
- **History ×7 vs. ×15 exchanges quirk** (see Chat model) — intentional parity
|
|
146
|
+
hack, not a bug to silently correct.
|
|
147
|
+
- **OpenSearch +$350/mo is conditional** — only counts when Document Search is
|
|
148
|
+
enabled; do not bake it into every quote.
|
|
149
|
+
- **Do not embed the absolute drive path as canonical** — the calculator lives
|
|
150
|
+
under the team shared AI drive; the path can move.
|
|
151
|
+
|
|
152
|
+
## Change history
|
|
153
|
+
- 2026-06-29 — Initial pricing/COGS model doc: chat token + feature drivers, voice per-minute drivers, two-part flat+per-user methodology with mid-band solve and consecutive-month smoothing. Captured from TALOS Pricing Calculator v9 (replaces v8). v9 verified to reproduce v8 exactly ($0.811182/conversation, $2,071.18/mo for 50 users × 20 conversations, Document Search only). History ×7 vs ×15-exchanges quirk preserved for parity and flagged for technical review. (jcardinal)
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
| [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
|
|
9
9
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
10
10
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
|
|
11
|
-
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/
|
|
11
|
+
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/fieldsConfig/index.ts |
|
|
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-29
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/surface/useFetchSurfaceMeta.ts
|
|
@@ -16,11 +16,13 @@ files:
|
|
|
16
16
|
- toga25-supply/src/surface/SurfaceSection.tsx
|
|
17
17
|
- toga25-supply/src/surface/resolve.ts
|
|
18
18
|
- toga25-supply/src/surface/types.ts
|
|
19
|
-
- toga25-supply/src/surface/featureFlag.ts
|
|
20
19
|
- toga25-supply/src/surface/index.ts
|
|
21
20
|
- toga25-supply/src/pages/Login/LoginPage.tsx
|
|
22
21
|
- toga25-supply/src/pages/SalesOrders/SalesOrders.tsx
|
|
23
22
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
|
|
23
|
+
- toga25-supply/src/pages/Items/ItemsPage.tsx
|
|
24
|
+
- toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx
|
|
25
|
+
- toga25-supply/src/fieldsConfig/index.ts
|
|
24
26
|
related:
|
|
25
27
|
- ../../_underscore/features/surface-resolver.md
|
|
26
28
|
- meta-driven-table-data.md
|
|
@@ -29,14 +31,17 @@ related:
|
|
|
29
31
|
## What it is
|
|
30
32
|
|
|
31
33
|
The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from
|
|
32
|
-
`GET /v2/surfaces/
|
|
34
|
+
`GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported JSON field bundles. Lives in
|
|
33
35
|
`src/surface/` for now with a `TODO(blox)` to extract the generic machinery into `@agilant/toga-blox`
|
|
34
|
-
so all 2.0 apps inherit it.
|
|
36
|
+
so all 2.0 apps inherit it. Surface is now the **enforced, unconditional presentation path** (no
|
|
37
|
+
feature flag — see below). Backend: [surface-resolver](../../_underscore/features/surface-resolver.md).
|
|
35
38
|
|
|
36
39
|
## How it works
|
|
37
40
|
|
|
38
41
|
- **`useFetchSurfaceMeta`** — the single fetch-once/React-Query-cached choke point for a surface's
|
|
39
|
-
meta. Loading a different *record* into the same surface reuses cached meta.
|
|
42
|
+
meta. Loading a different *record* into the same surface reuses cached meta. Builds the URL as
|
|
43
|
+
`/surfaces/meta?slug=...` (query string), **not** `/surfaces/<slug>/meta` (the path form makes the
|
|
44
|
+
engine parse the slug as a record uuid → 404 EV-6).
|
|
40
45
|
- **`evaluateSurfaceRule`** — the Tier-1 rule evaluator: a **frozen `all/any/none` + `{field,op,value}`
|
|
41
46
|
grammar** that **throws on an unknown op** (generalized from the SalesOrders
|
|
42
47
|
`buildPatchedTenantFields`/`evaluateEnableRule`/`resolveFlag` helpers). Evaluated client-side against
|
|
@@ -47,25 +52,53 @@ so all 2.0 apps inherit it. Backend: [surface-resolver](../../_underscore/featur
|
|
|
47
52
|
- **theme/message resolvers** (`resolve.ts`) — token → CSS and ICU message rendering.
|
|
48
53
|
- **`SurfaceActionBar`/`SurfaceActions` + `SurfaceSection`** — generic renderers.
|
|
49
54
|
|
|
50
|
-
##
|
|
55
|
+
## Enforced, not flagged
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
Surface is the **unconditional** presentation path. The `VITE_SURFACE_ENABLED` / `SURFACE_ENABLED`
|
|
58
|
+
feature flag was **removed entirely** (`featureFlag.ts` deleted, all gating conditionals and the
|
|
59
|
+
legacy pre-Surface fallback branches at the wired screens removed). Legacy per-screen `FIELDS` config
|
|
60
|
+
+ rendering are deleted as each screen migrates — there is no dual-path fallback.
|
|
61
|
+
|
|
62
|
+
## What's wired
|
|
63
|
+
|
|
64
|
+
Login + sales-orders list + the sales-order modal + the **Items LIST** screen. The SalesOrder action
|
|
65
|
+
bar reproduces `orderViewFields.json` approve/deny/approvalWorkflow/viewLog/editOrder by delegating
|
|
66
|
+
to the existing `onActionClick`/`onOpenLog` handlers. TABLE-type surfaces stay on the existing
|
|
67
|
+
TableView pipeline via `tableViewSlug` (see [meta-driven-table-data](meta-driven-table-data.md)) —
|
|
68
|
+
the Surface layer references the TableView, never replaces it.
|
|
69
|
+
|
|
70
|
+
## Per-screen migration recipe (proven on SalesOrders + Items)
|
|
71
|
+
|
|
72
|
+
The screen-by-screen full-app refactor follows one recipe:
|
|
73
|
+
1. Seed the screen's surface(s) in a `Core/*SurfaceSeed.sql` — a TABLE surface that references the
|
|
74
|
+
screen's existing `TableView` id via `tableViewSlug`, plus a BUTTON_BAR/actions surface + its
|
|
75
|
+
Actions + Messages (e.g. Items: `items-list` TABLE → TableView id 9 / recordId 21, and
|
|
76
|
+
`items-list-actions` refresh/columns/newItem).
|
|
77
|
+
2. Rewire the page + its view-model to consume the surface bundle (`ItemsPage.tsx` /
|
|
78
|
+
`useItemsPageViewModel.tsx`).
|
|
79
|
+
3. **Delete** the legacy `FIELDS` config: the page's `*Fields.json` and its `fieldsConfig/index.ts`
|
|
80
|
+
entries.
|
|
81
|
+
Keep TABLE rendering on the existing TableView pipeline; only the chrome (actions/labels/visibility)
|
|
82
|
+
moves to Surface. A screen with no client/role/lang variance ships a single DEFAULT bundle (no
|
|
83
|
+
Client overrides), as Items did.
|
|
58
84
|
|
|
59
85
|
## Gotchas
|
|
60
86
|
|
|
61
|
-
- **
|
|
62
|
-
|
|
87
|
+
- **No feature flag, no fallback.** Surface is the enforced path; a missing/broken surface bundle
|
|
88
|
+
does not silently fall back to legacy JSON — that path is deleted per screen as it migrates.
|
|
89
|
+
- **Endpoint is query-string form** — `GET /v2/surfaces/meta?slug=...`, not `/surfaces/<slug>/meta`
|
|
90
|
+
(the path form 404s EV-6).
|
|
63
91
|
- **Tier-1 only on the client.** Business-logic gates (Tier-2) arrive as resolved booleans from the
|
|
64
92
|
backend (`meta.surface`); never re-encode business rules in the FE evaluator.
|
|
65
93
|
- **`tsc` not yet run** this session (the private `@agilant/toga-blox` registry needs npm creds);
|
|
66
94
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
67
95
|
|
|
68
96
|
## Change history
|
|
97
|
+
- 2026-06-29 — DECISION: Surface is now enforced, not flagged — removed `VITE_SURFACE_ENABLED`/
|
|
98
|
+
`SURFACE_ENABLED` (deleted `featureFlag.ts` + all gating/legacy-fallback branches). Fixed
|
|
99
|
+
`useFetchSurfaceMeta` to the query-string endpoint `/surfaces/meta?slug=` (path form 404'd EV-6).
|
|
100
|
+
Migrated the Items LIST screen (second proof of the per-screen recipe; deleted `itemsPageFields.json`
|
|
101
|
+
+ its fieldsConfig entries). (jcardinal)
|
|
69
102
|
- 2026-06-25 — Built `src/surface/` (fetch hook, frozen Tier-1 rule evaluator, action registry, theme/
|
|
70
103
|
message resolvers, action-bar + section renderers); wired login + SalesOrders list + SO modal behind
|
|
71
104
|
the OFF-by-default `SURFACE_ENABLED` flag. Generic machinery destined for toga-blox. (jcardinal)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -24,7 +24,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
24
24
|
- **saml** (SAML SSO Gateway) — 3 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
25
25
|
- **toga2-view** (TOGa View Frontend) — 4 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
26
26
|
- **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
|
|
27
|
-
- **talos** (TOGa IQ) —
|
|
27
|
+
- **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
28
28
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
29
29
|
- **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
30
30
|
- **toga2-commerce** (TOGa Commerce) — 6 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
package/package.json
CHANGED