toga-ai 1.0.353 → 1.0.355

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.
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-13
9
+ updated: 2026-07-16
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - tools/mvc/talos/kb-documents/get.php
@@ -97,36 +97,100 @@ false success.
97
97
  ### `POST /talos/knowledge-bases` — create a new KB (synchronous provisioning)
98
98
 
99
99
  Admin action that provisions a brand-new Talos knowledge base end to end in **one
100
- synchronous request**: creates the S3 prefix, creates the Bedrock KB and **waits for it to
101
- reach `ACTIVE`**, creates the Bedrock data source (`MANAGED_KNOWLEDGE_BASE_CONNECTOR`), and
102
- inserts rows across **5 systems with no shared transaction**: `Team.KnowledgeBases` (`db_team`,
103
- Team MySQL cluster), `Client_True.VectorIndexes` (`db_true`, **Client MySQL cluster** — not the
104
- Team cluster), and 2 Talos **Postgres** rows (`public.knowledge_bases` +
105
- `public.assistant_knowledge_bases`, the latter linking a hard-coded assistant id). Step order:
106
- validate name (≤255) + derive slug via **`App_Talos_S3::slugify()`** (kebab-case, `iconv` ASCII
107
- translit; rejects a duplicate slug — `slug` is UNIQUE); create 3 S3 folder markers
108
- `development-team/{slug}/{approved,archive,upload}/` via **`App_Talos_S3::putFolderMarker()`**;
109
- create the Bedrock KB and wait for `ACTIVE`; create the data source; then the DB inserts. This flow
110
- **stays synchronous by design** — moving it to worker2/SQS async was explicitly rejected;
111
- the fix path is to raise timeouts, never to background the work.
100
+ synchronous request**. The KB is a **classic VECTOR knowledge base backed by Amazon S3
101
+ Vectors** (the canonical design — see the architecture note below), and the flow is now
102
+ **9 steps** across systems with **no shared transaction**:
103
+
104
+ 1. **validate slug** (≤255) + derive via **`App_Talos_S3::slugify()`** (kebab-case, `iconv`
105
+ ASCII translit; rejects a duplicate slug — `slug` is UNIQUE).
106
+ 2. **S3 folders** — 3 markers `development-team/{slug}/{approved,archive,upload}/` via
107
+ **`App_Talos_S3::putFolderMarker()`**.
108
+ 3. **S3 Vectors store** — create a dedicated vector bucket + index via
109
+ `App_Talos_Bedrock::createVectorStore()`, capturing the authoritative **`indexArn`**.
110
+ 4. **VECTOR KB** — `createKnowledgeBase($bedrock, $slug, $indexArn)` (VECTOR + `S3_VECTORS`
111
+ storage).
112
+ 5. **wait for `ACTIVE`** (`waitForKnowledgeBaseActive`).
113
+ 6. **S3 data source** — plain `S3` connector + `BEDROCK_DATA_AUTOMATION` parsing.
114
+ 7. **`Team.KnowledgeBases`** (`db_team`, Team MySQL cluster).
115
+ 8. **`Client_True.VectorIndexes`** (`db_true`, **Client MySQL cluster** — not the Team cluster).
116
+ 9. **2 Talos Postgres rows** (`public.knowledge_bases` + `public.assistant_knowledge_bases`,
117
+ the latter linking a hard-coded assistant id).
118
+
119
+ The create branch sets `set_time_limit(CREATE_MAX_EXECUTION_SECONDS = 600)` up top because the
120
+ flow blocks on multi-minute AWS waits (S3 Vectors index readiness + KB `ACTIVE`, up to ~300s).
121
+ This only prevents the PHP fatal — it does **not** lift the EB Apache `ProxyTimeout` ceiling
122
+ (handled separately in `.platform`, see below). This flow **stays synchronous by design** —
123
+ moving it to worker2/SQS async was explicitly rejected; the fix path is to raise timeouts,
124
+ never to background the work.
125
+
126
+ ### Bedrock KB architecture — VECTOR + S3 Vectors (canonical)
127
+
128
+ The canonical Talos KB config (from **`development-team-dunn`**, confirmed identical across
129
+ `development-team-ninja-rmm` and `development-team-warehouse`) is a **classic VECTOR** KB, **not**
130
+ the fully-MANAGED type:
131
+ - **KnowledgeBase:** `knowledgeBaseConfiguration.type = VECTOR`;
132
+ `vectorKnowledgeBaseConfiguration.embeddingModelArn =
133
+ arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0`;
134
+ `embeddingModelConfiguration.bedrockEmbeddingModelConfiguration.embeddingDataType = FLOAT32`;
135
+ `storageConfiguration.type = S3_VECTORS` pointing at the per-KB S3 Vectors index ARN.
136
+ - **S3 Vectors index** (one dedicated vector bucket + index **per KB**): `dimension = 1024`,
137
+ `dataType = float32`, `distanceMetric = euclidean`,
138
+ `metadataConfiguration.nonFilterableMetadataKeys = [AMAZON_BEDROCK_TEXT, AMAZON_BEDROCK_METADATA]`,
139
+ encryption `sseType = AES256` (SSE-S3). Vector bucket name mirrors the console:
140
+ `bedrock-knowledge-base-{slug}`; index name `bedrock-knowledge-base-default-index`.
141
+ - **DataSource:** `dataSourceConfiguration.type = S3` (plain S3 connector);
142
+ `s3Configuration.bucketArn = arn:aws:s3:::togaiq`;
143
+ `inclusionPrefixes = ["development-team/{slug}/approved/"]`;
144
+ `vectorIngestionConfiguration.parsingConfiguration.parsingStrategy = BEDROCK_DATA_AUTOMATION`;
145
+ `dataDeletionPolicy = DELETE`.
146
+
147
+ Account `654654170868`, region `us-east-1`. Verified working end-to-end in production
148
+ (2026-07-16).
112
149
 
