toga-ai 1.0.52 → 1.0.54

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.
@@ -24,10 +24,10 @@ knowledge/
24
24
  ├── registry.json # repo ↔ project ↔ framework ↔ role ↔ dependsOn
25
25
  ├── INDEX.md # auto-generated master index
26
26
  ├── 1.0/
27
- │ ├── apps/<repo>/architecture.md + features/*.md
27
+ │ ├── apps/<repo>/architecture.md + features/*.md + workflows/*.md
28
28
  │ └── standards/*.md # 1.0 coding standards (App_)
29
29
  ├── 2.0/
30
- │ ├── apps/<repo>/architecture.md + features/*.md
30
+ │ ├── apps/<repo>/architecture.md + features/*.md + workflows/*.md
31
31
  │ └── standards/*.md # 2.0 coding standards (_underscore)
32
32
  └── clients/<client>/
33
33
  ├── profile.md
@@ -83,13 +83,33 @@ declares the files it covers, so the doc indexes into the code.
83
83
 
84
84
  ## Document types
85
85
 
86
+ The knowledge base is organized by **subject** (a capability, a process, a component), never
87
+ by session or ticket. `capture` assigns the type — **developers never classify.**
88
+
86
89
  - **architecture** — how a repo/framework works overall (one per repo). _Elevated._
87
- - **feature** — a discrete, shared capability or repeatable workflow within a repo.
88
- - **client-feature** a client's customization/override of a shared feature; links
89
- back to the shared `apps/<repo>/features/<base>.md`.
90
- - **workflow** a client business process.
90
+ - **feature** — a capability anchored to specific code in a repo (a model, a class, a set of
91
+ files). Its template carries a **How it works** section, so the *process* of a feature lives
92
+ inside the feature doc — most features contain a process, and that does not make them a
93
+ workflow. Lives in `apps/<repo>/features/`.
94
+ - **workflow** — a standalone, multi-step process that **no single capability owns**; it spans
95
+ systems/actors/time. Two homes: operational/system processes in `apps/<repo>/workflows/`
96
+ (e.g. a deploy or reconciliation procedure), and client business processes in
97
+ `clients/<client>/workflows/`.
98
+ - **client-feature** — a client's customization/override of a shared feature; links back to
99
+ the shared `apps/<repo>/features/<base>.md`.
91
100
  - **standard** — a coding standard for a framework. _Elevated._
92
101
 
102
+ **Choosing the type (capture's rule — default to feature):**
103
+ - "Can I point at one capability/model/class that *is* this?" → **feature** (its process goes
104
+ in the *How it works* section).
105
+ - "Is this a sequence of steps spanning systems/actors that is a *procedure*, not a thing?" →
106
+ **workflow.**
107
+ - When in doubt → **feature.** Defaulting prevents workflow-file sprawl.
108
+
109
+ **Bugs are not a type.** A bug fix's durable lesson attaches as a **gotcha** (or a one-line
110
+ *Change history* entry) on the feature/workflow doc that owns the affected code — never a
111
+ standalone doc. Only a recurring/systemic failure class warrants its own record, case by case.
112
+
93
113
  _Elevated_ docs (architecture, standard) are senior-owned; `capture` flags any change
94
114
  to them with ⚠ so the developer knows what they're approving.
95
115
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
- | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php |
5
+ | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql |
6
6
  | [Compass: Item Fulfillments for Sales Order Items TableView (tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | The Compass TableView `item-fulfillments-for-sales-order-items` (`TableViews.id = 13` in Client_Compass) was rebuilt as part of the tracking-number bridge migra | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql |
7
7
  | [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
@@ -5,10 +5,12 @@ project: _Underscore
5
5
  client: compass-usa
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-08
8
+ updated: 2026-06-11
9
9
  owners: [jcardinal]
10
10
  files:
11
11
  - _underscore/Model/Compass/AdvanceShippingNotice.php
12
+ - api2/Component/Api/Cxml/Cxml.php
13
+ - dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql
12
14
  related:
13
15
  - ../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md
14
16
  ---
@@ -21,41 +23,81 @@ IFIUs for serialized units, and IF packages for tracking. ASNs arrive two ways:
21
23
  **cXML** (direct V2 API) and the worker email cron
22
24
  `3b_import_strategic_systems_advance_shipping_notices.php`.
23
25
 
26
+ Tracking numbers are propagated across **every** ASN and IF granularity (see *Tracking number
27
+ propagation* below), so a single ShipNotice tracking number lands at the header, item, and
28
+ unit level on both sides — not just on one unit.
29
+
24
30
  ## Key files / entry points
31
+ - `api2/Component/Api/Cxml/Cxml.php` — the cXML `ShipNoticeRequest` translator that builds
32
+ the `/advance-shipping-notices` POST payload.
25
33
  - `_underscore/Model/Compass/AdvanceShippingNotice.php` — `postPost(&$api, &$payload)`.
26
34
  - Fires only when the Compass ASN `ApiPayloadInterceptors` rows exist (interceptor is DB-driven).
35
+ - `Model/Compass/Usa/` and `Model/Compass/Canada/` ASN classes are **empty subclasses** that
36
+ inherit `postPost` from `_Model_Compass_AdvanceShippingNotice` — edit the parent.
27
37
 
28
38
  ## How it works
29
39
  1. Read ASN → resolve PO → SO via `SalesOrders_PurchaseOrders`; skip if no SO.
30
40
  2. Select ASN items whose SOI still has unfulfilled qty (`SOI.quantity > SUM(IFI.quantity)`).
31
41
  3. Reuse the IF if one already exists with the SO number, else POST a new IF.
32
- 4. Per ASN item: POST an IFI (qty = ASN item qty), then read its ASN units.
33
- 5. Per ASN unit: if it has a Unit (serialized) POST an IFIU; if it is tracking-only
34
- (`unitId IS NULL`)collect its tracking number for an IF package instead.
42
+ 4. Per ASN item: POST an IFI (qty = ASN item qty); then copy that ASN item's tracking numbers
43
+ (`AdvanceShippingNoticeItems_TrackingNumbers`) onto the IF item via
44
+ `POST /item-fulfillment-item-tracking-numbers` → `ItemFulfillmentItems_TrackingNumbers`.
45
+ 5. Per ASN unit: if it has a Unit (serialized) → POST an IFIU (carrying unit tracking); if it
46
+ is tracking-only (`unitId IS NULL`) → collect its tracking number for an IF package instead.
35
47
  6. Create one IF package per unique tracking number (header-level tracking merged with
36
48
  tracking-only ASN-unit tracking, deduped by tracking-number uuid).
37
49
 
50
+ ## Tracking number propagation
51
+ The cXML translator maps each `ShipControl` block's tracking number to a line via its
52
+ `LineNumber` Extrinsic (it iterates **all** ShipControls and **all** ShipNoticeItems, not just
53
+ `[0]`), then attaches the number at every level. End state for a cXML ShipNotice:
54
+
55
+ | Table | Populated by |
56
+ |---|---|
57
+ | `AdvanceShippingNotices_TrackingNumbers` | cXML payload (ASN header) |
58
+ | `AdvanceShippingNoticeItems_TrackingNumbers` | cXML payload (matched line's item) |
59
+ | `AdvanceShippingNoticeItemUnits_TrackingNumbers` | cXML payload (tracking-only unit) |
60
+ | `ItemFulfillments_TrackingNumbers` | `postPost` IF package |
61
+ | `ItemFulfillmentItems_TrackingNumbers` | `postPost` (copies ASN-item tracking) |
62
+ | `ItemFulfillmentItemUnits_TrackingNumbers` | `postPost` — **only when serialized units exist** |
63
+
64
+ Fix-forward only (decided 2026-06-11): applies to new ShipNotices; existing records are not
65
+ backfilled.
66
+
38
67
  ## Data model
39
68
  - `AdvanceShippingNoticeUnits.unitId` is nullable (tracking-only units have NULL).
40
69
  - `ItemFulfillmentItemUnits.unitId` is **NOT NULL** — a tracking-only unit can never be an
41
- IFIU; its tracking belongs on an `ItemFulfillmentPackage`.
42
- - Compass tracking lives in `ItemFulfillmentPackages` (header-level); IFIUs are only for
43
- genuinely serialized units.
70
+ IFIU; its tracking belongs on an `ItemFulfillmentPackage` / `ItemFulfillments_TrackingNumbers`.
71
+ So for a tracking-only ShipNotice (no serialized units, the Office Depot norm) the IFIU
72
+ bridge legitimately stays empty — there are no IF item units to attach to.
73
+ - Tracking bridge Records (Core): ASN = 216 (unit) / 217 (item) / 218 (header);
74
+ IF = 317 (header) / 318 (item) / 319 (unit). All six are registered inherent children of
75
+ their parents, so the nested payload keys are accepted with no metadata migration.
44
76
 
45
77
  ## Client variations
46
78
  Compass USA handler (`Model/Compass/`), extending `_Model_Client_AdvanceShippingNotice`.
47
- Compass Canada (`Model/Compass/Canada/`) is a separate sub-client.
79
+ Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass cXML API
80
+ (Core `ClientApiIdentities` identity `153531108`, clientId 2) holds roles 1 and 3.
48
81
 
49
82
  ## Gotchas / known issues
83
+ - **Record 217 ACL gap (fixed 2026-06-11):** `AdvanceShippingNoticeItems_TrackingNumbers`
84
+ (Record 217) had **zero** `AclRecordPermissions` and its `advanceShippingNoticeItemId`
85
+ (RecordField 1440) / `trackingNumberId` (1441) fields had **zero** `AclFieldPermissions`,
86
+ so ASN-item-level tracking POSTs were rejected (`EZ-1`). `dbchanges2/Client_Compass/2026-06-11
87
+ - AsnItemTrackingNumberAcl.sql` grants role 3 CRUD + role 7 read at the record level and
88
+ roles 1/3/4 writable at the field level, mirroring sibling Record 216. **This SQL must be
89
+ executed against the Compass DB before the cXML change works end-to-end.**
50
90
  - **Tracking-only ASN units (the majority — ~56k of ~101k in prod):** before 2026-06-08 the
51
91
  IFIU query used `INNER JOIN Units`, silently dropping NULL-`unitId` rows, so cXML shipments
52
92
  produced an IF + IFI with **no IFIU and no package — tracking was lost**. Fixed: `LEFT
53
93
  OUTER JOIN Units` + route tracking-only units to IF packages. Historical records (e.g.
54
- SO SA132502 / IF 66997) are **not backfilled** — fix-forward only; a one-time repair is
55
- still an open decision.
94
+ SO SA132502 / IF 66997) are **not backfilled** — fix-forward only.
56
95
  - Interceptor is **DB-driven**: `postPost` does nothing without the Compass
57
96
  `ApiPayloadInterceptors` rows in that env.
58
97
  - The IF is named after the SO number; an already-existing IF with that number is reused.
98
+ - **cXML SQL-injection hardening (2026-06-11):** the `ShipNoticeRequest` path interpolated the
99
+ cXML `Sender` Identity / SharedSecret and the OrderReference `orderID` (purchase order
100
+ number) straight into queries. These are now passed through `_Database::escape()`.
59
101
  - Separate latent bug in the 1.0 worker: the Strategic Systems cron's no-serials branch
60
102
  builds `$itemLevelTrackingNumbers` but never attaches it to the ASN payload.
61
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.52",
3
+ "version": "1.0.54",
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",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: capture
3
- description: End-of-session knowledge writer for TOGA Technology projects. Run this at the END of a coding session to record what you worked on into the team `claude` knowledge base. It figures out what changed this session, matches it against existing knowledge, and proposes create/update/retire changes for one-tap approval — then writes them, keeping registry.json, frontmatter, and INDEX files consistent. Trigger whenever the user says "capture", "wrap up", "save my work to the knowledge base", "I'm done — record this", or finishes a feature and wants it documented.
3
+ description: End-of-session knowledge writer for TOGA Technology projects. Run this at the END of a coding session to record what you worked on into the team `claude` knowledge base. It figures out what changed this session, matches it against existing knowledge, and proposes create/update/retire changes for one-tap approval — then writes them, keeping registry.json, frontmatter, and INDEX files consistent. Run at the END of EVERY session — not only for finished features but also for small bug fixes and "I just changed the way something works"; capture decides what is durable (usually a small UPDATE to an existing doc, sometimes a NO-OP). Trigger whenever the user says "capture", "wrap up", "save my work to the knowledge base", "I'm done — record this", or ends a session.
4
4
  ---
5
5
 
6
6
  # Capture — record this session's work into the team knowledge base
@@ -110,9 +110,38 @@ node "<TEAM_REPO>/knowledge.js" search --framework=<fw> --repo=<repo> --file=<to
110
110
  node "<TEAM_REPO>/knowledge.js" search --framework=<fw> --repo=<repo> --q=<keywords>
111
111
  ```
112
112
 
113
- Classify each item as **CREATE** (no doc yet), **UPDATE** (a doc exists and needs new
114
- content / a new `files:` entry / a gotcha), or **DEPRECATE/DELETE** (the doc describes
115
- something now removed or obsolete).
113
+ Classify each item as **CREATE** (no doc yet), **UPDATE** (a doc exists and needs new content /
114
+ a new `files:` entry / a gotcha / a *Change history* line), **DEPRECATE/DELETE** (the doc
115
+ describes something now removed or obsolete), or **NO-OP** (nothing durable — see below).
116
+
117
+ ### Always run; capture decides what (if anything) is durable
118
+
119
+ `/capture` runs at the end of **every** session — including small bug fixes and "I just changed
120
+ the way something works," not only finished features. The KB is organized by **subject** (a
121
+ capability, a process, a component), **not** by session or ticket, so capturing small work is
122
+ safe: it almost always **UPDATES one existing subject doc**, and rarely creates one.
123
+
124
+ **Guardrails against pollution (enforce these):**
125
+ - **One doc per subject** — never one per change, bug, or ticket.
126
+ - **Bias UPDATE over CREATE** — always search for an owning doc first; CREATE only when no
127
+ subject doc exists. A small "how it works" change UPDATES the feature's *How it works* section
128
+ and adds a dated one-line *Change history* entry, in place — it does not spawn a new file.
129
+ - **Relevance test** — for every candidate write ask: *"Will a teammate six months from now act
130
+ differently because this is written?"* If no → **NO-OP**.
131
+ - **NO-OP is a blessed, first-class outcome.** If nothing rises to durable knowledge (typo,
132
+ formatting, a fix obvious from the code), write nothing and report: "Nothing here is durable
133
+ team knowledge — KB unchanged." Never manufacture a doc to justify the run.
134
+
135
+ **Type assignment (you assign it — developers never classify; default to feature):**
136
+ - Anchored to one capability/model/class? → **feature** (its process → the *How it works* section).
137
+ - A standalone multi-step process no single capability owns? → **workflow**
138
+ (`apps/<repo>/workflows/` for operational/system processes; `clients/<client>/workflows/` for
139
+ client business processes).
140
+ - A client's override of a shared feature? → **client-feature**.
141
+ - A reversible team-wide rule/decision? → **standard** (⚠ elevated).
142
+ - A bug's durable lesson? → a **gotcha** or *Change history* line on the doc that owns the
143
+ affected code — **never its own doc.**
144
+ - When in doubt → **feature.**
116
145
 
117
146
  ## Step 4 — Build the proposal (one-tap approval)
118
147
 
@@ -217,27 +246,36 @@ How behavior differs per client (or "none — uniform").
217
246
  ## Gotchas / known issues
218
247
  The non-obvious things future-you will trip on.
219
248
 
249
+ ## Change history
250
+ Dated one-liners, newest first — notable behavior changes only. Keep it terse; this is not a
251
+ git log. Cap at ~10 entries; fold older ones away.
252
+ - YYYY-MM-DD — <what changed and why> (<author>)
253
+
220
254
  ## Related docs
221
255
  Links.
222
256
  ```
223
257
 
224
- ### workflow (client business process)
258
+ ### workflow (a standalone process — operational OR client business)
259
+ Two homes: `apps/<repo>/workflows/` for operational/system processes (set `repo`,
260
+ `client: shared`); `clients/<client>/workflows/` for client business processes (set the
261
+ `client` slug, omit `repo`).
225
262
  ```markdown
226
263
  ---
227
264
  title: <Workflow Name>
228
265
  framework: "<1.0|2.0>"
266
+ repo: <repo> # for apps/<repo>/workflows/; omit for a pure client workflow
229
267
  project: <Project>
230
- client: <client-slug>
268
+ client: <shared|client-slug>
231
269
  type: workflow
232
270
  status: active
233
271
  updated: <YYYY-MM-DD>
234
272
  owners: ["<AUTHOR_USERNAME>"] # from Step 1b — never empty
235
- files: []
273
+ files: [] # source files the process touches, if any
236
274
  related: []
237
275
  ---
238
276
 
239
277
  ## Summary
240
- The business process and who it serves.
278
+ The process, who/what it serves, and what triggers it.
241
279
 
242
280
  ## Steps
243
281
  1. …
@@ -247,6 +285,10 @@ Apps/repos, integrations, data.
247
285
 
248
286
  ## Edge cases & escalation
249
287
  What can go wrong and who handles it.
288
+
289
+ ## Change history
290
+ Dated one-liners, newest first. Keep terse; cap at ~10.
291
+ - YYYY-MM-DD — <what changed and why> (<author>)
250
292
  ```
251
293
 
252
294
  ### architecture (one per repo) — ⚠ ELEVATED