toga-ai 1.0.1139 → 1.0.1141

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.
@@ -15,6 +15,7 @@ related:
15
15
  - ../../togaview/workflows/route53-cross-account-zone-migration.md
16
16
  - ../../../../2.0/apps/worker2/features/alb-target-group-auto-registration.md
17
17
  - ../../../../2.0/apps/api2/workflows/codepipeline-codeconnections-deploy.md
18
+ - ../../vision/workflows/deploy-to-elastic-beanstalk-al2023.md
18
19
  ---
19
20
 
20
21
  Runbook to prove an EB env in legacy account 502614707982 (us-west-2) is unused (DNS in two accounts + external, shared-ALB rules, CodePipeline) and tear it down in safe order; also the legacy cost picture, what was retired (TRUE-82821, TRUE-82983, TRUE-83123), and open cost follow-ups.
@@ -54,7 +55,7 @@ Use when cutting the legacy AWS bill or cleaning old envs. An env is **used** on
54
55
  - **Claude auto mode blocks destructive AWS deletes** even with `Bash(*)`. Allow rules in `settings.local.json` (`Bash(aws codepipeline:*)`, `Bash(aws elasticbeanstalk:*)`, `Bash(aws elbv2:*)`, `Bash(aws route53:*)`) unblock most. Still blocked (TRUE-83123): EB terminate / TG delete loops when names come from a file (list names inline in the command instead); ALL Route 53 record changes (developer runs them); writing a delete script file. `delete-load-balancer` was blocked in TRUE-82821, worked in TRUE-83123. The `start-gate` hook blocks Claude from editing `settings*.json`; the developer edits it.
55
56
  - **PowerShell `Del` is a built-in alias** of `Remove-Item` and beats a user function of that name — never name a helper `Del`/`rm`/`ls` etc. Multi-line PS commands pasted to the developer get joined — give one-liners with `;` between statements.
56
57
  - **`--output text` with multi-column queries breaks shell `read`** when a column has spaces (EB app `Landing Pages`) — query per env instead.
57
- - **Externally referenced envs look dead from AWS.** `agilant-vision-prod` (`vision.asisystem.com`) has no Route 53 record but is live (`asi-api-prod` / `api.asisystem.com` was the same until retired).
58
+ - **Externally referenced envs look dead from AWS.** `agilant-vision-prod` (`vision.asisystem.com`, DNS in DNS Made Easy) had no Route 53 record but was live (`asi-api-prod` / `api.asisystem.com` was the same until retired).
58
59
  - **Target-group delete right after the ALB delete fails with `ResourceInUse`** for ~1 min — retry.
59
60
  - **Claude auto mode also blocks CodePipeline writes** (delete or edit) — the developer does them.
60
61
 
@@ -84,6 +85,7 @@ Legacy EB is now down to **only `agilant-worker`** (1.0 worker).
84
85
  - **Load balancers:** `SalesAdvisor`, `TestSharedLoadBalancer` deleted. Only the `AWSTransferFamily` NLB left (AS2 SFTP for agilant-as2 — keep). 41 unattached target groups deleted.
85
86
  - **DNS:** 50 Route 53 records removed across both accounts (agilantsolutions.com, truesolutions.com, truenetworksolutions.com, togacommerce.com, togatech.com, togahub.com incl. compass/nychh/spglobal/canon.api + apidoa, togaview.com walmart* hotfix/sprint/stage/ui). Outside DNS Made Easy: vision + visionbeta.asisystem.com removed by developer.
86
87
  - RDS out of scope (follow-up 2).
88
+ - **Vision revived after (TRUE-83056):** `vision.asisystem.com` now runs on `togatech-legacy-vision-production` (prod account, us-east-1) — [Vision deploy](../../vision/workflows/deploy-to-elastic-beanstalk-al2023.md). `punchout-togacommerce-production` stays retired (no punchout needed).
87
89
 
88
90
  ## Retired 2026-10-22 (TRUE-83055 — moved, not unused)
89
91
 