113
150
  The handler is **best-effort with no rollback**: on a mid-flow failure it stops and returns
114
151
  a `steps[]` report **without tearing down** already-created AWS resources (see Gotchas —
115
152
  orphaned KB).
116
153
 
117
- **`App_Talos_Bedrock`** (`_/app/talos/bedrock.php`) — Bedrock Agent client:
154
+ **`App_Talos_Bedrock`** (`_/app/talos/bedrock.php`) — Bedrock Agent + S3 Vectors clients:
155
+ - **`ROLE_ARN`** — the single **shared** execution role
156
+ `arn:aws:iam::654654170868:role/AmazonBedrockExecutionRoleForKnowledgeBase-talos`, hard-coded
157
+ (replaced the old per-KB `_tonej` role). See the IAM prerequisite below.
158
+ - `s3VectorsClient()` — `Aws\S3Vectors\S3VectorsClient`, api version `2025-07-15`, using the same
159
+ `[talos]` credential resolution as `App_Talos_S3::client()`.
160
+ - `vectorBucketName()` — derives `bedrock-knowledge-base-{slug}` and enforces the S3 Vectors
161
+ bucket-name length limit (3–63 chars).
162
+ - `createVectorStore()` — creates the vector bucket + index sized to Titan v2 (dim 1024,
163
+ float32, euclidean), then **polls `getIndex` for the authoritative index ARN** with a short
164
+ `NotFound` retry; returns an actionable error on `Conflict`/`AlreadyExists` from an orphaned
165
+ prior attempt.
166
+ - `createKnowledgeBase($bedrock, string $slug, string $indexArn)` — creates the VECTOR KB with
167
+ `S3_VECTORS` storage (signature now takes the index ARN).
118
168
  - `waitForKnowledgeBaseActive()` polls `getKnowledgeBase` every `WAIT_INTERVAL_SECONDS` (3s)
