toga-ai 1.0.686 → 1.0.688

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.
@@ -5,13 +5,15 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-07-29
9
- owners: [jcardinal, ajean]
8
+ updated: 2026-08-28
9
+ owners: [jcardinal, ajean, apeterson]
10
10
  files: []
11
11
  related:
12
12
  - ../apps/_underscore/architecture.md
13
13
  - ../apps/worker2/architecture.md
14
14
  - ../apps/api2/architecture.md
15
+ - ../apps/dbchanges2/architecture.md
16
+ - ../apps/dbchanges2/features/surface-layer-schema.md
15
17
  ---
16
18
 
17
19
  # Framework 2.0 (_underscore) Rules
@@ -137,6 +139,14 @@ Enforced mechanically by the `PreToolUse` hook
137
139
  examples, and the approved rewrites: `2.0/apps/dbchanges2/architecture.md` → *Database
138
140
  isolation*. **Applies to `dbchanges2` only — the 1.0 `dbchanges` repo is exempt.**
139
141
 
142
+ **Verified empirically in production, 2026-08-28:** the Core reader exposes only `Core`,
143
+ `Forecast`, `Forecast_Archive` and `Team`; the client reader exposes only `Client_*`. This is a
144
+ **physical** boundary, not a permissions setting to be widened — a `Client_*` file naming `Core.*`
145
+ does not fail an authorization check, it cannot resolve at all. Corollary for **reviewers**: "it
146
+ ran locally" and "it ran on beta" are both worthless as evidence of production-runnability, because
147
+ every non-prod environment is all-in-one. The only meaningful checks are the hook, or a
148
+ connection-scoped run against a user granted just the one database.
149
+
140
150
  ### `Core.Records` / `Core.RecordFields` — the only hardcoded `id`s on the platform
141
151
 
142
152
  **These two tables, and only these two, have team-maintained primary keys.** Their `id` values are