@@ -100,6 +102,7 @@ Legacy EB is now down to **only `agilant-worker`** (1.0 worker).
100
102
  Closed: Bedrock spend rise = LLM-jacking with the leaked static key 2026-10-09..12, stopped at key deletion (TRUE-82705) — [playbook gotchas](./responding-to-a-php-security-release.md#gotchas).
101
103
 
102
104
  ## Change history
105
+ - 2026-10-23 — Vision revived in the prod account after TRUE-83123 retirement; link to its deploy doc (TRUE-83056) (jcardinal)
103
106
  - 2026-10-23 — TRUE-83123 final legacy EB/pipeline/LB/TG teardown (jcardinal)
104
107
  - 2026-10-22 — TRUE-82983 teardown (8 envs, beta DNS, `true-alb-beta`); pre-terminate template + stack check, outside-DNS-first, TG `ResourceInUse` retry; Bedrock follow-up closed (jcardinal)
105
108
  - 2026-10-22 — Legacy prod envs + pipelines of the 5 1.0 web apps retired after the move to toga-prod; backup templates kept (TRUE-83055) (rgirish)
@@ -5,3 +5,4 @@
5
5
  | [Bundle Confirmation — Cart Preservation When Adding Add-On Services](features/bundleconfirmation-cart-preservation.md) | ODP TOGa Refresh 2026 fix — adding an add-on service no longer strips the 1-year tech support SKU from the cart (`bundleconfirmation.php` `?services=` block); o |
6
6
  | [ODP Customer Search — Loyalty Number (merchantId) Persistence](features/odp-customer-search-loyalty-persistence.md) | Fixes so the ODP loyalty number (`memberId` → `Customers.merchantId`) and the validated standardized address persist through the togarefresh2026 customer-search |
7
7
  | [ODP Retail ESD — McAfee Product Descriptions](features/odp-esd-mcafee-descriptions.md) | Where ODP Retail ESD McAfee card text comes from (`TOGA_ODP.Services`, not code) and the hardcoded-override trap in `bundleservices.php`; open before changing a |
8
+ | [ODP Retail Tech Services — Job Jacket Answer Flow (computerquestions → view → refreshJobJacketQuestionResponse)](features/odp-tech-services-job-jacket-answers.md) | How ODP TOGa Retail (togarefresh2023/2026) Tech Services job jacket answers travel from the question page to `ServiceRequestJobJacketAnswers`; open when touchin |
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: ODP Retail Tech Services — Job Jacket Answer Flow (computerquestions → view → refreshJobJacketQuestionResponse)
3
+ framework: "1.0"
4
+ repo: toga
5
+ project: TOGa
6
+ client: office-depot
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-10-23
10
+ owners: [jcardinal]
11
+ files:
12
+ - toga/app/togarefresh2026/customersearch/computerquestions.php
13
+ - toga/app/togarefresh2023/customersearch/computerquestions.php
14
+ - toga/app/togarefresh2026/customersearch/view.php
15
+ - toga/app/togarefresh2026/customersearch/servicequestions.php
16
+ - library/app/model/servicerequestitem.php
17
+ related:
18
+ - ./odp-customer-search-loyalty-persistence.md
19
+ - ../../../../clients/office-depot/profile.md
20
+ - ../../library/architecture.md
21
+ ---
22
+
23
+ How ODP TOGa Retail (togarefresh2023/2026) Tech Services job jacket answers travel from the question page to `ServiceRequestJobJacketAnswers`; open when touching the computer/service questions pages or `App_Model_ServiceRequestItem::refresh*JobJacket*`.
24
+
25
+ ## Summary
26
+
27
+ The Tech Services path asks job jacket questions, carries the answers in the URL and session, then saves them per service request through a browser `ajaxAction`. All saving runs through `App_Model_ServiceRequestItem` in `library`. Database: `TOGA_ODP`.
28
+
29
+ ## How it works
30
+
31
+ 1. `customersearch/computerquestions.php` (2023 + 2026 copies) — "Next" builds `paramsString` and redirects to `?content=togarefresh2026_customersearch_view&jobjacket_question_answers=<encoded>`.
32
+ 2. Answer format: `<questionId>=<answer>~|~<optionId>` pairs joined by `&` (then URL-encoded as one param).
33
+ 3. `customersearch/view.php` (top, ~line 24) reads `jobjacket_question_answers`, splits on `&` and `=`, and stores it in `$_SESSION['serviceRequestJobJacketAnswers']`.
34
+ 4. After the SR exists, `view.php` (~line 672) echoes the session answers via `json_encode` and calls `ajaxAction('App_Model_ServiceRequestItem', 'refreshJobJacketQuestionResponse', [answers, serviceId])`. `servicequestions.php` calls the same method.
35
+ 5. `refreshJobJacketQuestionResponse($answerArray, $serviceId)` deletes/replaces rows in `ServiceRequestJobJacketAnswers` (`jobJacketQuestionId`, `answerJobJacketQuestion`, `answerJobJacketQuestionOptionId`). `refreshArrayJobJacketQuestions` is the array-shaped variant.
36
+
37
+ ## Gotchas
38
+
39
+ - **Every argument is user input.** Both methods are called by browser `ajaxAction`, so `$serviceId`, question ids and option ids come from the client. Cast ids to `(int)`; save option id `0` / `'undefined'` as `null`. FK fields are NOT cast by the 1.0 model layer — see the `App_Model::getFieldValueForQuery()` gotcha in the [library architecture](../../library/architecture.md).
40
+ - **A stray quote in the redirect URL broke the whole path** (MySQL 1064). The old "Next" code ended with `+ "'"`, so the last option id became `0'` and reached the INSERT raw. When editing the redirect, check the last pair survives the `&`/`=` split clean.
41
+ - **Two copies of `computerquestions.php`** (togarefresh2023 and togarefresh2026) — fix both.
42
+ - **OPEN (not fixed, needs a ticket): no ownership check.** Neither method checks that the session user may write to the given ServiceRequest id — any logged-in user can rewrite another SR's answers.
43
+ - **OPEN (not fixed, needs a ticket): `refreshArrayJobJacketQuestions` saves the option id into the wrong property** — `jobJacketQuestionId` (~line 679) and `answerJobJacketQuestion` (~line 707) instead of `answerJobJacketQuestionOptionId`.
44
+
45
+ ## Change history
46
+ - 2026-10-23 — Removed stray `+ "'"` from both `computerquestions.php` redirects (fixed MySQL 1064 on save); `(int)`-cast serviceId/question/option ids in both refresh methods, option 0 → null (TRUE-83172) (jcardinal)
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-22
9
+ updated: 2026-10-23
10
10
  owners: [jcardinal, bala, rgirish]
11
11
  files:
12
12
  - tools/.ebextensions/004_http_to_https.config
@@ -66,12 +66,12 @@ Native PECL extensions are installed by `.platform/hooks/prebuild/*.sh` scripts,
66
66
  - **The package is `php8.5-pecl-redis6`** (phpredis major-version suffix `6`), **not** `php8.5-redis`.
67
67
  - **A plain `dnf install php8.5-pecl-redis6` drags in the php8.1 serializers.** It pulls the igbinary/msgpack deps built for **php8.1** → `php8.1-common`, which **conflicts with the platform's `php-common-8.5.6`** over `/etc/php.ini` and `/usr/lib64/php/modules/*.so` (the dnf transaction test aborts, failing the deploy). Install with `--setopt=install_weak_deps=False --exclude=php8.1-*`.
68
68
  - **The versioned package installs the `.so` into a non-platform module dir.** Even once installed, `redis.so` (and `pgsql.so`) lands in a dir the platform php does **not** scan. The platform php loads from its own `extension_dir` — discover it with `php -r 'echo ini_get("extension_dir");'`. The hook copies the `.so` there, writes a scan-dir ini enabling it, then **verifies with `php -m`** and fails the deploy on error.
69
- - **Escape hatch env property.** `02-install-php-redis.sh` honours `REDIS_HOOK_OPTIONAL=1` (EB env property) to skip on failure rather than block the whole deploy.
69
+ - **Escape hatch env property — broken in tools.** `REDIS_HOOK_OPTIONAL=1` is meant to skip on failure. But EB env properties are NOT exported to platform hook processes, so the hook must read it with `/opt/elasticbeanstalk/bin/get-config environment -k REDIS_HOOK_OPTIONAL`. Tools' copy reads `$REDIS_HOOK_OPTIONAL` from the process env, so the hatch does nothing there (vision's copy is fixed, TRUE-83056). Same hook loads the extension ini twice → php-fpm warns "Module redis/igbinary/msgpack already loaded" (open).
70
70
 