119
- up to **`WAIT_MAX_SECONDS` = 300** (raised from 90; MANAGED KBs were observed taking ~3 min
120
- to leave `CREATING`). Exits immediately on `ACTIVE`/`FAILED`, so fast activations are
121
- unaffected. 300s sits well under the 900s Apache/PHP ceiling.
122
- - `createDataSource()` — `connectorParameters` for `managedKnowledgeBaseConnectorConfiguration`
123
- is a **`Document` shape (free-form JSON)** in the AWS PHP SDK model and **must be passed as a
124
- native PHP array** so the SDK serializes a JSON *object*. Passing a pre-`json_encode()`d
125
- **string** fails `ValidationException "Invalid connector parameters format"`. Working object
126
- shape (matches the craftex data source):
127
- `{type:S3, filterConfiguration:{maxFileSizeInMegaBytes:"500",
128
- inclusionPrefixes:["development-team/{slug}/approved/"]}, connectionConfiguration:{bucketName:togaiq,
129
- bucketOwnerAccountId:654654170868, bucketArn:arn:aws:s3:::togaiq}, aclEnabled:false, version:"1"}`.
169
+ up to **`WAIT_MAX_SECONDS` = 300**. Exits immediately on `ACTIVE`/`FAILED`. 300s sits well
170
+ under the 900s Apache/PHP ceiling.
171
+ - `createDataSource()` — plain **`S3`** connector + `BEDROCK_DATA_AUTOMATION` parsing (rewritten
172
+ from the old `MANAGED_KNOWLEDGE_BASE_CONNECTOR`). See the architecture note above for the exact
173
+ shape.
174
+ - The vendored `aws/aws-sdk-php` (`^3.387`) already ships both the `S3Vectors` and `BedrockAgent`
175
+ clients and the `S3_VECTORS` `storageConfiguration` shape (verified against the SDK model). The
176
+ now-dead `BUCKET_NAME` / `BUCKET_OWNER_ACCOUNT_ID` constants were removed.
177
+
178
+ **IAM prerequisites (one-time, manual — the app does NOT provision IAM):**
179
+ - **Shared execution role** `AmazonBedrockExecutionRoleForKnowledgeBase-talos` — one role for
180
+ ALL Talos KBs (decision: do **not** create a role+policies per KB, which would require granting
181
+ the web app IAM write powers — rejected as a security expansion). Has 4 inline policies
182
+ mirroring dunn's but **wildcarded**: s3vectors data-plane on
183
+ `bucket/bedrock-knowledge-base-*/index/*`; `s3:GetObject` on
184
+ `togaiq/development-team/*/approved/*` (+ `s3:ListBucket` on `togaiq`); `bedrock:InvokeModel`
185
+ on `titan-embed-text-v2:0` (+ marketplace); BDA `GetDataAutomationStatus` +
186
+ `InvokeDataAutomationAsync`. Trust policy: `bedrock.amazonaws.com` assume-role with
187
+ `SourceAccount 654654170868` and `SourceArn knowledge-base/*`.
188
+ - **App credential grant** — the `[talos]` app credentials are IAM user
189
+ `arn:aws:iam::654654170868:user/bedrock-model-accessor`. It was granted (new inline policy
190
+ **`TalosKbProvisioning`**) `s3vectors:CreateVectorBucket/CreateIndex/GetIndex` and
191
+ `iam:PassRole` on the shared role. Its bedrock create actions were already covered by
192
+ `AmazonBedrockFullAccess` and S3 by `AmazonS3FullAccess`. **`s3vectors` is a SEPARATE service
193
+ NOT covered by `AmazonBedrockFullAccess`** — that was the real missing grant.
130
194
 
131
195
  ## Helpers
132
196
 
@@ -200,20 +264,23 @@ orphaned KB).
200
264
  - **php-fpm `request_terminate_timeout` can still kill the request.** If
201
265
  `/etc/php-fpm.d/www.conf`'s `request_terminate_timeout` is ever set to 60 it terminates the
