toga-ai 1.0.1004 → 1.0.1006

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,19 +6,24 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-12
10
- owners: [jcardinal]
9
+ updated: 2026-10-13
10
+ owners: [jcardinal, mhammontree]
11
11
  files:
12
12
  - library/app/cloud.php
13
13
  - library/app/systemmonitor/rdsproductioncluster.php
14
14
  - worker/ebs/cron.worker.php
15
15
  - toga/ebs/register_instance_to_shared_application_load_balancer.php
16
16
  - tools/ebs/check_s3fs.php
17
+ - library/app/model.php
18
+ - togadesk/ebs/check_s3fs.php
19
+ - togadesk/ebs/register_instance_to_shared_application_load_balancer.php
20
+ - togadesk/.ebextensions/020_setup_git_libraries.config
17
21
  related:
18
22
  - ../../../../2.0/apps/worker2/features/cross-account-aws-access.md
19
23
  - ../../../../2.0/apps/_underscore/features/cloud-s3-helpers.md
20
24
  - ../../tools/workflows/deploy-to-elastic-beanstalk-al2023.md
21
25
  - ../architecture.md
26
+ - mvc-page-pattern-and-app-skeleton.md
22
27
  ---
23
28
 
24
29
  How every 1.0 AWS call (App_Cloud CLI + SDK, deploy scripts, `setup_git_libraries.php`) gets credentials by assuming `WorkloadsRuntime` from the EC2 instance role; open before touching 1.0 AWS access or EB deploy hooks.
@@ -56,8 +61,12 @@ CodePipeline `Worker` (502, us-west-2) sources `_production` of **both** `agilan
56
61
  - **Never put `$cmd` in an exception or log message.** It contains `$preCommands` = live temporary credentials.
57
62
  - **CLI temp creds are visible in argv (`ps`).** Open follow-up: CLI profile with `credential_source=Ec2InstanceMetadata`.
58
63
  - **`setup_git_libraries.php` failing is silent.** When the key died its pipeline lookup failed and **library was not updated on deploy** — the old library kept running. If a library change "didn't deploy", check this script first. It was public-read with the key inside — a likely leak path; never put a credential in it.
64
+ - **Triage: `Aws\S3\Exception\S3Exception` 403 `InvalidAccessKeyId` (or any S3 403) in a 1.0 app → check App_Cloud credentials and whether the current library is on that app's instances — not the app code.** Library is not in the app repo: each app's EB deploy clones it (`.ebextensions/020_setup_git_libraries.config`) and pages load it from the include path (`require_once('_.php')`). A library-only fix reaches an app only on that app's next successful deploy. TRUE-82712: TOGa Desk MSP redbox (blob-field model load — see [BLOBSTORAGE](mvc-page-pattern-and-app-skeleton.md#gotchas)) stopped once library + togadesk were released 2026-10-13; no togadesk change.
65
+ - **`copyFileToS3()` (and the other CLI-based calls) never report failure.** `shell_exec('aws s3 cp ...')` then `return true` — bad credentials = silent lost upload. Uploads made while the old key was dead (repair photos, KB files, `BLOBSTORAGE` saves via `App_Model::save()`, email-intake attachments) may be missing from S3. Open, not fixed.
66
+ - **Open, not fixed: `togadesk` and `togaview` `ebs/check_s3fs.php` + `ebs/register_instance_to_shared_application_load_balancer.php` still hardcode the old dead key** (verified on `origin/_production` 2026-10-13; TRUE-82705 did not convert them). Not on the request path; follow up with Jeff/DevOps.
59
67
  - Verify assume-role in CloudTrail (`AssumeRole` on `WorkloadsRuntime`, by session name: `library-app-cloud`, `setup-git-libraries`, `register-instance`, `worker-deploy`).
60
68
  - Open, not fixed (developer owns): remove `AWS_ACCESS_KEY_ID`/`AWS_SECRET_KEY` from `agilant-worker` EB env properties.
61
69
 
62
70
  ## Change history
63
71
  - 2026-10-12 — Created: removed the static IAM key from all 1.0 code/config; App_Cloud, deploy scripts and `setup_git_libraries.php` assume `WorkloadsRuntime` (TRUE-82705) (jcardinal)
72
+ - 2026-10-13 — S3 403 triage, silent `copyFileToS3` failure, unconverted togadesk/togaview ebs scripts (TRUE-82712) (mhammontree)
@@ -6,7 +6,7 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-01
9
+ updated: 2026-10-13
10
10
  owners: [jcardinal, mhammontree]
11
11
  files:
12
12
  - library/app/framework.php
@@ -15,9 +15,11 @@ files:
15
15
  - library/app/database.php
16
16
  - library/app/model.php
17
17
  - library/app/config.php
18
+ - library/app/cloud.php
18
19
  related:
19
20
  - ../architecture.md
20
21
  - ../../togaview/architecture.md
22
+ - app-cloud-aws-credentials.md
21
23
  ---
22
24
 
23
25
  Recipe for a new 1.0 (`App_`) app and for adding pages: folder-based MVC routing, page lifecycle, models, multi-DB config; open when standing up a 1.0 app or adding a route.
@@ -63,6 +65,8 @@ class App_Model_Toga_Client extends App_Model {
63
65
  ```