71
71
  **Known-broken sibling — `01-install-php-pgsql.sh` (pgsql).** It has the *same* versioned-package / wrong-module-dir bug and currently **fails on fresh AL2023 instances**. It is presently **bypassed** via the EB env property `PGSQL_HOOK_OPTIONAL=1` (`.ebextensions/003_pgsql_hook_optional.config`). This is a **temporary** measure: it leaves the Talos "Knowledge Bases" create flow (the only pgsql consumer) broken. The fix is the same install-and-copy-into-`extension_dir` treatment `02-install-php-redis.sh` already received; once applied, **delete the `PGSQL_HOOK_OPTIONAL` bypass config.**
72
72
 
73
73
  ### Redis sessions (survive deploys & load balancing)
74
- Prod Tools runs multiple EB instances behind an ALB, so PHP sessions must live in a shared store, not per-instance local disk. Sessions use the **shared ElastiCache Redis cluster** (`asi-session-cache…clustercfg.usw2.cache.amazonaws.com:6379`, cluster-mode config endpoint — also used by the `vision` app) via the phpredis `rediscluster` handler.
74
+ Prod Tools runs multiple EB instances behind an ALB, so PHP sessions must live in a shared store, not per-instance local disk. Sessions use the **shared ElastiCache Redis cluster** (`asi-session-cache…clustercfg.usw2.cache.amazonaws.com:6379`, cluster-mode config endpoint; `vision` now uses ElastiCache `togadesk-sessions` instead — see the [Vision deploy doc](../../vision/workflows/deploy-to-elastic-beanstalk-al2023.md)) via the phpredis `rediscluster` handler.
75
75
 
76
76
  Because the INI-file parser silently drops the `seed[]=host:port` save_path (see the PHP ini step above), the handler is configured at **runtime in `tools/_/app/framework.php`** (`App_Framework_Tools::startSession()`): `ini_set()` sets `session.save_handler=rediscluster`, `session.save_path=seed[]=<cluster cfg endpoint>:6379`, and `session.gc_maxlifetime=345600` (4 days), guarded by `extension_loaded('redis')`, then calls `session_start()` itself. The full outage story (sessions were previously on the file handler on local disk, so every deploy logged everyone out) and the graceful-degrade ordering vs. App_Error are in the [architecture doc](../architecture.md).
77
77
 
