toga-ai 1.0.257 → 1.0.259

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.
@@ -36,10 +36,13 @@ auto-created.
36
36
  ## How it works
37
37
 
38
38
  ### Wiring overview
39
- - **`/sso/initiate`** (`mvc/sso/initiate/get.php`) — guards `isLoggedIn`, reads `config[saml]`,
40
- calls `App_Sso::initiate([...])` and `App_MVC::routeTo($url); exit;`. Initiation now uses the
41
- reusable 1.0 [`App_Sso`](../../library/features/app-sso-initiation.md) library class (no longer a
42
- placeholder).
39
+ - **`/sso/initiate`** (`mvc/sso/initiate/get.php`) — guards `isLoggedIn`, reads `config[saml]`
40
+ for the IdP/ACS urls + `domain_uuid`, sources the **rotating shared secret from `Core.Parameters`
41
+ via `App_Auth::currentApiSecret()`** (NOT from config — see below), calls `App_Sso::initiate([...])`
42
+ passing `'apiSecretAccessToken' => App_Auth::currentApiSecret()`, then `App_MVC::routeTo($url); exit;`.
43
+ Initiation uses the reusable 1.0 [`App_Sso`](../../library/features/app-sso-initiation.md) library
44
+ class (no longer a placeholder). The secret encrypts the RelayState — see the non-empty-token
45
+ gotcha below.
43
46
  - **`/sso`** (`mvc/sso/get.php`) — the handoff consumer (below).
44
47
  - **Core.Domains** — a fixed-uuid row registers this app's return domain so the gateway resolves
45
48
  client/environment/return URL: `uuid 2927bc15-e347-4358-a430-fb28f9446d27`, `clientId 1` (True),
@@ -59,18 +62,29 @@ auto-created.
59
62
  and that both decrypted values match a UUID regex.
60
63
  4. Load the active Client_True user: `WHERE uuid = ? AND isActive = 1`. No match → fail closed.
61
64
 
62
- ### Handoff key sourcing (`handoffKeys()` → `Core.Parameters`)
63
- The shared secret is **rotated regularly**, so the static `config.*.ini [saml]` token goes stale —
65
+ ### Handoff key sourcing (`handoffKeys()` → `Core.Parameters`) — used by BOTH SSO legs
66
+ The shared secret is **rotated regularly**, so any static `config.*.ini [saml]` copy goes stale —
64
67
  that staleness was exactly why `decryptHandoffValue()` returned `false` and production login failed
65
68
  (the payload decoded to the correct iv+base64 shape; it was a **key** mismatch, not a format bug).
66
- - `App_Auth::handoffKeys()` (new private helper) `SELECT \`key\`,\`value\` FROM Parameters WHERE
69
+ **Both SSO legs must source the secret from `Core.Parameters` at runtime** — the initiate leg
70
+ encrypts the RelayState with it, the consumer leg decrypts the handoff with it. A config-only copy
71
+ silently breaks SSO on the next rotation.
72
+ - `App_Auth::handoffKeys()` (private helper) `SELECT \`key\`,\`value\` FROM Parameters WHERE
67
73
  \`key\` IN ('API_SECRET_ACCESS_TOKEN','API_SECRET_ACCESS_TOKEN_PREVIOUS')` over the **`db_toga2core`**
68
74
  connection (2.0 PROD Core DB; `const CORE_DB = 'db_toga2core'`). Result is **cached per request**.
69
75
  `\`key\`` and `\`value\`` are SQL reserved words → **must be backticked**.
