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-
|
|
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