@@ -132,6 +132,7 @@ The deploy clones `agilantsolutions/library` and `resources` via `020_setup_git_
132
132
  - [TOGa Desk deploy (own Redis, offset/Medoo shims, syslog error visibility)](../../togadesk/workflows/deploy-to-elastic-beanstalk-al2023.md)
133
133
 
134
134
  ## Change history
135
+ - 2026-10-23 — `REDIS_HOOK_OPTIONAL` hatch broken (hooks need `get-config`); double ini load warning (TRUE-83056) (jcardinal)
135
136
  - 2026-10-22 — Shared gotchas: `php --ini` scan folder on AL2023, dart-sass arm64 (TRUE-83055) (rgirish)
136
137
  - 2026-10-22 — Log-bundle gotcha corrected: `/var/log/messages` is included, custom logs are not; link Desk deploy (TRUE-83053) (rgirish)
137
138
  - 2026-10-22 — Correction: nginx-only claim holds only for the nginx proxy; s3fs 1.84 IMDSv2 risk noted (TRUE-83054) (jcardinal)
@@ -0,0 +1,6 @@
1
+ # vision (Vision) — 1.0 knowledge
2
+
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [Vision (1.0 App_ App) Architecture](architecture.md) | Vision 1.0 app: App_ app on library, served at vision.asisystem.com/vision, no front controller; open before any vision code or deploy work. |
6
+ | [Deploying Vision to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)](workflows/deploy-to-elastic-beanstalk-al2023.md) | How Vision (1.0) deploys on EB PHP 8.5 / AL2023 (Apache proxy) in the prod account — env + pipeline, `/vision` folder hook, Redis sessions, s3fs `asifiles`, den |
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Vision (1.0 App_ App) Architecture
3
+ framework: "1.0"
4
+ repo: vision
5
+ project: Vision
6
+ client: shared
7
+ type: architecture
8
+ status: active
9
+ updated: 2026-10-23
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - vision/_/app/framework.php
13
+ - vision/.htaccess
14
+ - vision/.platform/httpd/conf.d/deny_files.conf
15
+ - vision/config.prod.ini
16
+ related:
17
+ - workflows/deploy-to-elastic-beanstalk-al2023.md
18
+ - ../library/features/error-capture-1-0.md
19
+ - ../library/features/app-cloud-aws-credentials.md
20
+ ---
21
+
22
+ Vision 1.0 app: App_ app on library, served at vision.asisystem.com/vision, no front controller; open before any vision code or deploy work.
23
+
24
+ ## Summary
25
+
26
+ Old 1.0 `App_` app bootstrapped from `library` (`App_Framework_Vision` in `_/app/framework.php`). Served at `https://vision.asisystem.com/vision/` from EB env `togatech-legacy-vision-production` (prod account, us-east-1) — deploy details in the [deploy doc](workflows/deploy-to-elastic-beanstalk-al2023.md). Used by Canon and Office Depot. PunchOut is retired (not revived).
27
+
28
+ - **No front controller.** Every `.php` file is served directly. Root `.htaccess` + `.platform/httpd/conf.d/deny_files.conf` are the only guard for non-public files.
29
+ - **Sessions** in Redis via env property `SESSION_REDIS_SAVE_PATH` (`startSession()`), file fallback.
30
+ - **Files** on s3fs `asifiles` only.
31
+ - **Config** = `config.<ENVIRONMENT>.ini`; prod uses `ENVIRONMENT=prod`.
32
+ - **Errors** go to Sentry, not `Logs.Issue`.
33
+
34
+ **Critical rules:** Any new non-public file or folder must be added to BOTH deny lists (`.htaccess` and `deny_files.conf`) — there is no router to protect it. Never store or log all of `$_SERVER` (env properties like `GITHUB_TOKEN` are in it). `ENVIRONMENT` is `prod`, not `production`. Known open security debt is listed in the deploy doc — check it before touching `ajax.php`, `ajax_nosession.php`, `external/`, or `portal/`.
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: Deploying Vision to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)
3
+ framework: "1.0"
4
+ repo: vision
5
+ project: Vision
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-10-23
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - vision/.ebextensions/009_setup_phpini.config
13
+ - vision/.ebextensions/020_setup_git_libraries.config
14
+ - vision/.platform/hooks/predeploy/20-setup-vision-folders.sh
15
+ - vision/.platform/hooks/prebuild/02-install-php-redis.sh
16
+ - vision/.platform/httpd/conf.d/deny_files.conf
17
+ - vision/.htaccess
18
+ - vision/_/app/framework.php
19
+ - vision/download_file.php
20
+ - vision/cr.php
21
+ - vision/ebs/s3fs.prod.json
22
+ - vision/composer.json
23
+ related:
24
+ - ../../api/workflows/deploy-to-elastic-beanstalk-al2023.md
25
+ - ../../tools/workflows/deploy-to-elastic-beanstalk-al2023.md
26
+ - ../../togadesk/workflows/deploy-to-elastic-beanstalk-al2023.md
27
+ - ../../test/workflows/retiring-unused-legacy-elastic-beanstalk-environments.md
28
+ - ../../library/features/app-cloud-aws-credentials.md
29
+ ---
30
+
31
+ How Vision (1.0) deploys on EB PHP 8.5 / AL2023 (Apache proxy) in the prod account — env + pipeline, `/vision` folder hook, Redis sessions, s3fs `asifiles`, deny rules (no front controller), open security debt; open before touching vision `.ebextensions`/`.platform` or deploying vision.
32
+
33
+ ## Summary
34
+
35
+ TRUE-83056 revived Vision on "PHP 8.5 running on 64bit Amazon Linux 2023/4.13.13" (Apache proxy, php-fpm). Shared AL2023 rules live in the [Tools deploy doc](../../tools/workflows/deploy-to-elastic-beanstalk-al2023.md); s3fs 1.97, `GITHUB_TOKEN` clone, HTTPS listener and pipeline pattern in the [api deploy doc](../../api/workflows/deploy-to-elastic-beanstalk-al2023.md). This doc holds only what differs for vision.
36
+
37
+ - Env `togatech-legacy-vision-production`, EB app `Vision`, us-east-1, prod account 654654170868, 2 × t4g.large. Old AL1 env `agilant-vision-prod` (legacy account, us-west-2) was terminated 2026-10-23 (TRUE-83123; saved config `pre-terminate-TRUE-83123-agilant-vision-prod`). `punchout-togacommerce-production` was not revived (no punchout needed).
38
+ - Pipeline `Legacy-Vision-Production` (us-east-1), copied from `Legacy-API-Production`: CodeStar source `agilantsolutions/vision` branch `_production`, Manual approval, EB deploy.
39
+ - Domain `vision.asisystem.com`: DNS in **DNS Made Easy** (not Route 53), CNAME → `togatech-legacy-vision-production.us-east-1.elasticbeanstalk.com`.
40
+ - App served at `/vision/`; health check `/vision/helloworld.txt` (set in `009`).
41
+
42
+ ## Steps
43
+
44
+ 1. **Env properties:** `ENVIRONMENT=prod` (not `production` — library loads `config.<ENVIRONMENT>.ini` and the s3fs hook reads `ebs/s3fs.<ENVIRONMENT>.json`; only `.prod` files exist), `GITHUB_TOKEN`, `SESSION_REDIS_SAVE_PATH`. Values live in the EB env config only.
45
+ 2. **`020`** clones `library`, `dbchanges` and `resources` (`_production`) with `GITHUB_TOKEN` via `get-config` + `GIT_ASKPASS`. Runs before the folder hook.
46
+ 3. **Composer:** `composer.json` platform `8.5.0`; lock keeps the same package versions. EB runs `composer install` itself.
47
+ 4. **`.platform/hooks/predeploy/20-setup-vision-folders.sh`** (replaces AL1 `ebs/setup_vision.php`): moves everything except dotfiles and `ebs/` into `vision/`; copies root `.htaccess` into `vision/`; favicon to root; links `/portal` and `/resources` into `vision/`; root `index.php` 302 → `/vision/`; fails if `vision/vendor/autoload.php` is missing; marker file `.vision-layout-done`.
48
+ 5. **s3fs** (api's 1.97 build + predeploy/confighooks mount) mounts only `asifiles`; cache `/var/cache/s3fs`. `agilant-catalog` and `asidbarchives` dropped (unused).
49
+ 6. **Redis sessions:** `App_Framework_Vision::startSession()` (`_/app/framework.php`) reads `SESSION_REDIS_SAVE_PATH` like togadesk — `seed[]=` → `rediscluster`, else `redis` handler; `ini_set` before parent init; file-session fallback with `error_log`; SameSite=Lax + `use_strict_mode`. Prod points at ElastiCache `togadesk-sessions` (us-east-1, non-cluster, same VPC). Its SG `sg-03e79bcb9c105e100` needs inbound 6379 from the Vision instance SG.
50
+ 7. **Load balancer:** HTTPS:443 listener, ACM `*.asisystem.com` (us-east-1 / 654654170868), policy `ELBSecurityPolicy-TLS13-1-2-2021-06`, ALB idle timeout 900. All IPs allowed (developer decision; old env was limited to the Naperville office + Canon).
51
+ 8. **Verify:** env Green; `/vision/` login 200; assets 200; session cookie Secure/HttpOnly/SameSite=Lax; 403 on the deny list; `download_file.php` 401 when logged out; DNS + cert valid.
52
+
53
+ ## Security (no front controller)
54
+
55
+ Vision serves every `.php` file directly, so deny rules are the only guard:
56
+ - Root `.htaccess` (copied into `vision/`) AND `.platform/httpd/conf.d/deny_files.conf` (works without AllowOverride): `config.*`; ini/env/json/sh/lock/yaml/phar/bat/sql/log/bak/dist/md; dotfiles; all of `_/`, `vendor`, `old_scripts`, `php-saml-master`, `ebs`; `resendemail.php`, `clearsession.php`; `Options -Indexes`. Add any new non-public file or folder to BOTH.
57
+ - `download_file.php` requires login (employee, portal user or punchout session); `downloadFileAction` only allows `Browser_Chat_Widget::downloadEventFile`.
58
+ - `cr.php` stores only an allowlist of `$_SERVER` keys — env properties (e.g. `GITHUB_TOKEN`) reach `$_SERVER`, so never store or log all of it.
59
+ - Files that held an AWS key, a GitHub token and SES SMTP keys were removed (`ebs/mount-s3fs-credentials-file.php`, `ebs/git.json`, `sendtestemail*.php`); still in git history — developer rotating.
60
+
61
+ ## Gotchas
62
+
63
+ - **`create-pipeline` starts a run at once** on the current `_production`. Reject it at Approval if `_production` does not yet hold the new config.
64
+ - **EB env properties are not exported to platform hook processes.** In `.platform/hooks/*` read them with `/opt/elasticbeanstalk/bin/get-config environment -k <NAME>` (vision's `02-install-php-redis.sh` does; tools' copy does not — see [Tools](../../tools/workflows/deploy-to-elastic-beanstalk-al2023.md)).
65
+ - **Dropped from AL1 (do not re-add):** IMAP/LDAP (unused), composer AL1 hook, codecept tests, prefork tuning, `/tmp` cleaner cron, symlinks config, `DOAcron.yaml` (crons dead), HP PQWS certs + `/var/www/8x8`.
66
+ - Vision errors go to Sentry, not `Logs.Issue` ([error capture](../../library/features/error-capture-1-0.md)).
67
+
68
+ ## Open follow-ups (pre-existing, not fixed)
69
+
70
+ 1. `ajax.php do=ajaxAction` lets a caller choose any `class::method`.
71
+ 2. `ajax_nosession.php` forgot-password change has no reset-token check and uses md5.
72
+ 3. `external/*.php` serve records by guessable ids.
73
+ 4. `portal/index.php` + `portal/franchise.php` `require_once` path traversal.
74
+ 5. ~100 `mysql_*` calls (dead since PHP 7.0).
75
+ 6. php-fpm warns "Module redis/igbinary/msgpack already loaded" — `02-install-php-redis.sh` loads the extension ini twice (same in tools).
76
+ 7. `config.prod.ini` still has old us-west-2 RDS hosts + unmounted catalog paths.
77
+
78
+ ## Change history
79
+ - 2026-10-23 — Created: Vision revived on PHP 8.5 / AL2023 in the prod account; folder hook, Redis sessions, s3fs `asifiles`, deny rules, open security list (TRUE-83056) (jcardinal)
@@ -43,7 +43,7 @@
43
43
  | [Location, Unit (asset), Ticket Department — notes + files tables, LocationTypes icons](features/location-unit-department-notes-files.md) | Shared Client-DB notes/files child tables for Locations, Units (assets) and TicketDepartments, plus LocationTypes icon columns; open before adding notes/files t |
44
44
  | [DB-free unit testing for _underscore model interceptors](features/model-interceptor-unit-testing.md) | DB-free unit-test scaffold for `_underscore` model interceptor/validation logic (stub the persistence base, reflect into privates); open when testing a `prePost |
45
45
  | [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` has `__get` but NO `__isset`, so `isset()`/`??`/`empty()` on a magic DB field are unreliable or false; open before probing an optional model field or d |
46
- | [_Model::save() parent FK cascade — stored-SQL-field recompute deadlocks](features/model-save-parent-cascade-stored-field-deadlock.md) | Why a child insert deadlocks (MySQL 1213): `_Model::save()` re-saves the parent to recompute stored SQL fields; open when a child-insert path deadlocks or befor |
46
+ | [_Model::save() parent FK cascade — stored-field deadlocks + bare-parent save() validation](features/model-save-parent-cascade-stored-field-deadlock.md) | Why a child save deadlocks (MySQL 1213) or throws the PARENT's validation error: `_Model::save()` re-saves each FK parent as a bare model; open when a child ins |
47
47
  | [_Model::save() vs raw _Query — no atomic conditional update](features/model-save-vs-query-atomic-update.md) | `_Model::save()` can't do an atomic conditional update or a second write on the same instance; open before writing a row-claim/optimistic-concurrency guard, a r |
48
48
  | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | Shared 2.0 `_underscore` NetSuite REST client (`_Component_Api_Netsuite`) — record GET/write, SuiteQL, auth, escaping, logging; open before any 2.0 NetSuite cal |
49
49
  | [NetSuite Sales Order sync — ship-to address, phone, and PO reference sourcing](features/netsuite-salesorder-address-phone-sync.md) | Where the shared NetSuite SO importer (`_Trait_Netsuite_SalesOrder`) sources SO#, PO#, and ship-to phone, and the traps in changing it; open before editing this |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-10-17
9
+ updated: 2026-10-23
10
10
  owners: ["jcardinal", "rgirish", "mhammontree", "ajean"]
11
11
  files:
12
12
  - _underscore/_underscore.php
@@ -43,7 +43,7 @@ business logic never moves into Surface config.
43
43
  Storage fields (`FIELD_STORAGE`) must be written by assigning then `save()` — historically a
44
44
  `__get` refetch on the write path silently dropped the assigned value (fixed 2026-07); still avoid
45
45
  *reading* a storage field between assign and save (it refetches and discards the unsaved value).
46
- `_Model::save()` **cascades to FK parents** — saving a child re-saves its parent to refresh the parent's stored SQL fields (`FIELDOPT_SQL_STORED`). A stored field that aggregates the parent's own children (e.g. a correlated-subquery total) therefore re-emits a parent+children lock on **every** child insert and will **deadlock (MySQL 1213)** under concurrent same-parent inserts; opt a model out with `$_model_skipParentCascadeOnSave` when no touched parent's stored field can go stale.
46
+ `_Model::save()` **cascades to FK parents** — saving a child re-saves each parent as a **bare model** (primary key only, never loaded) to refresh its stored SQL fields (`FIELDOPT_SQL_STORED`). Two traps: (1) a stored field that aggregates the parent's own children re-emits a parent+children lock on **every** child insert and will **deadlock (MySQL 1213)** under concurrent same-parent inserts; (2) a parent whose `save()` override validates its own fields sees nulls and throws on every child insert (e.g. CodeSet's "needs a slug" blocked all code-set-codes POSTs). Opt the child out with `$_model_skipParentCascadeOnSave` when no parent stored field can go stale — see [save-cascade doc](features/model-save-parent-cascade-stored-field-deadlock.md).
47
47
 