202
266
  request regardless of Apache — it must be `0` or `>= 900`.
203
- - **`get-data-source` echoes `connectorParameters` back as a quoted STRING** even though the
204
- create input must be a JSON **object** — the GET representation is misleading and does NOT
205
- indicate the required input shape.
206
- - **KB create has no rollback → orphaned KB risk.** A failure at the data-source step leaves an
207
- orphaned Bedrock KB with **no DB rows** (`Team.KnowledgeBases`/`VectorIndexes`/Postgres never
208
- inserted). Blindly re-running with the same name creates a **duplicate** Bedrock KB (Bedrock
209
- allows same-name KBs with new ids). After a failed create: either
210
- `aws bedrock-agent delete-knowledge-base` the orphan and re-run clean, or finish the DB rows
211
- manually.
212
- - **Newest KBs use Bedrock's fully-MANAGED type** (`knowledgeBaseConfiguration.type=MANAGED`,
213
- `managedKnowledgeBaseConfiguration.embeddingModelType=MANAGED`) — **AWS owns the vector store**,
214
- so there is **no S3 Vectors index, no `embeddingModelArn`, and no `storageConfiguration`** to
215
- build. Config was replicated from the existing `development-team-craftex` KB. Account
216
- `654654170868`, region `us-east-1`, execution role `…ForKnowledgeBase_ofl5a`.
267
+ - **The Bedrock API does NOT auto-provision the S3 Vectors bucket/index** (the console does).
268
+ They must be created first (`s3vectors CreateVectorBucket` + `CreateIndex`) and the resulting
269
+ index ARN passed into `CreateKnowledgeBase`. `createVectorStore()` handles this before KB
270
+ creation.
271
+ - **`s3vectors` is a separate AWS service** — it is **not** covered by `AmazonBedrockFullAccess`.
272
+ The app credentials need explicit `s3vectors:*` grants (see IAM prerequisite above); this was
273
+ the real missing permission.
274
+ - **KB create has no rollback → orphaned KB risk.** A failure mid-flow can leave an orphaned
275
+ S3 Vectors bucket/index and/or Bedrock KB with **no DB rows** (`Team.KnowledgeBases`/
276
+ `VectorIndexes`/Postgres never inserted). Blindly re-running with the same slug hits
277
+ `Conflict`/`AlreadyExists` on the vector bucket (surfaced as an actionable error) or creates a
278
+ **duplicate** Bedrock KB (Bedrock allows same-name KBs with new ids). After a failed create:
279
+ delete the orphaned vector store + KB and re-run clean, or finish the DB rows manually.
280
+ - **Two prior KBs use the WRONG (fully-MANAGED) type.** `development-team-craftex` and
281
+ `development-team-toga-desk` were created by the earlier MANAGED code and were **intentionally
282
+ left as-is** (this session's scope was code-only). They should be **recreated later** to match
283
+ the canonical VECTOR + S3 Vectors design (dunn).
217
284
  - **KB-list source differs per page.** The new `/talos/knowledge-bases` page lists from
218
285
  `Team.KnowledgeBases` (`db_team`); `/talos/kb-documents` lists from live S3 folders. Don't
219
286
  assume one reflects the other.
@@ -222,15 +289,27 @@ orphaned KB).
222
289
  `Client_True.VectorIndexes` (Client cluster): `id, uuid`(UNIQUE)`, name, docSlug`(UNIQUE,
223
290
  nullable)`, sectionSlug, chunkSlug, description` — create sets `docSlug=sectionSlug=chunkSlug=
224
291
  bedrockKbId`.
225
- - **Not yet run end-to-end in production.** The whole feature is code-complete, `php -l` clean,
226
- and reviewed, but the create flow has not been exercised in prod pending the `pdo_pgsql`
227
- deploy plus network/IAM prerequisites (the developer is handling those).
292
+ - **Verified working end-to-end in production (2026-07-16)** with the VECTOR + S3 Vectors design.
228
293
  - No new secret literals introduced; credentials live in the config sections above (documented by
229
294
  section/account, never by value). AWS account id `654654170868`, bucket `togaiq`, and the KB