64
66
  Usage: `new App_Model_X($id)` loads a row; `->save()` persists; `App_Model_X::lookupValueByField()`.
65
67
 
68
+ `BLOBSTORAGE` fields are not in the row — the file lives outside the DB at `<env>/<DatabaseName>/<Table>/<field>/<id>` (`<env>` = `prod` when `worker`). `buildModel()` reads it on **every** model load: dev mode → local `[internal] storage_folder`; otherwise downloads it from S3 bucket `asifiles` via `App_Cloud::getFileFromS3()`. `save()` uploads/deletes it the same way.
69
+
66
70
  ### Databases (multiple are first-class)
67
71
  DB access is mysqli via `App_Database::query($sql, 'db_<name>')`. **Multiple databases are first-class:** each `[database_<name>]` section in `config.<env>.ini` is registered as registry key `db_<name>` by `App_Database::registerDatabaseConnect()`. SELECTs **auto-route to `<link>_read`** when the host is an RDS cluster (read-cluster support). Escape with `App_Database::sqlEscape()`; read results with `buildArrayOfRows()` / `fetchRow()`.
68
72
 
@@ -85,6 +89,7 @@ The app depends on the `library` core repo (bootstrapped via `_.php`).
85
89
 
86
90
  - **Route folders must stay distinct.** Matching is longest-to-shortest, so overlapping route folders can produce surprise matches.
87
91
  - **An unset `ENVIRONMENT` loads PRODUCTION config.** `getenv('ENVIRONMENT')` defaults to `'worker'`, so running a 1.0 cron locally without it loads `worker/config.worker.ini` — prod DB hosts, `test_mode=0`, `email_errors=1`. Set `ENVIRONMENT` to your dev config name (`config.dev-<name>.ini`) first. Symptom: errors mention `/var/www/cache`.