48
48
  ## Entry point & boot sequence
49
49
 
@@ -1,17 +1,19 @@
1
1
  ---
2
- title: _Model::save() parent FK cascade — stored-SQL-field recompute deadlocks
2
+ title: _Model::save() parent FK cascade — stored-field deadlocks + bare-parent save() validation
3
3
  framework: "2.0"
4
4
  repo: _underscore
5
5
  project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-23
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - _underscore/Model.php
13
13
  - _underscore/Model/Client/PurchaseOrder.php
14
14
  - _underscore/Model/Client/AdvanceShippingNotice.php
15
+ - _underscore/Model/Client/CodeSet.php
16
+ - _underscore/Model/Client/CodeSetCode.php
15
17
  related:
16
18
  - ./calculated-sql-fields.md
17
19
  - ./model-save-vs-query-atomic-update.md
@@ -19,15 +21,15 @@ related:
19
21
  - ../../../../clients/compass-usa/features/asn-to-item-fulfillment.md
20
22
  ---
21
23
 
22
- Why a child insert deadlocks (MySQL 1213): `_Model::save()` re-saves the parent to recompute stored SQL fields; open when a child-insert path deadlocks or before marking a parent field `FIELDOPT_SQL_STORED`.
24
+ Why a child save deadlocks (MySQL 1213) or throws the PARENT's validation error: `_Model::save()` re-saves each FK parent as a bare model; open when a child insert deadlocks/fails on a parent rule, or before marking a parent field `FIELDOPT_SQL_STORED`.
23
25
 
