toga-ai 1.0.663 → 1.0.665
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: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-26
|
|
10
10
|
owners: ["jcardinal", "rgirish", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/_underscore.php
|
|
@@ -369,6 +369,7 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
|
|
|
369
369
|
pin is `Addresses.isValidated` and `Entitlements.serviceAddressId` both-or-neither. Relevant code:
|
|
370
370
|
`_underscore/Model/Rate/Entitlement.php`, `api2/Component/Api/V2/V2.php::getFullModelData`.)
|
|
371
371
|
- **`_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).
|
|
372
|
+
- **`_Model::load()` with a collapsed WHERE clause silently full-table-scans → OOM (guarded 2026-08-26, `Model.php`).** `_Model::search()` builds `SELECT <pk> FROM <table> [WHERE …] ORDER BY <pk>` and emits the WHERE **only if at least one search term survives**. `buildSqlFieldValue()` **drops** a term (returns false) when a set field's value is `null` and its type is a plain `FIELD_CHAR` — i.e. not one of the special-cased types (`AUTOINCREMENT`, `DATETIME_CREATED`, `DATETIME_UPDATED`, `SQL`, `STORAGE`, `CHAR_UUID`). If the only set search field collapses this way, the WHERE vanishes and `search()` selects the **whole table**, constructing one model per row → memory exhaustion on a large table. Because `load()` calls `search()` first and only then checks `count() == 1`, it **OOMs building the result array before it ever counts** — the empty-WHERE case never reaches the "not exactly one" branch. **`load()` means "find exactly one by criteria"; an empty WHERE is always a bug, never a full-table scan.** **Fix (commit `0b944a51`, `_production`):** `load()` now guards at the top — if no set search field yields a WHERE term (via `buildSqlFieldValue`), it `error_log`s and returns `false` (or throws when `throwExceptionIfNotFound`) instead of scanning. `search()` is unchanged, so intentional "list all rows" callers of `search()` are unaffected. **Triage:** a PHP OOM stack trace of `Model.php search() → __construct` that is *shallow* (a single search→construct, not deeply repeated) is a runaway result set from a collapsed WHERE, **not** recursion — confirm by checking whether the single set search field is a `FIELD_CHAR` whose value is null. (Root cause of a recurring prod OOM: `Logs.Issue` ref `2L` (#38), first seen 2026-08-03, 136 occurrences, trace `Model.php search()→__construct`.)
|
|
372
373
|
- **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.)
|
|
373
374
|
|
|
374
375
|
- **A controller exception used to be silently swallowed by `Route.php`, then masked as a view error
|
|
@@ -395,6 +396,7 @@ multi-file UI components (`.php`/`.html`/`.css`/`.js`) invoked as `<_ComponentNa
|
|
|
395
396
|
stack trace) to API consumers. Not yet done — tracked as a follow-up.
|
|
396
397
|
|
|
397
398
|
## Change history
|
|
399
|
+
- 2026-08-26 — Documented and guarded the `_Model::load()` collapsed-WHERE full-table-scan OOM: `search()` omits the WHERE when the only set field is a null `FIELD_CHAR` (dropped by `buildSqlFieldValue`), so `load()` scans the whole table and OOMs building models before its `count()==1` check; `load()` now returns false / throws instead of scanning (commit `0b944a51`). Added the shallow `search()→__construct` OOM-trace triage rule. Root cause of prod OOM `Logs.Issue` ref `2L`, 136 occurrences since 2026-08-03. (jcardinal)
|
|
398
400
|
- 2026-07-27 — Added the gotcha that **in-request reads cannot see the transaction's own uncommitted nested writes** — a `postPost` interceptor must resolve just-created records from the `getFullModelData`-hydrated `$payload`, not a same-request `_Query` read-back (even on the write host). Found on the Rate WH service-address pin (TRUE-79533). (mhammontree)
|
|
399
401
|
- 2026-06-11 — Documented lazy transaction gotcha in `_Database::register()` (rgirish)
|
|
400
402
|
- 2026-06-25 — Added the Surface platform UI presentation/configuration layer (DB-driven UI config replacing `Page::meta()`, CTO-reviewed AGREE-WITH-ADJUSTMENTS) (jcardinal)
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-26
|
|
10
10
|
owners: [jcardinal, bala, mhammontree, dfranks]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Controller/Index.php
|
|
@@ -131,6 +131,8 @@ One ~2,000-line `execute()` then `processRoutePairs()`:
|
|
|
131
131
|
> was at fault. The **sibling CloudWatch JSONL file-append logger** (`Controller/Index.php` ~L433) is
|
|
132
132
|
> **intentionally left bare-ms** — a JSONL append has no UNIQUE key and cannot collide.
|
|
133
133
|
|
|
134
|
+
> **A present-but-null client `transactionId` triggered the `_Model::load()` full-table scan on `Logs.Api` → memory exhaustion on `/v2/auth/public` (FIXED 2026-08-26).** The inbound request-logger's duplicate-`transactionId` check does `$existingLog = new $logsApiModelName(); $existingLog->transactionId = $transactionId; if ($existingLog->load()) {…}`. The `Logs Api` models declare `transactionId = FIELD_CHAR` (confirmed in both `_Model_Client_Logs_Api` and `_Model_Core_Logs_Api`), so when `$transactionId` is **null**, `load()` drops the only search term, the WHERE collapses, and it **full-table-scans the enormous prod `Logs.Api` table → OOM** (the `_underscore` `Model.php` collapsed-WHERE mechanism — see [_underscore architecture — Gotchas](../_underscore/architecture.md)). **Trigger:** a caller posting a *present-but-null* transactionId (e.g. `{"transactionId": null}`). V2 only auto-generates a uuid when the key is **absent** (`if (!\_Page::isSet('transactionId'))`, ~L543); a *present* null falls through to `$transactionId = \_Page::get('transactionId')` = null. **Fix (commit `36ef31f`, `_production`):** (a) after reading `transactionId` in the auth block (~L553), coerce null/empty → `_String::generateUuid()`; (b) guard the final request-logger dup-check (~L2277) to skip `load()` when `transactionId` is null/empty. **Triage:** don't assume an OOM/incident was caused by the latest deploy — this predated the suspected afternoon deploy by ~3 weeks; check `Logs.Issue.dtCreated` (first-occurrence) and `Logs.Api` volume/response-code trends first. (Recall Known issue #6: api2 clones `_underscore` from the moving `_<ENVIRONMENT>` branch, so framework behavior can shift under a stable api2 commit.)
|
|
135
|
+
|
|
134
136
|
## CRUD engine — `processRoutePairs()`
|
|
135
137
|
|
|
136
138
|
**Metadata-driven** — routes/models/fields/permissions come from Core/Client DB tables, not
|
|
@@ -325,6 +327,7 @@ they are the known sharp edges. Do not re-discover these from scratch.
|
|
|
325
327
|
single-caller branches in `V2.php` as unverified until exercised directly.
|
|
326
328
|
|
|
327
329
|
## Change history
|
|
330
|
+
- 2026-08-26 — Documented and fixed a `/v2/auth/public` OOM: a present-but-null client `transactionId` (`{"transactionId": null}`) flowed into the request-logger's `_Model::load()` dup-check; because `Logs.Api.transactionId` is `FIELD_CHAR`, a null value collapses the WHERE and full-table-scans prod `Logs.Api` → memory exhaustion (the `_underscore` collapsed-WHERE mechanism). V2 only auto-uuids an *absent* transactionId (~L543), so a present null fell through (~L553 read → null). Fix (commit `36ef31f`): coerce null/empty transactionId → uuid in the auth block, and skip `load()` in the final logger dup-check (~L2277) when null/empty. Added the "don't blame the latest deploy — check first-occurrence" triage note. (jcardinal)
|
|
328
331
|
- 2026-08-25 — **Resolved the auto-generated `Api.transactionId` 1062 collision** (was the 2026-07-28
|
|
329
332
|
gotcha): the two DB-insert log sites now uuid the log-row `transactionId` (`_String::generateUuid()`)
|
|
330
333
|
— `V2.php` `internalApiRequest()` logger (~L2370) and `Controller/Index.php` error-recovery logger
|
package/package.json
CHANGED
|
@@ -28,6 +28,23 @@ pushes that skill describes. It does not authorize publishing anything else in t
|
|
|
28
28
|
|
|
29
29
|
Force-pushing is never automatic in any repo, including the knowledge repo.
|
|
30
30
|
|
|
31
|
+
## Claude never creates git worktrees
|
|
32
|
+
|
|
33
|
+
Claude **must never** run `git worktree add` (or otherwise create a git worktree) in any
|
|
34
|
+
repo, for any developer, under any circumstances — **including** to isolate a hotfix from the
|
|
35
|
+
branch the developer currently has checked out. There is no approved use of worktrees.
|
|
36
|
+
|
|
37
|
+
Worktrees strand the developer: their editor and terminal end up pinned inside the worktree
|
|
38
|
+
directory (`C:\WWW\_hotfix\...`) with no obvious way back to their normal checkout, and
|
|
39
|
+
cleaning it up needs worktree-specific commands (`git worktree remove`) most developers do
|
|
40
|
+
not expect.
|
|
41
|
+
|
|
42
|
+
To work on a branch **other** than the one checked out, use the normal single-checkout flow:
|
|
43
|
+
make the edits on the current branch, or tell the developer which branch is needed and let
|
|
44
|
+
them switch (`git checkout` / `git switch`) — Claude does **not** switch branches itself
|
|
45
|
+
either (see *Claude never rewrites code because a branch changed*). Present diffs/patches and
|
|
46
|
+
let the developer commit, PR, and deploy.
|
|
47
|
+
|
|
31
48
|
## Claude never rewrites code because a branch changed
|
|
32
49
|
|
|
33
50
|
Many developers work across many projects and switch branches constantly. A change that is
|