230
295
  execution-role ARN are non-secret resource identifiers.
231
296
 
232
297
  ## Change history
233
298
 
299
+ - 2026-07-16 — **Corrected the KB-create architecture from MANAGED to VECTOR + S3 Vectors**
300
+ (the canonical design, from `development-team-dunn`; the earlier code was modeled on the WRONG
301
+ `craftex`/MANAGED KB). Rewrote `App_Talos_Bedrock`: KB is now `VECTOR` + `S3_VECTORS` storage;
302
+ added `s3VectorsClient()`, `vectorBucketName()`, and `createVectorStore()` (create vector
303
+ bucket + index sized to Titan v2, poll `getIndex` for the ARN); `createKnowledgeBase()` now
304
+ takes the index ARN; `createDataSource()` rewritten to plain `S3` + `BEDROCK_DATA_AUTOMATION`;
305
+ removed dead `BUCKET_NAME`/`BUCKET_OWNER_ACCOUNT_ID`. Wired a new **S3 Vectors provisioning
306
+ step** into `post.php` before KB creation (flow is now 9 steps) and added
307
+ `set_time_limit(CREATE_MAX_EXECUTION_SECONDS=600)`. **Decided on ONE shared IAM execution role**
308
+ (`…ForKnowledgeBase-talos`, hard-coded `ROLE_ARN`) instead of per-KB roles, to avoid granting
309
+ the app IAM write powers; granted the `[talos]` user (`bedrock-model-accessor`) the missing
310
+ `s3vectors` + `iam:PassRole` permissions (`s3vectors` is NOT in `AmazonBedrockFullAccess`).
311
+ **Verified working end-to-end in production.** `craftex` and `toga-desk` remain on the old
312
+ MANAGED type (left as-is; recreate later). (jcardinal)
234
313
  - 2026-07-13 — Added the **`/talos/knowledge-bases` list + inline-rename page** (`get.php`;
235
314
  lists from `Team.KnowledgeBases`, rename-only, no delete, existence-check before update),
236
315
  registered first in the `talos-kb` nav group. Added the **`App_Pg`** tools-only PDO `pgsql`
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-07-07
9
+ updated: 2026-07-16
10
10
  owners: ["jcardinal", "rgirish", "mhammontree"]
11
11
  files:
12
12
  - _underscore/_underscore.php
@@ -342,6 +342,7 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
342
342
  still refetches storage unconditionally, so a dirty read (read-after-assign, pre-save) discards the
343
343
  unsaved value.
344
344
  - **`_Database::register()` auto-starts a lazy transaction (since Apr 2 2026, commit `fa7835ed`).** Any code that calls `register()` and then writes to that DB must call `_Database::transactionCommit()` before the request ends — otherwise MySQL silently rolls back all writes when the connection closes. Lazy transactions only materialise on the first write, so read-only callers are unaffected. See `_underscore/Database.php:48`. First discovered when Rate SAML user provisioning silently discarded all new user INSERTs (Jun 2026).
345
+ - **PHP "Unclosed '{'" parse errors report a MISLEADING line number.** When a `.php` file loaded by the autoloader (`Loader.php`) has a dropped/unbalanced brace, PHP reports `Unclosed '{' on line N` where N is the **outermost `class X {` line** and fails at EOF — NOT at the true location of the missing `}`. Worse, because the file loads lazily via the SPL autoloader, the runtime trace points at the **caller** that triggered the autoload (e.g. a `new _Email()` call site), not the broken file. **Triage rule:** for an "Unclosed '{'" error, the real culprit is a missing `}` somewhere between the reported line and EOF of the file that failed to load — run `php -l <file>` (it reports the EOF line) and scan the whole file. **Merge-conflict resolutions are a common source of a single dropped brace** — review the entire merge, not just the one file the error appears to name. (First hit: production 500 EO-1, Jul 2026 — a `}` dropped from `_Email::send()` during merge `685e4a14` surfaced as a trace pointing at the `new _Email()` caller.)
345
346
 
