toga-ai 1.0.469 → 1.0.470

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: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-28
9
+ updated: 2026-07-29
10
10
  owners: ["mhammontree"]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
@@ -66,8 +66,39 @@ fact AIG **does** post live entitlements — the writes were **failing (HTTP 500
66
66
  core `Logs`**, not `Logs_Aig`. See
67
67
  [AIG entitlement intake → Intake is a live API feed](../../../../clients/aig/features/entitlement-intake.md).
68
68
 
69
+ ## Recovering an intake backlog from logged payloads
70
+
71
+ Because the core/writer `Logs.Api` stores **every** inbound request — including failed writes —
72
+ each row retains the **complete original `requestPayload`** (full contact / device / pricing /
73
+ coverage). That makes the log a **replayable source of truth** for a client's dropped-intake
74
+ backlog and a diagnostic for *which* upstream programs actually attempted intake:
75
+
76
+ - **Rebuild the backlog** by replaying the stored `requestPayload`s, **deduped to one row per
77
+ contract** via a MySQL 8 window function keyed on
78
+ `JSON_UNQUOTE(JSON_EXTRACT(requestPayload,'$.number'))` (many failed attempts exist per contract
79
+ when a feed re-sends — see below).
80
+ - **Determine which programs/vendors reached the API** with a split on
81
+ `JSON_UNQUOTE(JSON_EXTRACT(requestPayload,'$.vendor.number'))` — a vendor present in the failed
82
+ writes attempted intake; a vendor entirely absent never routed to api2 at all.
83
+
84
+ This is both a **fallback recovery path** and a **scope diagnostic**. In the TRUE-80179 AIG
85
+ incident it answered the scope question — the failed writes contained only vendor `STS_001` and
86
+ **zero `SA_001`**, proving `SA_001` never reached api2 — while the *replay* itself was ultimately
87
+ **not needed**, because AIG's scheduled feed re-sent the outstanding contracts and self-healed the
88
+ backlog once intake succeeded. Keep the replay technique as the fallback for feeds that do **not**
89
+ re-send.
90
+
91
+ > **Never copy a logged row's values into a doc or a replay artifact you commit** — these rows
92
+ > carry live Bearer tokens (in `requestHeaders`) and customer PII. Reference the row by location;
93
+ > replay operationally, not by pasting payloads into the KB.
94
+
69
95
  ## Change history
70
96
 
97
+ - 2026-07-29 — Documented (TRUE-80179) that the core/writer `Logs.Api` retains the **full original
98
+ `requestPayload`** on failed writes, making it a **replayable backlog source** and a scope
99
+ diagnostic: dedupe to one-per-contract with a window function on
100
+ `JSON_EXTRACT(requestPayload,'$.number')`, and split on `$.vendor.number` to see which programs
101
+ actually attempted intake. (mhammontree)
71
102
  - 2026-07-28 — Refined (TRUE-80179): a **failed write (non-2xx `POST`/`PUT`) lands in the core/writer
72
103
  `Logs`** on `api-writer.togahub.com` **even when the request is fully client-scoped** — not just
73
104
  pre-scope auth failures. `Logs_<Client>.Api` therefore shows reads + successful writes; failed
@@ -2,6 +2,6 @@
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
- | [AIG Contract Reconciliation & Dealer Programs (STS_001 / SA_001)](features/contract-reconciliation.md) | 2.0 | How to reconcile an **AIG contract sales sheet** against the `Client_Aig` tenant, and the durable finding that the **Staples Advantage.com (`SA_001`) program is | _underscore/Model/Client/Entitlement.php, _underscore/Model/Aig/Entitlement.php, _underscore/Model/Client/Contact.php, _underscore/Model/Aig/Contact.php |
5
+ | [AIG Contract Reconciliation & Dealer Programs (STS_001 / SA_001)](features/contract-reconciliation.md) | 2.0 | How to reconcile an **AIG contract sales sheet** against the `Client_Aig` tenant, and the durable finding that the **Staples Advantage.com (`SA_001`) program do | _underscore/Model/Client/Entitlement.php, _underscore/Model/Aig/Entitlement.php, _underscore/Model/Client/Contact.php, _underscore/Model/Aig/Contact.php |
6
6
  | [AIG Entitlement Intake & SaleItem Code Resolution](features/entitlement-intake.md) | 2.0 | The `_Model_Aig_Entitlement::prePost` interceptor below is the code path for AIG protection-plan entitlements arriving as `api2` V2 JSON POSTs — and that path * | _underscore/Model/Aig/Entitlement.php, dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql |
7
7
  | [AIG (Staples Protection Plan)](profile.md) | 2.0 | AIG is the warranty underwriter behind the **Staples Protection Plan** retail program. | |
@@ -5,7 +5,7 @@ project: API
5
5
  client: aig
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-07-28
8
+ updated: 2026-07-29
9
9
  owners: ["mhammontree"]
10
10
  files:
11
11
  - _underscore/Model/Client/Entitlement.php
@@ -20,17 +20,18 @@ related:
20
20
  ## Summary
21
21
 
22
22
  How to reconcile an **AIG contract sales sheet** against the `Client_Aig` tenant, and the
23
- durable finding that the **Staples Advantage.com (`SA_001`) program is entirely absent from
24
- `Client_Aig`** (TRUE-80179). AIG runs **two dealer programs under the one `Client_Aig`
25
- tenant**, distinguished by `DealerId` in AIG's own data:
23
+ durable finding that the **Staples Advantage.com (`SA_001`) program does not route into
24
+ `Client_Aig` via api2 at all** (TRUE-80179). AIG runs **two dealer programs under the one
25
+ `Client_Aig` tenant**, distinguished by `DealerId` in AIG's own data:
26
26
 
27
- | DealerId | Program |
28
- |---|---|
29
- | `STS_001` | **Staples Technology Solutions** |
30
- | `SA_001` | **Staples Advantage.com** |
27
+ | DealerId | Program | Status |
28
+ |---|---|---|
29
+ | `STS_001` | **Staples Technology Solutions** | live api2 feed; intake gap **RESOLVED 2026-07-29** (self-healed) |
30
+ | `SA_001` | **Staples Advantage.com** | **never reaches api2** — separate feed; scope decision is the one open item |
31
31
 
32
- `STS_001` arrives as a **live API feed** (and had a large gap from a now-fixed intake bug — see
33
- below); `SA_001` was never ingested at all (open scope question).
32
+ `STS_001` arrives as a **live API feed**; its 156-contract gap from the now-fixed intake bug
33
+ **self-healed** (the feed re-sent and all 156 succeeded — see below). `SA_001` never reaches our
34
+ API at all — a confirmed fact, not a missing-intake bug (see the `SA_001` section).
34
35
 
35
36
  ## Match keys (sales sheet → `Client_Aig`)
36
37
 
@@ -70,41 +71,62 @@ and dropping every contract that used an `ASI-*` code. Cause: the TRUE-79534 `AS
70
71
  query threw MySQL ERROR 1064 → 500. Full chain in
71
72
  [entitlement-intake → Root cause of the 500s](entitlement-intake.md#root-cause-of-the-intake-500s-true-79534-catalog-never-deployed).
72
73
 
73
- **Fix status (2026-07-28):** the catalog (~2,200 codes) is now loaded on prod, so **new** `STS_001`
74
- POSTs succeed. **Still open — do NOT mark resolved:**
75
- - **(a) Back-fill of already-dropped contracts.** Contracts that 500'd are **not** created
76
- retroactively — back-fill needs **AIG to re-send** them, or a **manual/`dbchanges2` load**.
77
- Quantify the exact set by re-running this reconciliation against a **fresh prod copy**.
78
- - **(b) Unguarded empty-`$itemId` in `prePost`** — a still-missing code will 500 again (ERROR 1064)
79
- instead of returning a clean "Missing AIG item ID." Latent until guarded.
80
- - **(c) Deploy process gap** — investigate *why* the migration SQL step was skipped in the deploy,
81
- so future `dbchanges2` changes aren't silently missed.
74
+ **RESOLVED 2026-07-29 — the 156 self-healed.** After the catalog (~2,200 codes) loaded on prod, AIG's
75
+ **next scheduled batch re-sent all 156 outstanding `STS_001` contracts and every one succeeded**
76
+ (`POST /v2/entitlements` = 201 × 156, zero failures; `Client_Aig.Entitlements` gained exactly 156
77
+ rows). **No manual back-fill was needed** — the feed re-sends OUTSTANDING contracts every run, so
78
+ the backlog cleared itself once intake worked (see
79
+ [entitlement-intake → scheduled re-send batch](entitlement-intake.md#feed-is-a-scheduled-re-send-batch-not-one-shot--self-healing)).
80
+ The `STS_001` intake failure mode is **RESOLVED in prod.**
81
+
82
+ **Still open (tickets, not doc work):**
83
+ - **(a) Unguarded empty-`$itemId` in `prePost`** — a still-missing sale-item code will 500 again
84
+ (ERROR 1064) instead of returning a clean "Missing AIG item ID." Latent until guarded.
85
+ - **(b) Deploy process gap** — investigate *why* the TRUE-79534 migration SQL step was skipped in
86
+ the deploy, so future `dbchanges2` changes aren't silently missed.
82
87
 
83
- ### 545 `SA_001` missing — still an open scope question
88
+ ### 545 `SA_001` missing — confirmed out-of-pipeline (separate feed)
84
89
 
85
90
  The entire `SA_001` (Staples Advantage.com) feed is **absent from `Client_Aig`** — confirmed
86
91
  absent from **all** local tenant DBs, not just this one — and is a **separate** matter from the
87
92
  `STS_001` catalog bug above.
88
93
 
94
+ **CONFIRMED (2026-07-29): `SA_001` never reaches our API at all.** The failed-POST logs and the
95
+ vendor split (on `$.vendor.number`) show **only `STS_001`** (100 unique failed contracts) and
96
+ **zero `SA_001`** across **all** response codes. So the 545 `SA_001` contracts (506 active) are a
97
+ **genuinely separate feed that does not route into `Client_Aig` via api2** — this is **not** a
98
+ missing-intake bug on our side. The long-open scope question is now a **decided fact**: `SA_001` is
99
+ out-of-pipeline. Do **not** re-investigate whether it is a missing-intake defect.
100
+
89
101
  **Real support failure that surfaced it:** a customer (client contract `7679463206-2`, AIG
90
102
  contract `1000046237903`, an **active 2YR Printer** plan) called in and could not be found,
91
103
  because their `SA_001` contract was never ingested.
92
104
 
93
- **Open scope question (still unresolved):** is `SA_001` *supposed* to route into `Client_Aig` at
94
- all, or is it a separate program with its own destination? This needs an answer from AIG / the
95
- team before back-filling — do not assume the gap is simply a missed load.
105
+ **The one remaining open item (a ticket/business decision, not a bug):** *should* `SA_001` flow
106
+ into `Client_Aig`, and if so via what feed/integration? That is a scope/integration decision for
107
+ AIG / the team — not a back-fill of a dropped load. Until decided, `SA_001` stays out-of-pipeline.
96
108
 
97
109
  ## How to load a missing program / sheet (back-fill)
98
110
 
99
- If `SA_001` (or a back-fill of dropped `STS_001` contracts) is confirmed in-scope for
100
- `Client_Aig` and AIG cannot re-send via the API, it can be loaded the same way the SaleItem
101
- catalog is — a `dbchanges2/Client_Aig/` migration with a session-scoped temp table + anti-join
111
+ If `SA_001` is ever confirmed in-scope for `Client_Aig` and AIG cannot route it through api2, it
112
+ can be loaded the same way the SaleItem catalog is — a `dbchanges2/Client_Aig/` migration with a
113
+ session-scoped temp table + anti-join
102
114
  (see [entitlement-intake.md → Uploading new codes](entitlement-intake.md#uploading-new-codes-the-recurring-task);
103
- **pin the temp column's collation** to avoid the prod-only ERROR 1267). Preferred, though, is
104
- letting AIG **re-send** the contracts through the now-working live feed.
115
+ **pin the temp column's collation** to avoid the prod-only ERROR 1267). Note the `STS_001` backlog
116
+ did **not** need this — a scheduled re-send self-healed it once intake worked, so prefer letting a
117
+ feed re-send over a manual load whenever the contracts do reach api2.
105
118
 
106
119
  ## Change history
107
120
 
121
+ - 2026-07-29 — TRUE-80179 / TRUE-79534: **156 active `STS_001` RESOLVED (self-healed).** The next
122
+ scheduled batch re-sent all 156 outstanding contracts and every one succeeded (201 × 156, zero
123
+ failures; `Client_Aig.Entitlements` +156) — no manual back-fill needed, because the feed re-sends
124
+ outstanding contracts every run. Also **confirmed `SA_001` never reaches api2**: the failed-POST
125
+ logs / vendor split show only `STS_001` (100 unique failed) and zero `SA_001` across all codes, so
126
+ the 545 `SA_001` (506 active) are a genuinely separate out-of-pipeline feed — converting the old
127
+ scope question into a decided fact (not a missing-intake bug). Remaining open items are tickets:
128
+ the `SA_001` scope/integration decision, the unguarded `prePost` lookup, and the skipped-migration
129
+ deploy gap. (mhammontree)
108
130
  - 2026-07-28 — TRUE-80179 / TRUE-79534: root-caused the **156 missing active `STS_001`**. These are
109
131
  a **live API feed** whose writes were 500'ing (ERROR 1064) and being dropped because the
110
132
  TRUE-79534 `ASI-*`/`SM-*` catalog never deployed to prod `Client_Aig.Items` — corrects the earlier
@@ -5,7 +5,7 @@ project: API
5
5
  client: aig
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-07-28
8
+ updated: 2026-07-29
9
9
  owners: ["mhammontree"]
10
10
  files:
11
11
  - _underscore/Model/Aig/Entitlement.php
@@ -25,13 +25,17 @@ The `_Model_Aig_Entitlement::prePost` interceptor below is the code path for AIG
25
25
  protection-plan entitlements arriving as `api2` V2 JSON POSTs — and that path **is a live
26
26
  production feed.** AIG posts real customer contracts to `POST /v2/entitlements` on
27
27
  `api-writer.togahub.com` (authenticated as client `Aig`, vendor `STS_001`) using the current
28
- `ASI-*` / `SM-*` sale-item codes. Until 2026-07-28 that feed was **silently failing with HTTP
29
- 500** because `Client_Aig.Items` was missing the entire `ASI-*`/`SM-*` catalog (the TRUE-79534
30
- load never ran on prod), so every posted contract was dropped at intake (see
31
- [Intake is a live API feed](#intake-is-a-live-api-feed-and-was-silently-failing-true-80179--true-79534)
32
- and [Root cause of the 500s](#root-cause-of-the-intake-500s-true-79534-catalog-never-deployed),
33
- TRUE-80179 / TRUE-79534). The historical **430** `Client_Aig` entitlements were a one-time
34
- Sept-2025 bulk load — **that part stands** — but AIG has since switched to the live API feed.
28
+ `ASI-*` / `SM-*` sale-item codes. From before 2026-07-28 until the catalog was loaded, that feed
29
+ was **silently failing with HTTP 500** because `Client_Aig.Items` was missing the entire
30
+ `ASI-*`/`SM-*` catalog (the TRUE-79534 load never ran on prod), so every posted contract was
31
+ dropped at intake. **RESOLVED 2026-07-29** — once the catalog loaded, AIG's next scheduled batch
32
+ delivered all **156** outstanding `STS_001` contracts and **every one succeeded** (`POST
33
+ /v2/entitlements` = **201 × 156**, zero failures; `Client_Aig.Entitlements` gained exactly 156
34
+ rows). **No manual back-fill was needed — the feed self-healed** (see
35
+ [Intake is a live API feed](#intake-is-a-live-api-feed-resolved-2026-07-29-true-80179--true-79534)
36
+ and [Feed is a scheduled re-send batch](#feed-is-a-scheduled-re-send-batch-not-one-shot--self-healing)).
37
+ The historical **430** `Client_Aig` entitlements were a one-time Sept-2025 bulk load — **that part
38
+ stands** — but AIG has since switched to the live API feed.
35
39
 
36
40
  On a V2 POST, before the entitlement is written, `prePost` resolves the **sold warranty SKU**
37
41
  (`saleItem.partNumber`) against `Client_Aig.Items` to set the `Entitlement.saleItemId` foreign
@@ -40,7 +44,7 @@ the lookup returns an **empty `$itemId`**, the downstream fulfillment query inte
40
44
  empty value, and it dies with MySQL **ERROR 1064** → **HTTP 500** (see the gotcha). Keeping
41
45
  `Client_Aig.Items` populated with AIG's current sale-item catalog is what keeps the feed working.
42
46
 
43
- ## Intake is a live API feed (and was silently failing) (TRUE-80179 / TRUE-79534)
47
+ ## Intake is a live API feed (RESOLVED 2026-07-29) (TRUE-80179 / TRUE-79534)
44
48
 
45
49
  The API-intake path in *How it works* is the **real, live feed.** An earlier capture wrongly
46
50
  concluded "no live feed — periodic bulk load only." That conclusion came from auditing
@@ -65,10 +69,34 @@ What the corrected audit found (TRUE-80179):
65
69
  contracts in [reconciliation](contract-reconciliation.md), and it kept losing contracts **daily**
66
70
  until the catalog was loaded on 2026-07-28.
67
71
 
72
+ **Resolution (2026-07-29):** once the catalog was loaded (TRUE-79534) and the temp-table collation
73
+ fixed, AIG's **next scheduled batch cleared the entire backlog in one pass** — `Logs_Aig` shows
74
+ `POST /v2/entitlements` = **201 × 156** on 2026-07-29 with **zero failures** in the core/writer
75
+ `Logs`, and `Client_Aig.Entitlements` gained exactly the **156** previously-missing active
76
+ `STS_001` rows. **No manual log-replay was needed.** The prior "intake 500s / Missing AIG item ID"
77
+ failure mode is **RESOLVED in prod for the STS_001 side**, which is now healthy and self-maintaining
78
+ (see next section for *why* it self-healed).
79
+
68
80
  **Practical takeaway:** AIG intake is a **live API feed whose health must be monitored** — not a
69
81
  manual bulk load, and not a phantom "no feed." When auditing it, check the **core/writer `Logs`**
70
82
  for non-2xx writes, not just `Logs_Aig`. For reconciling a sales sheet against the tenant (and the
71
- still-open `SA_001` program gap), see [AIG contract reconciliation](contract-reconciliation.md).
83
+ one remaining `SA_001` scope decision), see [AIG contract reconciliation](contract-reconciliation.md).
84
+
85
+ ## Feed is a scheduled re-send batch (not one-shot) → self-healing
86
+
87
+ **Correction of an interim read.** AIG's entitlement feed is a **scheduled batch that re-sends
88
+ OUTSTANDING / unacknowledged contracts every run** — it is **not** one-shot-per-contract, and it
89
+ does **not** "not retry." Supersedes any earlier note implying the feed does not retry.
90
+
91
+ Evidence: the same ~156 contracts were **re-POSTed on every run** and 500'd on the missing catalog —
92
+ that is what produced **~110,504 failed attempts for only ~100 distinct contract numbers** in the
93
+ logs. The moment the catalog resolved, the **very next scheduled run cleared all 156 in a single
94
+ pass** (156 × 201).
95
+
96
+ **Operational consequence:** a persistent intake bug is **silently retried, not dropped**, until it
97
+ is fixed — so a catalog/intake fix **auto-recovers the whole backlog on the next scheduled run**
98
+ with no manual back-fill. This is the key operational fact for the next incident: fix the intake
99
+ cause and let the feed re-send.
72
100
 
73
101
  ## Root cause of the intake 500s (TRUE-79534 catalog never deployed)
74
102
 
@@ -86,10 +114,12 @@ The full failure chain behind the HTTP 500s:
86
114
  ongoing daily, until the catalog was loaded (2026-07-28).
87
115
 
88
116
  The empty-`$itemId` is **unguarded** (see the gotcha): a missing code should fail cleanly with
89
- "Missing AIG item ID," not a raw SQL syntax error. This defect is now **confirmed firing in prod**,
90
- not theoretical. Loading the catalog fixes intake **going forward**; contracts already dropped are
91
- **not** retroactively created — back-fill is tracked in
92
- [contract reconciliation](contract-reconciliation.md).
117
+ "Missing AIG item ID," not a raw SQL syntax error. This defect was **confirmed firing in prod**,
118
+ not theoretical, and the **unguarded lookup remains open** (a future missing code will 500 again).
119
+ Loading the catalog fixed intake, and because the feed re-sends outstanding contracts (see
120
+ [scheduled re-send batch](#feed-is-a-scheduled-re-send-batch-not-one-shot--self-healing)), the
121
+ **backlog of dropped contracts self-healed on the next scheduled run** — no manual back-fill was
122
+ needed (see [contract reconciliation](contract-reconciliation.md)).
93
123
 
94
124
  ## Key files / entry points
95
125
 
@@ -245,6 +275,16 @@ this interceptor or use this dual-purpose Items pattern.
245
275
 
246
276
  ## Change history
247
277
 
278
+ - 2026-07-29 — TRUE-80179 / TRUE-79534 (**RESOLVED + correction**): the `STS_001` intake gap is
279
+ **closed and it self-healed.** After the catalog load + collation fix shipped, AIG's next
280
+ scheduled batch delivered all **156** outstanding `STS_001` contracts and every one succeeded
281
+ (`POST /v2/entitlements` = 201 × 156 on 2026-07-29, zero failures; `Client_Aig.Entitlements`
282
+ +156 rows). **No manual replay needed.** Also corrected the feed model: it is a **scheduled batch
283
+ that re-sends OUTSTANDING/unacknowledged contracts every run** (the ~156 re-POSTs on the missing
284
+ catalog produced ~110,504 failed attempts for only ~100 distinct contracts), so a persistent
285
+ intake bug is silently retried until fixed and then **auto-recovers on the next run** — supersedes
286
+ any earlier "no retry" implication. The unguarded empty-`$itemId` lookup and the skipped-migration
287
+ deploy gap remain open. (mhammontree)
248
288
  - 2026-07-28 — TRUE-80179 / TRUE-79534 (**CORRECTION — supersedes the entry below**): AIG intake
249
289
  **is** a live production API feed, and it was **silently failing with HTTP 500.** The earlier
250
290
  same-day "no live feed / bulk load only" conclusion was **wrong** — it audited `Logs_Aig` alone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.469",
3
+ "version": "1.0.470",
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",