@@ -161,6 +171,58 @@ This is also what makes the `dbchanges2` isolation rule above workable: a `Clien
161
171
  never has to read `Core` to resolve a `recordFieldId`. Full detail and examples:
162
172
  `2.0/apps/dbchanges2/architecture.md` → *`Core.Records` / `Core.RecordFields`*.
163
173
 
174
+ #### ⚠ Bounded exemption — the Surface tables
175
+
176
+ **`Core.Surfaces`, `Core.SurfaceElements` and `Core.Messages` also carry team-reserved literal `id`
177
+ blocks.** This is a **specific exemption for the Surface layer, not a general precedent** — do not
178
+ cite it to justify hardcoding ids for any other feature. Reserving them was forced by the cluster
179
+ split above: `SurfaceOverrides` lives in the **Client** DB while the elements it overrides live in
180
+ **Core**, so a `Client_*` override file must reference a Core id it is forbidden to look up at run
181
+ time. Without a stable literal there is nothing for that file to hardcode.
182
+
183
+ When seeding into these three tables:
184
+
185
+ - **Verify the block is actually free** — run both `SELECT MAX(id)` *and*
186
+ `SELECT id FROM … WHERE id BETWEEN <lo> AND <hi>`. Confirm whether a number you were handed means
187
+ **"next free"** or **"highest used"**; reading one as the other has already caused a live
188
+ collision.
189
+ - **Use a plain `INSERT`, never `INSERT IGNORE`.** `IGNORE` turns a primary-key collision — the one
190
+ error that must halt the migration — into a silent mis-wiring. Put idempotency in a `NOT EXISTS`
191
+ guard on a *semantic* key, or in a paired `UPDATE`.
192
+
193
+ Blocks in use, the authoring house style, and the collision incident:
194
+ [surface-layer-schema](../apps/dbchanges2/features/surface-layer-schema.md).
195
+
196
+ ### A committed migration records INTENT, not deployed state
197
+
198
+ **Never infer what an environment contains by reading `dbchanges2`. Query the environment.**
199
+
200
+ There is no automated executor. A human applies each `.sql` per environment, and where the
201
+ isolation rule bites — a client-side file that needs a Core id — the operator resolves the id
202
+ against the target's Core and **hand-writes the literal statement at run time**. Those
203
+ transpositions have historically **not been written back to the repo**. The consequence is
204
+ permanent and structural, not a backlog item:
205
+
206
+ - A file in the repo may never have run anywhere.
207
+ - A file that *did* run may have run in an edited form that exists nowhere in version control.
208
+ - Deployed rows can therefore have no committed file that would reproduce them.
209
+
210
+ So before authoring anything whose correctness depends on current state — an override, a
211
+ correction, an `INSERT … NOT EXISTS` guard, an id block — **establish that state with read-only
212
+ `SELECT`s against the target environment's read replicas.** Connect using the `readhost` keys of
213
+ the `[database]` and `[databaseClient]` groups in `api2/Config/production.ini`; never copy
214
+ credential values into a doc, a ticket, a commit or a chat message.
215
+
216
+ **The hook cannot save you here.** `dbchanges2-cluster-isolation.js` is a static check on the text
217
+ of a file, so it catches a cross-database *reference* at authoring time. It cannot know whether a
218
+ file ever ran, ran in edited form, or matches the target environment. Cluster isolation is
219
+ mechanically enforced; state verification is **on you**.
220
+
221
+ Worked examples of both failure modes — a file unrunnable in prod, and prod rows with no
222
+ corresponding committed file — are in
223
+ [surface-layer-schema](../apps/dbchanges2/features/surface-layer-schema.md) and
224
+ [surface-resolver](../apps/_underscore/features/surface-resolver.md).
225
+
164
226
  ## Checking dependencies before touching shared code
165
227
 
166
228
  `dependsOn` in `knowledge/registry.json` means a repo extends or depends on another repo's classes. Before modifying a class in a dependency repo (e.g. `_underscore` core):
@@ -230,6 +292,22 @@ and not how the convention works. Use a non-`c_` column when you *do* want the f
230
292
  ACL-governed field treatment.
231
293
 
232
294
  ## Change history
295
+ - 2026-08-28 — Extended § Schema migrations with the production-state rules. Recorded that cluster
296
+ isolation is **physical** (verified: the prod Core reader exposes only Core/Forecast/
297
+ Forecast_Archive/Team, the client reader only `Client_*`), so a cross-database reference cannot
298
+ resolve rather than merely being denied — and that neither a local nor a beta run is evidence of
299
+ prod-runnability, since every non-prod environment is all-in-one. Added a **bounded exemption**
300
+ to the hardcoded-id rule for the Surface tables only (`Core.Surfaces`/`SurfaceElements`/
301
+ `Messages` carry reserved literal blocks because `Client_*` override files must reference Core
302
+ ids across the cluster split and cannot resolve them at run time) — **explicitly not a general
303
+ precedent**; the platform rule that only `Core.Records`/`Core.RecordFields` have team-maintained
304
+ ids is unchanged. Attached the seeding safeguards to that exemption (verify the block is free
305
+ with `MAX(id)` *and* a `BETWEEN` check; "next free" vs "highest used"; plain `INSERT`, never
306
+ `INSERT IGNORE`). New subsection: **a committed migration records intent, not deployed state** —
307
+ there is no automated executor, operators hand-transpose Core ids at run time and historically
308
+ did not write them back, so current state must be established with read-only queries against the
309
+ target environment, and the cluster-isolation hook catches cross-database references but
310
+ **cannot** catch state drift. (apeterson)
233
311
  - 2026-07-29 — Documented the `c_`-prefixed framework-dynamic column convention (schema-only,
234
312
  no model-class/RecordFields/ACL declaration), generalized from the transcript AI-model routing
235
313
  work. (ajean)
@@ -0,0 +1,134 @@
1
+ ---
2
+ type: session
3
+ slug: nycdoe-phase1-sftp
4
+ title: NYCDOE 2.0 Phase 1 — SFTP Ingestion Code-Level Plan (TRUE-80519)
5
+ author: mhammontree
6
+ repos: [dbchanges2, _underscore, worker2, worker]
7
+ framework: "both"
8
+ client: nycdoe
9
+ status: active
10
+ created: 2026-08-28
11
+ updated: 2026-08-28
12
+ ---
13
+
14
+ # Session: nycdoe-phase1-sftp
15
+ **Date:** 2026-08-28
16
+ **Project/Repo:** dbchanges2, _underscore, worker2 (2.0) + worker (1.0)
17
+ **Task:** Following Jeff's scoping decision (Phase 1 = the SFTP ingestion portion of the NYCDOE
18
+ 2.0 rewrite, TRUE-80519), produced a code-level Phase 1 design document for Mark's review meeting
19
+ with Jeff, then resolved repo/branch scope and local database setup requirements to start
20
+ implementation against `Client_Nycdoe` (the only current client receiving Lenovo ASN files, with
21
+ real production examples to test against). Continues directly from the
22
+ `2026-08-28-nycdoe-2-0-integration-mhammontree` session saved earlier today.
23
+
24
+ ---
25
+
26
+ ## What WORKED
27
+
28
+ - **Produced `C:\Users\mhammontree\.claude\plans\phase-1-sftp-ingestion-plan.md`** — a full
29
+ code-level design doc: real table DDL (`SftpCredentials`, `SftpManufacturerImports`), a worker2
30
+ class/method sketch (`_Worker_Sftp_Discover::ForManufacturer`/`::ForClient`) grounded in the
31
+ confirmed-real `Startech.php` credential-resolution pattern, cron registration SQL, dbchanges2
32
+ module scope, the 1.0→2.0 SFTP bridge plan, explicit security requirements (secrets by reference,
33
+ never raw columns), and a scale section for the confirmed 1000+ client target.
34
+ - **Confirmed via code read**: `_underscore\Model\Client\File.php` already exists as an established
35
+ Model — meaning `Files` is very likely already a registered `/v2` API Record, so adding the new
36
+ `c_status`/`c_partnerId`/`c_dtProcessed` custom fields is a `dbchanges2` data change, not an
37
+ `api2` code change. This directly answered whether `api2` needed a working branch (it doesn't,
38
+ for Phase 1).
39
+ - **Confirmed via code read**: existing S3-upload precedent already lives in `worker2`
40
+ (`Worker/Team/Transcripts.php`, `Worker/Client/Compass/VipSupport.php`) — real code to reuse/adapt
41
+ for the SFTP-to-S3 backup step, not a new S3 component to invent from scratch.
42
+ - **Confirmed the §2.5 open question from the Phase 1 doc**: every client has its own genuinely
43
+ separate SFTP server and credentials — no shared-server-with-per-client-subfolder topology.
44
+ Settles the `SftpCredentials` table as `Client_<Name>`-scoped (as originally designed) and
45
+ confirms the real scale problem is up to 1000+ genuinely separate SFTP connections per
46
+ manufacturer cron tick — updated the plan doc's §2.5 and §7 accordingly (previously "open
47
+ question," now "confirmed design constraint").
48
+ - **Resolved which repos need working branches for Phase 1**, reasoned from the plan doc's actual
49
+ scope, not guessed: `dbchanges2`, `_underscore`, `worker2`, and 1.0 `worker` (for the temporary
50
+ bridge script change) — NOT `api2` (see above), NOT 1.0 `dbchanges` (the bridge change is pure
51
+ PHP logic in an existing 1.0 script, not a schema/data migration).
52
+ - **Resolved local database requirements for starting `Client_Nycdoe` work**: `Core_2` (local-
53
+ renamed 2.0 Core) + `Client_Nycdoe` + **`Logs_Nycdoe` (confirmed necessary, not optional)** +
54
+ `Archive_Nycdoe` (parity, unexercised by Phase 1). The `Logs_Nycdoe` requirement is directly tied
55
+ to a real platform gotcha: `_ApiRequest` logging silently no-ops (or errors "Unknown database")
56
+ unless the Logs database is registered as `DB_CLIENT_LOGS` — load-bearing because this whole
57
+ design's rationale for writing through the API instead of direct model writes is to get logging
58
+ for free, so it has to actually be verifiable locally.
59
+ - **Located the exact local-db-refresh workflow doc** for confirming all four databases are present
60
+ and consistent: `C:\Users\mhammontree\toga-tech\knowledge\2.0\apps\_underscore\workflows\local-db-refresh-from-beta.md`
61
+ (in the **team knowledge repo**, not inside the `_underscore` codebase itself — worth being
62
+ precise about this, since the two are easy to conflate).
63
+ - Both plan-file updates (Phase 1 doc §2.5/§7 revision, new §8 "Getting started" section) were made
64
+ **before** this save, per this session's own established discipline of writing durable decisions
65
+ to disk before compacting/running low on context.
66
+
67
+ ## What did NOT work — DO NOT RETRY THESE
68
+
69
+ - None this session — this was a clean continuation with no rejected approaches. (The prior
70
+ session, `2026-08-28-nycdoe-2-0-integration-mhammontree`, has the real "did not work" list from
71
+ the broader design work — S3-vs-SFTP-bridge confusion, the ledger-table-vs-custom-fields
72
+ correction, the SO/PO grain flip-flop. Nothing from that list was revisited or retried here.)
73
+
74
+ ## Not tried yet (candidates for next session)
75
+
76
+ - Actually verify `files` is a registered `Core.Records` route in `api2` (the plan doc treats this
77
+ as "very likely true, worth a quick sanity check" — not yet directly confirmed).
78
+ - Read `Worker/Team/Transcripts.php` and `Worker/Client/Compass/VipSupport.php` in full to extract
79
+ the actual reusable S3-upload pattern (only confirmed they exist via grep, not yet read in depth).
80
+ - Determine the exact secrets-resolution mechanism for `SftpCredentials.secretRef` (Secrets Manager
81
+ vs. SSM Parameter Store, and which helper/component — if any — already exists for reading either
82
+ from PHP; the plan doc's `_Component_Secrets::resolve()` call is a placeholder name, not confirmed
83
+ real).
84
+ - Confirm `phpseclib`'s SFTP client supports a server-side `rename()`/move (for the 1.0 bridge
85
+ script) vs. requiring a full download+reupload — still an open item carried from the prior session.
86
+ - Run the `local-db-refresh-from-beta.md` workflow against `Client_Nycdoe` and confirm all four
87
+ local databases (`Core_2`, `Client_Nycdoe`, `Logs_Nycdoe`, `Archive_Nycdoe`) actually exist and
88
+ are current — Mark suspected he already has them all but had not yet confirmed at end of session.
89
+ - Everything else still open from the prior session's "not tried yet" list remains open and
90
+ unaffected by this session's work (SO/PO grain confirmation with NetSuite/accounting, ASN file
91
+ format consistency across manufacturers/clients, §4.1 PO-suffix problem, Incidents/INCs entirely
92
+ deferred, worker-tier concurrency load estimate for 1000+ simultaneous SFTP connections).
93
+
94
+ ## Current file state
95
+
96
+ | File | Status | Notes |
97
+ |------|--------|-------|
98
+ | `C:\Users\mhammontree\.claude\plans\phase-1-sftp-ingestion-plan.md` | Created and revised this session | New file: full code-level Phase 1 design (table DDL, worker2 class sketch, cron SQL, dbchanges2 scope, security requirements, scale analysis). Revised §2.5/§7 from "open question" to "confirmed" once the per-client-SFTP-topology fact was confirmed. New §8 "Getting started" section added (repos/branches, local DB setup) just before this save. |
99
+ | `C:\Users\mhammontree\.claude\plans\true-80519-this-is-a-sunny-riddle.md` | Unchanged this session | Still pending the update noted as "next step" in the prior session's save (Case→ServiceRequest model, `Core.Partners` mechanism, Phase 1/scale requirement) — not done in this session either; carries forward as outstanding. |
100
+ | `C:\Users\mhammontree\.claude\plans\true-80519-file-mapping.md` | Unchanged this session | Same — still pending the no-Ticket-model revision noted previously. |
101
+ | `C:\Users\mhammontree\.claude\plans\2.0-generic-sftp-netsuite-integration-pattern.md` | Unchanged this session | Same — still pending the `Core.Partners`/`PartnerApiIdentities` correction noted previously (still references the earlier speculative "Integrations table" idea). |
102
+ | No code files | Unchanged | Still pure planning/design; no implementation started yet. Branches are cut but empty of new work as of this session. |
103
+
104
+ ## Decisions made
105
+
106
+ - **Start Phase 1 implementation with `Client_Nycdoe`** — rationale: the only current client
107
+ receiving Lenovo ASN files, with abundant real production examples already reviewed this session
108
+ to test against. No alternative client seriously considered for the starting point.
109
+ - **Branch `dbchanges2`, `_underscore`, `worker2`, and 1.0 `worker`; do NOT branch `api2`** —
110
+ rationale: traced directly against the plan doc's actual scope rather than assumed; `api2`
111
+ excluded because adding custom fields to an already-existing `File` Model is a data change, not
112
+ an API code change. Rejected: branching `api2` "just in case" (Mark's original instinct/question)
113
+ — deferred until a real need surfaces.
114
+ - **`SftpCredentials` stays `Client_<Name>`-scoped** (not `Core`-scoped) — rationale: confirmed
115
+ every client has genuinely separate SFTP servers/credentials per manufacturer, no shared-server
116
+ topology to design around. This was the single open question flagged as most likely to reshape
117
+ the Phase 1 doc, and it resolved in favor of the design as originally written.
118
+ - **`Logs_Nycdoe` is a required local database, not optional** — rationale: the design's core
119
+ reason for writing through the API (vs. direct model writes) is to get logging for free; that
120
+ can't be verified locally without the Logs database registered and present.
121
+
122
+ ## Blockers
123
+
124
+ None.
125
+
126
+ ## Exact next step
127
+
128
+ > Verify `phpseclib`'s SFTP client supports a server-side `rename()` for the 1.0 bridge script
129
+ > (carried over, still unconfirmed), then run the `local-db-refresh-from-beta.md` workflow to
130
+ > confirm all four local databases (`Core_2`, `Client_Nycdoe`, `Logs_Nycdoe`, `Archive_Nycdoe`) are
131
+ > present and current before writing the first `dbchanges2` migration for `_modules/sftp-import/`.
132
+
133
+ ---
134
+ _Saved by /session-save on 2026-08-28_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.686",
3
+ "version": "1.0.688",
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",