24
26
  ## Summary
25
27
 
26
- `_Model::save()` runs a **generic parent foreign-key cascade**: inserting (or saving) a child row that carries an FK to a parent causes `_Model` to **re-load and re-save that parent** so the parent's stored SQL fields (`FIELDOPT_SQL_STORED => true`) are refreshed. When a parent's stored field is a **correlated subquery** over the parent's own children, that re-save emits an UPDATE that **X-locks the parent row and S-locks every child row** — even though the child insert never changed any value the stored field depends on. Under concurrent inserts of children for the **same parent**, this is a textbook **MySQL 1213 deadlock**. Definitive diagnosis for the api2 ASN-path deadlocks. Reusable rule: **a stored SQL field whose expression aggregates the model's children turns every child insert into a full parent-plus-children lock.**
28
+ `_Model::save()` runs a **generic parent foreign-key cascade**: inserting (or saving) a child row that carries an FK to a parent causes `_Model` to **re-save that parent** so the parent's stored SQL fields (`FIELDOPT_SQL_STORED => true`) are refreshed. When a parent's stored field is a **correlated subquery** over the parent's own children, that re-save emits an UPDATE that **X-locks the parent row and S-locks every child row** — even though the child insert never changed any value the stored field depends on. Under concurrent inserts of children for the **same parent**, this is a textbook **MySQL 1213 deadlock**. Definitive diagnosis for the api2 ASN-path deadlocks. Reusable rule: **a stored SQL field whose expression aggregates the model's children turns every child insert into a full parent-plus-children lock.**
27
29
 