346
347
  ## Change history
347
348
  - 2026-06-11 — Documented lazy transaction gotcha in `_Database::register()` (rgirish)
@@ -349,3 +350,4 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
349
350
  - 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)
350
351
  - 2026-07-02 — Fixed framework-wide `FIELD_STORAGE` write-drop and folder-read bugs in `Model.php`; noted the remaining unconditional-refetch dirty-read follow-up. (mhammontree)
351
352
  - 2026-07-07 — Documented `_Query` writes-only async mode (`isAsync` → Worker `Infrastructure/Database/Query`) in the database-architecture section, linked `features/async-query-execution.md`, and noted the SQS-first `_Worker::runTask()` enqueue departure (caller does no MySQL; worker tier creates the row on pickup). (jcardinal)
353
+ - 2026-07-16 — Documented the misleading "Unclosed '{'" parse-error gotcha (autoloader reports the outermost `class {` line / caller trace, not the true dropped-brace location; merge conflicts a common cause). First hit production 500 EO-1 via a dropped `_Email::send()` brace. (jcardinal)
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-30
9
+ updated: 2026-07-16
10
10
  owners: ["jcardinal", "bala", "mhammontree"]
11
11
  files:
12
12
  - _underscore/Model/Client/EmailTemplate.php
@@ -106,6 +106,14 @@ can keep using `sendEmail($api, ...)`.
106
106
  use ASCII-only content. Recommended **root-cause fix**: set `$mailer->CharSet = 'UTF-8'` in
107
107
  `_Email::send()` — but that affects **every** client, so it should be a separate, tested
108
108
  change rather than a drive-by edit.
109
+ - **⚠ SECURITY — live AWS SES SMTP credentials are hardcoded in `_underscore/Email.php`.**
110
+ The `SMTP_USERNAME` / `SMTP_PASSWORD` class constants (near the top of `_Email`) hold
111
+ **real** AWS SES SMTP credentials committed in source, violating the team no-secrets-in-code
112
+ rule (`rules/common/security.md`, `git-workflow.md`). Pre-existing (not introduced by the
113
+ Jul 2026 parse-error fix). **Remediation:** treat the committed values as compromised and
114
+ rotate them in AWS SES, then move them out of source into `Config/[environment].ini`
115
+ (read via `_Config`), matching how DB credentials are handled. Until then, be aware the
116
+ secret lives in the `_Email` constants — do **not** copy the values anywhere.
109
117
  - **`clientIdentifier` does NOT pick the template DB.** `_Email::send()` uses
110
118
  `clientIdentifier` only for CloudWatch logging/validation. The template loads from whatever
111
119
  DB the `DB_CLIENT` alias points at — so register the right `Client_<x>` schema before
@@ -130,6 +138,11 @@ worker method) in-process instead.
130
138
 
131
139
  ## Change history
132
140
 
141
+ - 2026-07-16 — Recorded the **hardcoded AWS SES SMTP credentials** security gotcha in
142
+ `_Email` (`SMTP_USERNAME`/`SMTP_PASSWORD` constants; pre-existing — rotate + move to
143
+ `Config`). Surfaced while fixing a production 500 (EO-1) whose root cause was a single
144
+ `}` dropped from `_Email::send()`'s header `foreach` during merge `685e4a14`; the fix
145
+ restored the brace (`php -l` clean). (jcardinal)
133
146
  - 2026-06-30 — Documented the **`CharSet=UTF-8` mojibake gotcha** (`_Email::send()` doesn't
134
147
  set it → non-ASCII content garbles; scoped workaround = ASCII-only, root-cause = set
135
148
  `$mailer->CharSet` as a separate tested change), clarified that `clientIdentifier` does not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.353",
3
+ "version": "1.0.355",
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",