92
+ - **An S3 error makes a plain model load fatal.** Any model with a `BLOBSTORAGE` field calls S3 in its constructor; `getFileFromS3()` swallows only 404 and rethrows the rest, so an AWS credential/permission failure turns `new App_Model_X($id)` into a redbox on every page that loads it (TRUE-82712: TOGa Desk MSP select via `ClientMsp.logo`, and NYCDOE `Repair::addNotes` via `App_Model_Common_NYCDOETicket.rawData`). Triage: [App_Cloud credentials](app-cloud-aws-credentials.md#gotchas).
88
93
  - **Secrets are currently stored in plaintext ini files** — a known practice, not a recommended one. Document where a credential lives, never its value.
89
94
 
90
95
  ## Related
@@ -93,3 +98,4 @@ The app depends on the `library` core repo (bootstrapped via `_.php`).
93
98
 
94
99
  ## Change history
95
100
  - 2026-10-01 — Unset-`ENVIRONMENT` gotcha: it loads production config locally; added symptom and fix (TRUE-82180). (mhammontree)
101
+ - 2026-10-13 — `BLOBSTORAGE` load/save path + S3-error-is-fatal gotcha (TRUE-82712). (mhammontree)
@@ -6,7 +6,7 @@ project: TOGa Desk
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-10-02
9
+ updated: 2026-10-13
10
10
  owners: ["mhammontree", "jcardinal"]
11
11
  files:
12
12
  - index.php
@@ -25,6 +25,9 @@ files:
25
25
  - desk/includes/functions.php
26
26
  - desk/template/header.php
27
27
  - ebs/setup_phpini.php
28
+ - .ebextensions/020_setup_git_libraries.config
29
+ - ebs/check_s3fs.php
30
+ - ebs/register_instance_to_shared_application_load_balancer.php
28
31
  related:
29
32
  - 1.0/apps/togadesk/features/per-client-host-restriction.md
30
33
  - 1.0/apps/togaview/architecture.md
@@ -36,6 +39,7 @@ related:
36
39
  - 1.0/apps/togadesk/features/notifications.md
37
40
  - 1.0/apps/togadesk/workflows/standalone-test-scripts.md
38
41
  - 1.0/apps/togadesk/features/staff-sso-login.md
42
+ - 1.0/apps/library/features/app-cloud-aws-credentials.md
39
43
  ---
40
44
 
41
45
  1.0 (App_) support desk + field-service platform — staff at `/desk/`, public/client portal at `/`; open for the request pipeline, domain-class layer, MSP data model, crons/integrations, or system topology.
@@ -147,6 +151,7 @@ Invariants:
147
151
  - The prod O365 OAuth secret used by `crons/tickets_prod.php` has a **hard expiry (~2026-12-20)**.
148
152
  - **Cross-tenant login hole in the per-client host restriction (pre-existing, NOT fixed).** The password-login access condition in `desk/includes/functions.php` (~L627-631) is a chain of ORs; two clauses grant access regardless of which client's subdomain is in use, letting an IM Houston or Fordham user sign in on another client's branded host. Post-login filtering keys off the session `clientid` so it is not an immediate record leak, but it defeats the restriction and contradicts the comment directly above it. Deserves its own ticket.
149
153
  - **Sessions are host-scoped, so a domain change logs everyone out.** `ebs/setup_phpini.php` sets `session.save_handler=rediscluster` but no `session.cookie_domain`, and there is no `session_set_cookie_params()` anywhere. Combined with `people.sessionid` being a **single column**, logging in on a second domain invalidates the session on the first.
154
+ - **Library is not in this repo — a library fix is live in Desk only after a Desk deploy.** `.ebextensions/020_setup_git_libraries.config` clones `library` on every EB deploy; both loaders `require_once('_.php')` from the include path. An S3 403 / `InvalidAccessKeyId` redbox (e.g. selecting an MSP, which loads the `ClientMsp.logo` blob field) is a library credentials problem, not Desk code — see [App_Cloud credentials](../library/features/app-cloud-aws-credentials.md#gotchas). `ebs/check_s3fs.php` and `ebs/register_instance_to_shared_application_load_balancer.php` still hardcode the old dead IAM key (open).
150
155
 
151
156
  ## Related
152
157
  - [Staff SSO login](features/staff-sso-login.md)
package/knowledge.js CHANGED
@@ -32,6 +32,7 @@
32
32
 
33
33
  const fs = require('fs');
34
34
  const path = require('path');
35
+ const os = require('os');
35
36
 
36
37
  const ROOT = path.join(__dirname, 'knowledge');
37
38
  const REGISTRY = path.join(ROOT, 'registry.json');
@@ -1048,10 +1049,31 @@ function cmdSkills(args) {
1048
1049
  * every resolved knowledge doc in the main session; the gate detects it and releases.
1049
1050
  *
1050
1051
  * This command intentionally does nothing but confirm — the gate keys off the command
1051
- * itself, so no shared state is needed here.
1052
+ * itself, so no shared state is needed here. The gate only allows the exact 3-word form, so
1053
+ * the session id comes from the env (as the hooks read it), never a flag. After the confirm
1054
+ * line it repeats the /rename line when /start saved a session title; else nothing extra.
1052
1055
  */
1056
+ const MAX_SESSION_TITLE_LENGTH = 60;
1057
+
1058
+ // Saved "<TICKET>: <short>" title from /start (start-helper.js set-title), or ''.
1059
+ function currentSessionTitle() {
1060
+ const sid = String(process.env.CLAUDE_SESSION_ID || process.env.CLAUDE_CODE_SESSION_ID || '').replace(/[^A-Za-z0-9_-]/g, '');
1061
+ if (!sid) return '';
1062
+ const dir = process.env.TOGA_SESSIONS_DIR || path.join(os.homedir(), '.claude', 'toga-sessions');
1063
+ try {
1064
+ const t = JSON.parse(fs.readFileSync(path.join(dir, sid + '.json'), 'utf8')).sessionTitle;
1065
+ return typeof t === 'string' ? t.replace(/[\u0000-\u001f\u007f`]+/g, ' ').trim().slice(0, MAX_SESSION_TITLE_LENGTH) : '';
1066
+ } catch (e) {
1067
+ if (e.code !== 'ENOENT') console.error('currentSessionTitle: ' + e.message);
1068
+ return '';
1069
+ }
1070
+ }
1071
+
1053
1072
  function cmdKickoffPrimed() {
1054
1073
  console.log('Team knowledge base primed in the main session — start gate released. Priming was synchronous; no subagents ran during it.');
1074
+ const title = currentSessionTitle();
1075
+ // Same text as skills/start/scripts/start-helper.js cmdSetTitle tellDeveloper — keep in sync.
1076
+ if (title) console.log('Reminder — tell the developer: Paste this to name the session: `/rename ' + title + '`');
1055
1077
  }
1056
1078
 
1057
1079
  function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1004",
3
+ "version": "1.0.1006",
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",
@@ -102,7 +102,9 @@ prefix words only if needed to stay short; never "TOGa". Max 60 characters. Save
102
102
  node ".../start-helper.js" set-title --session <sid> --title "<T>: <short>"
103
103
  ```
104
104
 
105
- Then tell the developer in one line: "Paste this to name the session: `/rename <title>`".
105
+ **Send the `tellDeveloper` line now — never skip, never defer.** Team rule for every developer.
106
+ Put it in its own message, as its own plain line (not in a code block), BEFORE you start Step 4.
107
+ Do not wait for the Step 8 brief. Example: Paste this to name the session: `/rename TRUE-82605: ClickUp workflow`
106
108
  The SessionStart hook also offers it as the session title on resume/compact, only when the
107
109
  developer has not named the session.
108
110
 
@@ -239,11 +241,14 @@ Status flow: to do -> in progress (/start) -> development finished (/finish).
239
241
  ## Step 8 — Brief
240
242
 
241
243
  Show a short brief, then propose the plan (multi-agent for non-trivial work, as in the old
242
- kickoff Step 6: planner -> implement -> reviewers; auto-review rules apply):
244
+ kickoff Step 6: planner -> implement -> reviewers; auto-review rules apply).
245
+ Start the brief with the rename line, as its own plain line ABOVE the code block:
246
+
247
+ **Name this session:** `/rename <session title>`
243
248
 
244
249
  ```
245
250
  Ticket: <T> — <title> (<status>, <list>)
246
- Session: <session title> (paste: /rename <session title>)
251
+ Session: <session title>
247
252
  Apps: <repos> (core: <core>)
248
253
  Clients: <clients>
249
254
  Task type: <type>
@@ -357,7 +357,14 @@ function cmdSetTitle(args) {
357
357
  if (title.length > MAX_TITLE_LENGTH) return fail('title is longer than ' + MAX_TITLE_LENGTH + ' characters');
358
358
  r.s.sessionTitle = prefix + title.slice(prefix.length);
359
359
  writeSession(r.sid, r.s);
360
- print({ ok: true, sessionId: r.sid, ticket: r.s.ticket, sessionTitle: r.s.sessionTitle });
360
+ print({
361
+ ok: true,
362
+ sessionId: r.sid,
363
+ ticket: r.s.ticket,
364
+ sessionTitle: r.s.sessionTitle,
365
+ // Same text as knowledge.js cmdKickoffPrimed reminder — keep in sync.
366
+ tellDeveloper: 'Paste this to name the session: `/rename ' + r.s.sessionTitle + '`',
367
+ });
361
368
  }
362
369
 
363
370
  /* Secret scan for text Claude is about to post (PR body, PM comment). Prints hit TYPES only. */