28
30
  ## How it works
29
31
 
30
- - The cascade lives in `_Model::save()` (`Model.php` ~L360-390). Saving a model with a populated FK to a parent re-loads that parent and calls `save()` on it, whose purpose is to recompute and persist the parent's `FIELDOPT_SQL_STORED` columns.
32
+ - The cascade lives in `_Model::save()` (`Model.php` ~L425-459). When a child's FK value changed, it builds a **bare** parent (`new $Model()`, only the primary key set, **never loaded**) and calls its `save()` — purpose: recompute and persist the parent's `FIELDOPT_SQL_STORED` columns.
31
33
  - `_Model_Client_PurchaseOrder` declares `_total` as a **stored** SQL field (`Model/Client/PurchaseOrder.php:36`, `FIELDOPT_SQL_STORED => true`). Its expression is a correlated subquery:
32
34
 
33
35
  ```sql
@@ -64,6 +66,7 @@ Protected flag on `_Model`, `$_model_skipParentCascadeOnSave` (default **false**
64
66
  - **A stored SQL field that aggregates the model's own children makes every child insert a parent+children lock.** Before marking a field `FIELDOPT_SQL_STORED => true` on a high-fan-in parent, consider that every child write will re-emit its recompute under the parent cascade.
65
67
  - **The deadlocking UPDATE is invisible in child/parent code.** Grepping PO or ASN code for the `_total` UPDATE finds nothing — it is generated by the cascade in `Model.php`. Diagnose from the DB error log statement, not the application code path.
66
68
  - **Skipping the cascade is only correct when no touched parent has a stored field that could go stale from this child's write.** `$_model_skipParentCascadeOnSave` is a per-model opt-out, not a global switch — verify the parents' stored fields before opting a model in.
69
+ - **A parent whose `save()` override validates its own fields breaks every child insert.** The cascade parent is bare, so the override sees nulls and throws the parent's error on the child's request. Case (2026-10-23, TRUE-82974): `_Model_Client_CodeSet::save()` checks slug + name, so every `POST code-set-codes` failed with EV-10 "A code set needs a slug." — no code could be added. Fix: `protected $_model_skipParentCascadeOnSave = true;` on `_Model_Client_CodeSetCode` (CodeSet has no stored SQL field; `_codeCount` is non-stored). Keep the parent validation strict. **Rule:** child of a parent with a validating `save()` override → opt out of the cascade (only if the parent has no stored SQL fields), or make the parent's `save()` not depend on loaded fields.
67
70
 
68
71
  ## Related
69
72
 
@@ -6,9 +6,10 @@ project: Database Changes
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-21
9
+ updated: 2026-10-23
10
10
  owners: [jcardinal, apeterson, tcox, bala]
11
11
  files:
12
+ - dbchanges2/Core/2026-10-23x - TRUE-82974 WorkflowActionsMenu.sql
12
13
  - dbchanges2/Core/2026-09-15a - ItemCreateDefaultsSurfaceSeed.sql
13
14
  - dbchanges2/Client_Compass/2026-09-15a - ItemCreateCatalogDefault.sql
14
15
  - dbchanges2/Client_CompassCanada/2026-09-15a - ItemCreateCatalogDefault.sql
@@ -84,7 +85,7 @@ Structural definitions live in **Core** (platform-shared, mirrors `Records`/`Rec
84
85
 
85
86
  | Table | DB | Purpose |
86
87
  |---|---|---|
87
- | `Surfaces` | Core | A configurable UI region (`type` ENUM TABLE/SECTION/BUTTON_BAR/ROW_ACTIONS/TAB_STRIP/FILTER_SET/MODAL/DRAWER) |
88
+ | `Surfaces` | Core | A configurable UI region (`type` ENUM TABLE/SECTION/BUTTON_BAR/ROW_ACTIONS/TAB_STRIP/FILTER_SET/MODAL/DRAWER). **No `MENU` type** — a kebab/actions menu (e.g. Desk `<record>-actions`) is `ROW_ACTIONS` with `config` `{"renderAs":"menu"}` + `MENU_ITEM` elements |
88
89
  | `SurfaceElements` | Core | Ordered items: ~15 typed attr columns (two orthogonal axes `valueType` + `inputType`, plus edit-mode columns — see below) + one JSON `config` long-tail + `visibilityRule`/`enabledRule` JSON |
89
90
  | `Actions` | Core | Behavior action catalog (FE registry keys + JSON payload) |
90
91
  | `Vocabularies` | Core | A value-set (status/stage/priority/type/channel) per record |
@@ -0,0 +1,5 @@
1
+ # toga2-template (TOGa App Template) — 2.0 knowledge
2
+
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [TOGa App Template — Architecture (in development)](architecture.md) | Placeholder for the in-development toga2-template (starter for new 2.0 platform apps); its knowledge lives in the repo's own `docs/` until launch. |
@@ -0,0 +1,20 @@
1
+ ---
2
+ title: TOGa App Template — Architecture (in development)
3
+ framework: "2.0"
4
+ repo: toga2-template
5
+ project: TOGa App Template
6
+ client: shared
7
+ type: architecture
8
+ status: draft
9
+ updated: 2026-10-23
10
+ owners: [jcardinal]
11
+ files: []
12
+ related: []
13
+ ---
14
+ Placeholder for the in-development toga2-template (starter for new 2.0 platform apps); its knowledge lives in the repo's own `docs/` until launch.
15
+
16
+ ## Summary
17
+
18
+ toga2-template is the starter repo every new 2.0 platform app is created from (React + Vite SPA over api2/_underscore, shared UI lib TOGa Blox), currently IN DEVELOPMENT. Its knowledge lives in the repo's own `docs/` folder until launch and will be transferred into the team KB then (repo `docs/MIGRATION-TO-TEAM-KB.md`). Do not document it here before launch.
19
+
20
+ **Critical rules:** read the repo's `docs/START-HERE.md` (and `NEW-APP-KICKOFF.md` for a new app), not this stub.
@@ -14,10 +14,10 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
14
14
  - **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