70
- - `decryptHandoffValue()` then tries the current key, falling back to previous (rotation window).
71
- Its **public signature is unchanged** — only `mvc/sso/get.php` calls it.
72
- - The old `[saml] api_secret_access_token` / `_previous` config values are now **dead** (removable).
73
- `[saml] true_client_uuid` is **still used** (`sso/get.php:80-84`) and stays.
76
+ - **Consumer leg** (`mvc/sso/get.php`): `decryptHandoffValue()` tries the current key, falling back
77
+ to previous (rotation window). Its **public signature is unchanged**.
78
+ - **Initiate leg** (`mvc/sso/initiate/get.php`): calls **`App_Auth::currentApiSecret()`** (public;
79
+ reuses the cached `handoffKeys()` lookup, returns the current `API_SECRET_ACCESS_TOKEN`) and passes
80
+ it to `App_Sso::initiate(['apiSecretAccessToken' => …])` to encrypt the RelayState.
81
+ - **Do NOT remove `[saml] api_secret_access_token` / `_previous` from config.** ⚠ An earlier capture
82
+ this session wrongly called these "dead/removable" — that guidance **broke SSO**: the initiate leg
83
+ still read the config token, so removing it made `App_Sso::initiate()` throw on an empty token
84
+ (`/login?error=1`). The fix moved the initiate leg to `Core.Parameters` too; the config copies are
85
+ now genuinely unused, but the durable rule is that the **only source of truth is `Core.Parameters`**.
86
+ - **`[saml] true_client_uuid` is still read from config** (`sso/get.php:80-84`) and **must NOT be
87
+ removed**. The IdP/ACS urls and `domain_uuid` also stay in config.
74
88
  - See [Core.Parameters key/value store](#coreparameters-key-value-store) for the table shape and
75
89
  which DB cluster it lives on.
76
90
 
@@ -115,6 +129,19 @@ removed.
115
129
  preloader and runs `App_Page::flushCapture()` (`ob_end_flush + flush`, `library/app/page.php:213`)
116
130
  **before** `body()/App_MVC::loadFile()` runs the mvc page. Fixed with the same
117
131
  `if (!headers_sent()) { http_response_code(401); }` guard (matches `App_MVC::routeTo`'s own check).
132
+ - **`App_Sso::initiate()` hard-requires a non-empty `apiSecretAccessToken`** (`library/app/sso.php:64-65`,
133
+ `empty()` guard → `InvalidArgumentException`; the token encrypts the RelayState). An empty/absent token
134
+ surfaces as a redirect to **`/login?error=1` ("Sign in failed. Please try again.")** — the initiate leg
135
+ catches the exception (`sso/initiate/get.php:30-34`) and routes to `/login?error=1`. This fails
136
+ **before** the IdP round-trip, so it never reaches the consumer's **"Authentication failed"**
137
+ (`tools_ssoFail`) page. **The two error UIs tell you which leg failed:** `/login?error=1` = initiate
138
+ (RelayState encryption / token), "Authentication failed" = consumer (decrypt). This was the
139
+ 2026-06-30 regression — removing the config token per the (wrong) earlier note made initiate throw.
140
+ - **Both legs depend on `db_toga2core` + a populated `Core.Parameters` token.** If `[database_toga2core]`
141
+ is not configured, `currentApiSecret()`/`handoffKeys()` runs `App_Database::query` against an
142
+ unregistered alias and **throws**. Because initiate only catches `InvalidArgumentException`/`RuntimeException`,
143
+ that surfaces as a **500** — not the login banner. **Diagnostic:** a 500 at initiate = missing
144
+ `db_toga2core`; `/login?error=1` = empty/absent `API_SECRET_ACCESS_TOKEN`.
118
145
  - **No app-side replay defense.** The handoff token carries no nonce/timestamp the app verifies.
119
146
  Recommend the gateway embed `iat` + `jti`.
120
147
 
@@ -140,8 +167,11 @@ from the 1.0 prod-cluster and from the 2.0 client cluster that backs `db_true`/`
140
167
  `db_toga2core`, dbname `Core`, the prod core cluster host (`production-core-cluster…us-west-2…`),
141
168
  needed in **each** `config.*.ini` so the handoff keys can be read at runtime (developer is adding
142
169
  creds). `[saml]`: `true_client_uuid` (still used), `client_authentication_uuid`, `domain_uuid`,
143
- plus the IdP/ACS urls used by initiation; `[internal]` `dev_mode`. The old `[saml]
144
- api_secret_access_token` / `_previous` are now **dead** (superseded by `Core.Parameters`).
170
+ plus the IdP/ACS urls used by initiation; `[internal]` `dev_mode`. `[saml] api_secret_access_token` /
171
+ `_previous` are **superseded by `Core.Parameters`** (the only source of truth for BOTH legs) — but do
172
+ **not** treat them as freely removable: removing the config token while the initiate leg still read it
173
+ is what caused the 2026-06-30 `/login?error=1` regression. `[saml] true_client_uuid` is **still read
174
+ from config** and must stay.
145
175
 
146
176
  **Secret location.** Rotating secrets are now sourced from `Core.Parameters` **at runtime** rather
147
177
  than static config — an improvement over the old committed `config.production.ini [saml]` tokens.
@@ -150,6 +180,7 @@ remains (already noted in tools knowledge) — **flag for rotation.** Document *
150
180
  never the values.
151
181
 
152
182
  ## Change history
183
+ - 2026-06-30 — Fixed SSO "Sign in failed" (`/login?error=1`) regression and **corrected the earlier wrong "config tokens are removable" note**. The prior fix moved only the consumer/decrypt leg to `Core.Parameters` and advised the `[saml] api_secret_access_token` was removable — but the **initiate** leg (`mvc/sso/initiate/get.php`) still read it from config, so removing it made `App_Sso::initiate()` throw on an empty token (`library/app/sso.php:64-65`) → `/login?error=1`. Fix: **both** legs now source the rotating secret from `Core.Parameters` — added public `App_Auth::currentApiSecret()` (reuses cached `handoffKeys()`), and `sso/initiate/get.php` passes it instead of the config value. Documented the non-empty-token requirement, the `/login?error=1` (initiate) vs "Authentication failed" (consumer) leg distinction, the `true_client_uuid`-stays-in-config rule, and the `db_toga2core`-missing→500 diagnostic. `php -l` clean on both files. (jcardinal)
153
184
  - 2026-06-30 — Fixed prod SSO login failure: handoff decrypt keys now sourced from `Core.Parameters` (rotated `API_SECRET_ACCESS_TOKEN`/`_PREVIOUS`) via new `App_Auth::handoffKeys()` over `db_toga2core` (per-request cached, current→previous fallback), instead of the stale committed `config [saml]` tokens — the stale key was the root cause. Added `[database_toga2core]` connection requirement to every config and documented the AES-256-CBC crypto format. Fixed `tools_ssoFail()` 500→intended 401: `http_response_code(401)` fataled on headers-already-sent (preloader flushes before mvc page runs); guarded with `!headers_sent()`. (jcardinal)
154
185
  - 2026-06-26 — Wired up real SSO initiation via the new 1.0 `App_Sso` library class (`/sso/initiate`), replacing the `initiation_url` placeholder; registered the fixed-uuid Core.Domains return row (dbchanges2 `2026-06-26a`); fixed the `session_regenerate_id` headers-already-sent fatal (guarded with `!headers_sent()`) and documented the mid-render failure-path limitation; collapsed home → `/login` to one-step sign-in; noted prod secrets live in `config.production.ini [saml]` (jcardinal)
155
186
  - 2026-06-25 — Built App_Auth: SAML `?saml=` handoff consumer with dual-key decrypt, hash_equals client-uuid check, fail-closed 401, persona-cached session (HttpOnly+SameSite=Lax), and a double-gated dev bypass. Initiation + replay defense left as open items (jcardinal)
@@ -14,40 +14,41 @@ updated: 2026-06-30
14
14
  # Session: talos-pricing-platform
15
15
  **Date:** 2026-06-30
16
16
  **Project/Repo:** tools (1.0) + worker2 (2.0) + dbchanges2 (2.0 Team DB)
17
- **Task:** Build a data-driven Talos pricing platform (Team-DB schema + tools onboarding/dashboard UI + worker2 cron automation) to replace the Excel calculator; then fix tools SSO login that was failing in production.
17
+ **Task:** Build a data-driven Talos pricing platform (Team-DB schema + tools onboarding/dashboard UI + worker2 cron automation) to replace the Excel calculator; then fix tools SSO login (both consumer and initiate legs) that was failing in production.
18
18
 
19
19
  ---
20
20
 
21
21
  ## What WORKED
22
- - **Schema (Team DB):** `dbchanges2/Team/2026-06-29a - TalosPricingTables.sql` — 9 tables created, SQL-reviewed (0 critical), all 7 important review fixes applied (dtUpdated on fact tables, DECIMAL(6,4) margins, isFeature* boolean naming, DECIMAL(14,8) calibration factors, full column comments, band-range CHECK). Tables live in the **Team** DB; `dbchanges2/Team/` is the migration folder (Team-DB migrations use unqualified table names).
23
- - **worker2 automation:** `worker2/Worker/Talos/Pricing.php` (`_Worker_Talos_Pricing`) + `worker2/Database/TalosPricingCrons.sql` — `php -l` clean. Three monthly cron actions: ImportAwsActuals (Cost Explorer), RecomputeMargins (calibration + margin/streak/recommendation), MonthlyReport (PhpSpreadsheet xlsx + templated email). initialize() registers Team DB via `_underscore::DB_TEAM` (mirrors `_Worker_Team_Transcripts`).
24
- - **tools UI:** nav folder + 6 pages + estimator + CSS, all `php -l` clean. `App_Talos_Estimator` (`tools/_/app/talos/estimator.php`), onboarding get/post, pricing dashboard, usage benchmarks, cost-factors get/post. Live JS estimate + recommended per-band fees on the onboarding form.
25
- - **SSO 500 fix:** `tools/mvc/sso/get.php` `tools_ssoFail()` — added `if (!headers_sent())` guard around `http_response_code(401)`. `php -l` clean. Root cause confirmed: `App_FrameworkIndex::render()` flushes the preloader buffer (`page.php:213`) BEFORE `body()`/`loadFile()`, so headers are already sent when an mvc page runs.
26
- - **SSO key sourcing:** `tools/_/app/auth.php` `App_Auth::decryptHandoffValue()` rewritten to read `API_SECRET_ACCESS_TOKEN` / `API_SECRET_ACCESS_TOKEN_PREVIOUS` from `Core.Parameters` (rotated there) via new cached `handoffKeys()` + `const CORE_DB='db_toga2core'`. `php -l` clean.
27
- - **Crypto format confirmed:** `App_String::encryptWithKey/decryptWithKey` (library/app/string.php:1075-1094) = AES-256-CBC, `base64(iv . base64ciphertext)`, interoperable with 2.0 `_String`. The user's real `?saml=` payload decoded to exactly that shape — proving the SSO failure was a stale-key/config mismatch, not a format bug.
28
- - **Knowledge captured + pushed twice:** pricing platform (6 docs incl. an approved ELEVATED architecture section in `2.0/apps/talos/architecture.md`) and SSO fixes (2 tools feature-doc updates). Both PUSHED to `_main`.
22
+ - **Schema (Team DB):** `dbchanges2/Team/2026-06-29a - TalosPricingTables.sql` — 9 tables, SQL-reviewed (0 critical), all 7 important review fixes applied (dtUpdated on fact tables, DECIMAL(6,4) margins, isFeature* booleans, DECIMAL(14,8) calibration factors, column comments, band-range CHECK). `dbchanges2/Team/` is the Team-DB migration folder (unqualified table names).
23
+ - **worker2 automation:** `worker2/Worker/Talos/Pricing.php` (`_Worker_Talos_Pricing`) + `worker2/Database/TalosPricingCrons.sql` — `php -l` clean. 3 monthly crons: ImportAwsActuals (Cost Explorer), RecomputeMargins (calibration + margin/streak/recommendation), MonthlyReport (PhpSpreadsheet xlsx + EmailTemplate). initialize() registers Team DB via `_underscore::DB_TEAM`.
24
+ - **tools UI:** nav folder + 6 pages + `App_Talos_Estimator` + CSS, all `php -l` clean. Onboarding get/post (live JS estimate + recommended per-band fees), pricing dashboard, usage benchmarks, cost-factors get/post.
25
+ - **SSO 500 fix (consumer leg):** `tools/mvc/sso/get.php` `tools_ssoFail()` — added `if (!headers_sent())` guard around `http_response_code(401)`. Root cause: `App_FrameworkIndex::render()` flushes the preloader buffer (`page.php:213`) BEFORE `body()`/`loadFile()`, so headers are already sent when any mvc page runs.
26
+ - **SSO key sourcing → Core.Parameters (BOTH legs):** `tools/_/app/auth.php` — `decryptHandoffValue()` (consumer) reads keys via new cached `handoffKeys()`; added public `currentApiSecret()` for the initiate leg; `const CORE_DB='db_toga2core'`. `tools/mvc/sso/initiate/get.php` now passes `'apiSecretAccessToken' => App_Auth::currentApiSecret()`. Both `php -l` clean.
27
+ - **Crypto format confirmed:** `App_String::encryptWithKey/decryptWithKey` (library/app/string.php:1075-1094) = AES-256-CBC, `base64(iv . base64ciphertext)`, interoperable with 2.0 `_String`. The real `?saml=` payload decoded to exactly that shape — proving SSO failure was a key/config issue, not a format bug.
28
+ - **"Sign in failed" root cause found + fixed:** that banner is `login/get.php` `?error=1`, set ONLY by `sso/initiate/get.php:33,38` when `App_Sso::initiate()` throws. The initiate leg still read `api_secret_access_token` from config; after it was removed from config (per earlier wrong advice), `App_Sso::initiate()` (library/app/sso.php:64-65, empty() guard) threw InvalidArgumentException → `/login?error=1`. Fixed by sourcing the secret from Core.Parameters on the initiate leg too.
29
+ - **Knowledge captured + pushed (4 times):** pricing platform (6 docs incl. approved ELEVATED `2.0/apps/talos/architecture.md` section), SSO fixes (`saml-sso-auth.md` + `mvc-data-access-patterns.md`), and the SSO-initiate correction (`saml-sso-auth.md` again). All PUSHED to `_main`.
29
30
 
30
31
  ## What did NOT work — DO NOT RETRY THESE
31
- - **`App_Database::escape()` does NOT exist** — the 1.0 escaper is `App_Database::sqlEscape()`. Using `::escape` fatals.
32
- - **Passing `App_Database::query(...)` inline to `buildArrayOfRows()`/`fetchOne()`** triggers PHP "Only variables should be passed by reference" (both take `&$res`); with display_errors on it renders into output. MUST assign the query result to a variable first (the login post.php does this).
33
- - **Reading SSO tokens from `config.production.ini` `[saml]`** — they go stale because the gateway rotates `API_SECRET_ACCESS_TOKEN`; that stale token was why decrypt returned false and login failed. Do not rely on config tokens; use Core.Parameters.
34
- - **Calling `http_response_code()` / `header()` / `session_regenerate_id()` from any page loaded inside `body()`** — the preloader buffer is already flushed, so it fatals/no-ops. Always guard with `headers_sent()`.
35
- - **Writing `[database_team]`/`[database_toga2core]` into config with a guessed host** — avoided deliberately; the 2.0 Core/Team host (`production-core-cluster…us-west-2`, from config.alpha.ini `[database_prod_toga2core]`) differs from the 1.0 prod-cluster and the 2.0 client cluster (`reader1.client.database.togahub.com` = db_true). Don't assume they're the same host.
32
+ - **Removing `api_secret_access_token` from `config.production.ini` `[saml]`** — broke SSO. The INITIATE leg (`sso/initiate/get.php`) reads it from config to encrypt the RelayState; empty → `App_Sso::initiate()` throws `InvalidArgumentException('...missing required parameter: apiSecretAccessToken')` → `/login?error=1` "Sign in failed". DO NOT keep the rotating secret in config; it now comes from Core.Parameters on both legs.
33
+ - **`App_Database::escape()`** does NOT exist — use `App_Database::sqlEscape()`.
34
+ - **Passing `App_Database::query(...)` inline to `buildArrayOfRows()`/`fetchOne()`** (both take `&$res`) — triggers PHP "Only variables should be passed by reference" (renders with display_errors on). Assign the query result to a variable first.
35
+ - **Calling `http_response_code()`/`header()`/`session_regenerate_id()` from a page loaded inside `body()`** — preloader buffer already flushed → fatals/no-ops. Guard with `headers_sent()`.
36
+ - **Writing `[database_team]`/`[database_toga2core]` with a guessed host** — avoided. The 2.0 Core/Team host is `production-core-cluster…us-west-2` (from config.alpha.ini `[database_prod_toga2core]`), distinct from the 1.0 prod-cluster and the 2.0 client cluster (`reader1.client.database.togahub.com` = db_true).
36
37
 
37
38
  ## Not tried yet (candidates for next session)
38
- - Seeding/calibrating `TalosCostFactors` from the real Langfuse aggregates (estimator currently ships placeholder DEFAULTS).
39
- - Wiring the xlsx as an actual email attachment — `_Email` attachment support is unconfirmed (`MonthlyReport` writes xlsx to a temp path; email body has the HTML table meanwhile).
39
+ - Seeding/calibrating `TalosCostFactors` from real Langfuse aggregates (estimator ships placeholder DEFAULTS).
40
+ - Wiring xlsx as an email attachment — `_Email` attachment support unconfirmed (MonthlyReport writes xlsx to temp; email body has HTML table).
40
41
  - Confirming the AWS Cost Explorer cost-allocation tag key (worker assumes `Client`).
41
- - Creating the leadership `EmailTemplates` record in Client_True and replacing the placeholder `REPORT_EMAIL_TEMPLATE` UUID in `_Worker_Talos_Pricing`.
42
- - Verifying the live SSO login end-to-end after the `[database_toga2core]` connection is added + deployed.
42
+ - Creating the leadership `EmailTemplates` record in Client_True and replacing the placeholder `REPORT_EMAIL_TEMPLATE` UUID.
43
+ - Live end-to-end SSO verification after `[database_toga2core]` is configured + deployed.
43
44
 
44
45
  ## Current file state
45
46
  | File | Status | Notes |
46
47
  |------|--------|-------|
47
48
  | dbchanges2/Team/2026-06-29a - TalosPricingTables.sql | created | 9 Talos* tables, reviewed+fixed. Apply to Team DB. |
48
- | worker2/Worker/Talos/Pricing.php | created | `_Worker_Talos_Pricing`; lint clean. REPORT_EMAIL_TEMPLATE is a placeholder UUID. |
49
+ | worker2/Worker/Talos/Pricing.php | created | `_Worker_Talos_Pricing`; lint clean. REPORT_EMAIL_TEMPLATE placeholder UUID. |
49
50
  | worker2/Database/TalosPricingCrons.sql | created | 3 Core.CronJobs seeds (4th of month). Not yet inserted. |
50
- | tools/_/app/nav.php | modified | Added "Talos Pricing" folder (4 links; Cost Factors narrowed to technical personas). |
51
+ | tools/_/app/nav.php | modified | "Talos Pricing" folder (4 links; Cost Factors narrowed to technical personas). |
51
52
  | tools/_/app/talos/estimator.php | created | `App_Talos_Estimator`; placeholder DEFAULT factors. |
52
53
  | tools/mvc/talos/onboarding/get.php + post.php | created | Form + live estimate; writes TalosClients + TalosPricingBands in a txn. |
53
54
  | tools/mvc/talos/pricing/get.php | created | Read-only margin/recommendation dashboard (db_team). |
@@ -55,21 +56,23 @@ updated: 2026-06-30
55
56
  | tools/mvc/talos/factors/get.php + post.php | created | Technical-only cost-factor editor (upsert TalosCostFactors). |
56
57
  | tools/assets/css/style.css | modified | Appended .pricing-table / .badge / form-grid / estimate-readout styles. |
57
58
  | tools/mvc/sso/get.php | modified | headers_sent() guard in tools_ssoFail() (500→graceful 401). |
58
- | tools/_/app/auth.php | modified | decryptHandoffValue() reads keys from Core.Parameters via db_toga2core; added handoffKeys() + CORE_DB const. |
59
+ | tools/_/app/auth.php | modified | decryptHandoffValue() + currentApiSecret() read tokens from Core.Parameters via db_toga2core; handoffKeys() cache; CORE_DB const. |
60
+ | tools/mvc/sso/initiate/get.php | modified | apiSecretAccessToken now from App_Auth::currentApiSecret() (Core.Parameters), not config. |
59
61
 
60
62
  ## Decisions made
61
- - **Pricing model:** flat monthly ORG FEE (the adjustable margin lever) + per-user fee that STEPS by user-count band; band rate schedule locked at signing. On a band crossing the per-user fee auto-steps to the agreed rate, THEN the org fee is adjusted to restore margin. Rejected: pure per-user banded (no lever) and both-vary.
62
- - **Cost methodology:** calibrate Langfuse→AWS (monthly factor = AWS_actual / Langfuse_costCorrected; new clients use a global blended factor). Rejected: AWS-only and Langfuse-only. Always use Langfuse's cache-corrected cost (it undercounts cache badly).
63
- - **Table location:** the **Team** DB (`_underscore::DB_TEAM`) — what the user called "Core.Teams". Tenant link to Core.Clients is a plain indexed column, NOT a hard FK (Team DB may be a separate instance).
63
+ - **Pricing model:** flat monthly ORG FEE (adjustable margin lever) + per-user fee that STEPS by user-count band; band rate schedule locked at signing; on a band crossing the per-user fee auto-steps, THEN org fee is adjusted. Rejected: pure per-user banded; both-vary.
64
+ - **Cost methodology:** calibrate Langfuse→AWS (monthly factor = AWS_actual / Langfuse_costCorrected; new clients use a global blended factor). Always use cache-corrected Langfuse cost. Rejected: AWS-only; Langfuse-only.
65
+ - **Table location:** Team DB (`_underscore::DB_TEAM`). Tenant link to Core.Clients is a plain indexed column, NOT a hard FK (Team DB may be a separate instance).
64
66
  - **Onboarding writes:** direct DB write from the 1.0 tools app (not via api2) for speed.
65
- - **SSO keys from Core.Parameters:** sourced at runtime (rotated) instead of static config, via a `db_toga2core` connection alias; keeps current→previous rotation fallback. Rejected: continuing to read config `[saml]` tokens.
67
+ - **SSO secret source:** the rotating `API_SECRET_ACCESS_TOKEN` (+ `_PREVIOUS`) lives ONLY in `Core.Parameters`, read by BOTH SSO legs (initiate encrypts RelayState; consumer decrypts current→previous) via the `db_toga2core` connection. Config copies go stale on rotation and silently break SSO. `[saml] true_client_uuid` and the IdP URLs STAY in config. Rejected: config-stored tokens.
66
68
 
67
69
  ## Blockers
68
- - **SSO login still fails until the `[database_toga2core]` connection is added to the tools configs and tools is redeployed** — the `db_toga2core` alias is referenced by `App_Auth::CORE_DB` but no `[database_toga2core]` section exists in `config.production.ini` yet (developer is handling creds). Section template: hostname `production-core-cluster.cluster-clwbyqvxdm4q.us-west-2.rds.amazonaws.com`, username awsroot, dbname `Core`.
69
- - All Talos UI pages need the `[database_team]` connection (db_team → Team DB) before they render data (also developer-supplied creds).
70
+ - **SSO login still fails until `[database_toga2core]` is added to the tools configs AND tools is redeployed.** `App_Auth::CORE_DB='db_toga2core'` is referenced by both SSO legs, but no `[database_toga2core]` section exists in `config.production.ini` yet (developer handling creds). Template: hostname `production-core-cluster.cluster-clwbyqvxdm4q.us-west-2.rds.amazonaws.com`, username awsroot, dbname `Core`. DIAGNOSTIC: a **500 at initiate** = db_toga2core unconfigured (query against unregistered alias throws, uncaught); **/login?error=1** = empty/absent token in Core.Parameters.
71
+ - `Core.Parameters.API_SECRET_ACCESS_TOKEN` must be populated and match the gateway; `[saml] true_client_uuid` must remain in config (consumer leg sso/get.php:80-84 reads it).
72
+ - All Talos UI pages need the `[database_team]` connection (db_team → Team DB) before they render data.
70
73
 
71
74
  ## Exact next step
72
- > Add the `[database_toga2core]` section (alias db_toga2core, dbname=Core, host=production-core-cluster…us-west-2) to `tools/config.production.ini` (and the dev configs you test with), deploy tools, then attempt a fresh SSO login at https://tools.togatech.com/sso to confirm `decryptHandoffValue()` succeeds against the live rotated key.
75
+ > Add `[database_toga2core]` (alias db_toga2core, dbname=Core, host=production-core-cluster…us-west-2) to `tools/config.production.ini` (and the dev configs you test with), confirm `Core.Parameters.API_SECRET_ACCESS_TOKEN` is set and `[saml] true_client_uuid` is still present, deploy tools, then click "Sign in with SSO" at https://tools.togatech.com — expect redirect to the IdP (not `/login?error=1`); a 500 at initiate means db_toga2core isn't wired.
73
76
 
74
77
  ---
75
78
  _Saved by /session-save on 2026-06-30_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.257",
3
+ "version": "1.0.259",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",