15
15
  - **walmart-activate** (Walmart Activate) — 1 doc(s) → [1.0/apps/walmart-activate/INDEX.md](1.0/apps/walmart-activate/INDEX.md)
16
16
  - **test** (Test) — 20 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
17
- - **toga** (TOGa) — 4 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
17
+ - **toga** (TOGa) — 5 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
18
18
  - **store** (Store) — 5 doc(s) → [1.0/apps/store/INDEX.md](1.0/apps/store/INDEX.md)
19
19
  - **tools** (Tools) — 25 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
20
- - **vision** (Vision) — 0 doc(s) → [1.0/apps/vision/INDEX.md](1.0/apps/vision/INDEX.md)
20
+ - **vision** (Vision) — 2 doc(s) → [1.0/apps/vision/INDEX.md](1.0/apps/vision/INDEX.md)
21
21
  - **api** (API 1.0) — 3 doc(s) → [1.0/apps/api/INDEX.md](1.0/apps/api/INDEX.md)
22
22
 
23
23
  ## 2.0 framework
@@ -34,6 +34,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
34
34
  - **toga2-commerce** (TOGa Commerce) — 34 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
35
35
  - **toga25-supply** (TOGa 2.5 Supply) — 27 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
36
36
  - **toga25-iq** (TOGa IQ) — 1 doc(s) → [2.0/apps/toga25-iq/INDEX.md](2.0/apps/toga25-iq/INDEX.md)
37
+ - **toga2-template** (TOGa App Template) — 1 doc(s) → [2.0/apps/toga2-template/INDEX.md](2.0/apps/toga2-template/INDEX.md)
37
38
  - **toga25-desk** (TOGa Desk 2.5) — 1 doc(s) → [2.0/apps/toga25-desk/INDEX.md](2.0/apps/toga25-desk/INDEX.md)
38
39
  - **toga25-blox** (TOGa Blox 2.5) — 7 doc(s) → [2.0/apps/toga25-blox/INDEX.md](2.0/apps/toga25-blox/INDEX.md)
39
40
  - **toga-blox** (TOGa Blox) — 15 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
@@ -12,6 +12,8 @@ apps:
12
12
  - forward
13
13
  - store
14
14
  - worker1.5
15
+ - library
16
+ - vision
15
17
  project: TOGa Voice
16
18
  client: office-depot
17
19
  type: profile
@@ -26,6 +28,7 @@ related:
26
28
  - ../../standalone/apps/voice-to-voice/features/ticket-api-integration.md
27
29
  - ../../1.0/apps/togadesk/architecture.md
28
30
  - ../../1.0/apps/toga/features/odp-esd-mcafee-descriptions.md
31
+ - ../../1.0/apps/toga/features/odp-tech-services-job-jacket-answers.md
29
32
  ---
30
33
 
31
34
  Office Depot uses TOGa Voice for inbound store-associate tech support and Desk ticketing; open for client scope and links to its voice configuration.
@@ -21,6 +21,7 @@ apps:
21
21
  - forward
22
22
  - toga25-blox
23
23
  - toga25-iq
24
+ - toga2-template
24
25
  - apiproxy
25
26
  - toga
26
27
  - store
@@ -247,6 +247,17 @@
247
247
  "worker2"
248
248
  ]
249
249
  },
250
+ {
251
+ "repo": "toga2-template",
252
+ "project": "TOGa App Template",
253
+ "framework": "2.0",
254
+ "role": "app",
255
+ "dependsOn": [
256
+ "toga25-blox",
257
+ "api2",
258
+ "worker2"
259
+ ]
260
+ },
250
261
  {
251
262
  "repo": "toga25-desk",
252
263
  "project": "TOGa Desk 2.5",
@@ -6,7 +6,7 @@ project: Claude Harness
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-21
9
+ updated: 2026-10-23
10
10
  owners: ["jcardinal", "rgirish"]
11
11
  files:
12
12
  - claude/skills/start/SKILL.md
@@ -171,9 +171,15 @@ All tested 2026-10-08:
171
171
  to paste. The SessionStart hook also returns it as `sessionTitle` when the developer has not
172
172
  named the session. Hook titles only work at SessionStart (per docs); VS Code support is
173
173
  unconfirmed; `/rename` works everywhere.
174
+ - **A text-only reply ends Claude's turn.** `/start` once stalled after the `/rename` line
175
+ because SKILL.md said to send it "in its own message". Show the line and call the next step's
176
+ tool in the same response; `set-title` returns a `next` field and `kickoff-primed` prints a
177
+ keep-going line for this.
174
178
 
175
179
  ## Change history
176
180
 
181
+ - 2026-10-23 — `/start` no longer stalls after the `/rename` line (same-response next step) (TRUE-83056). (jcardinal)
182
+
177
183
  - 2026-10-21 — TRUE-82674: macOS `/start` temp-dir block gotcha + workaround. (rgirish)
178
184
  - 2026-10-15 — Ticket writes need no approval: `/finish` writes fields itself, `/pseudocode`
179
185
  writes once the plan is agreed; the gate allows any field/status/tag/comment on the session
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1139",
3
+ "version": "1.0.1141",
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",