toga-ai 1.0.511 → 1.0.513

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.
@@ -49,6 +49,26 @@ happening and were deliberately never ticketed. Nobody had that view before.
49
49
  Filesystem routing (1.0 Tools MVC): `mvc/errors/` = **`/errors`** (the listing) and
50
50
  `mvc/errors/issue/` = **`/errors/issue`** (the detail page).
51
51
 
52
+ **`/errors/issue` takes two parameters, one meaning each** (2026-08-04):
53
+
54
+ | Parameter | Means | Use |
55
+ | --- | --- | --- |
56
+ | `?issue=4KP` | `Issue.reference` — the identifier everything else quotes (ClickUp title, escalation email, neglect digest, production error page) | **all new links**, including the listing rows and worker2's `buildToolsIssueUrl()` |
57
+ | `?issueId=9` | `Issue.id` | the original form, kept working **indefinitely** |
58
+
59
+ `?issueId=` is never removed: links built from it are already sitting in ClickUp tasks and emails
60
+ sent months ago that cannot be rewritten.
61
+
62
+ > **Why two parameters instead of one that accepts either.** References are **base 32 over an
63
+ > alphabet that includes digits**, so an ordinary reference — `'68'`, `'137'`, `'400'` — is all
64
+ > digits and **cannot be distinguished from a primary key by inspection**. A combined parameter
65
+ > would have to guess; naming the parameter after the column removes the ambiguity. An earlier
66
+ > combined `?issue=` with reference-first/id-fallback precedence was written and then replaced.
67
+
68
+ `?issue=` also accepts the **event** form (`?issue=4KP-12` strips the event half), because that is
69
+ what people copy out of an error page or ClickUp comment. An unknown reference leaves the id at 0
70
+ and renders the existing "no such issue" view.
71
+
52
72
  **Listing** — 25 per page with a filtered `COUNT` (it was previously a silent `LIMIT 200`, so
53
73
  older issues simply did not exist as far as a curator could tell); bold monospace Issue ID; an
54
74
  **EB Environment** column read from the newest event's context; a **Type** column showing
@@ -75,6 +95,22 @@ curated text, affected clients) right, with an always-present Type chip.
75
95
  - Header chip order is **Issue / Environment / Type / Urgency** — Type was moved ahead of Urgency so
76
96
  urgency finishes the row at the far right.
77
97
 
98
+ ### Neglect indicator (2026-08-04)
99
+
100
+ Both pages now surface the **neglect** contribution to urgency: the listing computes
101
+ `openTaskAgeHours` as a correlated subquery and renders a `$describeNeglect` amber note
102
+ (`.neglect-note`, `#8a5a00`) **under the urgency badge**; the detail page's *Acknowledged* fact row
103
+ reads "not yet — waiting 3 days" alongside a NORMAL/HIGH neglect chip.
104
+
105
+ **Why:** the urgency badge alone is genuinely misleading. A **HIGH beside "Lifetime 1"** is a
106
+ statement about *response time*, not about the error, and nothing on the page let a reader tell
107
+ those apart. **Nothing renders while the clock is running but under 24h** — below that the clock
108
+ contributes nothing to urgency, so saying "neglect" would overstate it.
109
+
110
+ **Deliberately scoped to the neglect contribution only.** The console never claims *which* axis is
111
+ winning: computing the full driving axis in Tools would mean copying the volume, sustained and
112
+ release-band thresholds too, and that much duplicated policy would rot.
113
+
78
114
  ### Table presentation rules (established 2026-08-04)
79
115
 
80
116
  **Stacked datetimes in every table on both pages** — listing *Last seen*; detail *Events → When*,
@@ -143,6 +179,17 @@ They were renamed from plural on 2026-08-01, after the pipeline was already in p
143
179
 
144
180
  ## Gotchas / known issues
145
181
 
182
+ - **⚠ OPEN BUG (found 2026-08-04, deliberately left unfixed pending the developer's go-ahead):
183
+ `tools/mvc/errors/post.php` ~line 181, the merge-fingerprint action, will merge into the WRONG
184
+ issue.** It takes a reference **or** a numeric id in **one** field and disambiguates with
185
+ `ctype_digit()`. That was safe under the old **base-26** scheme, where every reference contained a
186
+ letter. Under the new **base-32** alphabet an all-digit reference like `'68'` is treated as **id
187
+ 68** and silently repoints the fingerprint at the wrong Issue. Fix is either reference-first-then-id
188
+ precedence or two separate inputs (as `/errors/issue` now does).
189
+ - **The 24/48 neglect thresholds are duplicated as plain numbers in both Tools views.** Tools is
190
+ 1.0 and cannot read worker2's `NEGLECT_NORMAL_HOURS` / `NEGLECT_HIGH_HOURS`; both sites carry a
191
+ comment naming those constants as the source of truth. **Changing the policy means changing three
192
+ places** (worker2 + both Tools views).
146
193
  - **A 2.0 table rename breaks this page silently until it is run.** This console has **no model
147
194
  layer** — `get.php` and `post.php` interpolate table names into hand-written `App_Database`
148
195
  SQL (40+ literal occurrences between them). Nothing here fails at deploy time; it fails when
@@ -170,6 +217,22 @@ They were renamed from plural on 2026-08-01, after the pipeline was already in p
170
217
 
171
218
  ## Change history
172
219
 
220
+ - 2026-08-04 (latest, **uncommitted/undeployed** at time of writing) — **Built:** a **neglect
221
+ indicator** on both pages — listing gained an `openTaskAgeHours` correlated subquery and a
222
+ `$describeNeglect` amber note (`.neglect-note`, `#8a5a00`) under the urgency badge; the detail
223
+ page's *Acknowledged* row now reads "not yet — waiting 3 days" with a NORMAL/HIGH neglect chip.
224
+ The urgency badge alone was misleading (a HIGH beside "Lifetime 1" describes response time, not
225
+ the error) and nothing distinguished the two; nothing renders under 24h since the clock is
226
+ contributing nothing yet. Scoped to the neglect contribution only — computing the winning axis
227
+ would mean duplicating the volume/sustained/release-band thresholds as well. **Built:**
228
+ `/errors/issue` now accepts **`?issue=<reference>`** (also tolerating the `4KP-12` event form) and
229
+ the listing links by reference; `?issueId=<id>` is kept working **indefinitely** for links already
230
+ sitting in old ClickUp tasks and emails. Two parameters rather than one, because base-32
231
+ references can be all digits and are indistinguishable from a primary key. **Recorded two
232
+ maintenance hazards:** the 24/48 thresholds are now duplicated as literals in both Tools views
233
+ (Tools is 1.0 and cannot read worker2's constants — three places to change), and an **open bug** in
234
+ `post.php`'s merge-fingerprint `ctype_digit()` disambiguation, which under base-32 will merge into
235
+ the wrong Issue (flagged, left unfixed). (jcardinal)
173
236
  - 2026-08-04 (later, **uncommitted/undeployed** at time of writing) — UI pass on both `/errors`
174
237
  pages: **stacked datetimes** in every table via a shared `$datetime` closure emitting
175
238
  `.dt`/`.dt-date`/`.dt-time` (a naturally wrapping datetime broke inside the day of the month), and
@@ -364,6 +364,18 @@ in `_underscore` and `library` in the **same** release, and the `/errors` consol
364
364
  `dbchanges2/Logs_Client/2026-08-01a - Drop Error table.sql`. Do not reintroduce it.
365
365
  - **Shared-schema migration collision.** Because these tables live in the *shared* Core Logs
366
366
  DB, only **one** migration may `CREATE` them; later migrations only `ALTER`.
367
+ - **The `dbchanges2/Logs/` migration files LAG the deployed schema — read the live cluster, not the
368
+ migrations.** `dbchanges2/Logs/2026-07-30a - Error reporting Issues and Events.sql` does **not**
369
+ describe the live `Logs.Issue`: notably the identity column is **`issueKey` (varchar 128)**, not
370
+ `code`. Live columns include `reference`, `dtLastOccurred`, `isManaged`, `urgency`,
371
+ `minimumUrgency`, `consecutiveQuietWindows`, `subject`, `errorMessage`, `occurrences`,
372
+ `totalOccurrences`, `recurrenceCount`, `dtLastNotified`, `notificationCount`, `dtAcknowledged`,
373
+ `dtMutedUntil`, `createsClickupTask`, `clickupPriority`, `clickupAssigneeId`. Query
374
+ `information_schema` (or `SHOW TABLES` / `SHOW COLUMNS`) against the live Logs cluster **before**
375
+ writing any SQL against this feature — reading the migrations instead produced confidently wrong
376
+ conclusions twice on 2026-08-04. Same trap for the **singular** table names
377
+ (`Issue`/`Event`/`IssueFingerprint`/`IssueAreaOwner`/`IssueClickupTask`/`IssueEmailAddress`,
378
+ renamed by `2026-08-01a` and confirmed applied in prod).
367
379
  - **Pre-existing committed secrets in this area (not fixed — out of scope, report only).**
368
380
  A ClickUp API token at `_underscore/Component/Api/Clickup/Clickup.php:5`; SES SMTP
369
381
  credentials at `_underscore/Email.php:11-12`; a named individual's Sentry personal access
@@ -418,6 +430,12 @@ clientUserId). **Neither was built.** As built instead:
418
430
 
419
431
  ## Change history
420
432
 
433
+ - 2026-08-04 — **Correction/diagnostic:** recorded that the `dbchanges2/Logs/` migration files lag
434
+ the deployed schema — `2026-07-30a` describes a `code` identity column, but live `Logs.Issue` is
435
+ keyed on **`issueKey` (varchar 128)** — and listed the live column set. Query
436
+ `information_schema` against the live Logs cluster before writing SQL here; trusting the
437
+ migrations (and forgetting the applied singular rename) produced two confidently wrong
438
+ conclusions in one session. No code change. (dfranks)
421
439
  - 2026-08-04 (later, **uncommitted/undeployed** at time of writing) — **Reference encoding reworked
422
440
  to base 32** over `'0123456789ACDEFGHJKMNPQRSTUVWXYZ'` (all digits kept; `O`/`I`/`L`/`B` dropped
423
441
  as impersonators of `0`/`1`/`1`/`8`), replacing the base-26 decimal-counter+letter scheme, with
@@ -18,7 +18,7 @@
18
18
  | [Compass VIP Support Importer (worker2)](features/compass-vip-support-importer.md) | A worker2 action that ingests Compass's quarterly VIP spreadsheet and assigns each VIP user's support technician by setting `Users.c_supportedByUserId` in `Clie | worker2/Worker/Client/Compass/VipSupport.php |
19
19
  | [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
20
20
  | [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
21
- | [Error Escalation Cron (Errors::Escalate → ClickUp / email)](features/error-escalation-cron.md) | `_Worker_Infrastructure_Errors::Escalate` (renamed from `SyncWithClickup`) is the sole owner of **escalation, de-escalation, ClickUp ticketing, reminders, busin | worker2/Worker/Infrastructure/Errors.php, worker2/Worker/Notification/Email.php, worker2/Worker/Notification/EmailTemplate.php, worker2/Worker/Client/True.php, worker2/Worker/Clickup/ErrorTask.php, worker2/Worker/Clickup.php, worker2/Controller/Index.php, worker2/Config/production.ini, _underscore/Model/Core/Logs/Issue.php, dbchanges2/Core/2026-07-30a - Error escalation cron job.sql, dbchanges2/Logs/2026-08-03a - Issue clickupPriority.sql |
21
+ | [Error Escalation Cron (Errors::Escalate → ClickUp / email)](features/error-escalation-cron.md) | `_Worker_Infrastructure_Errors::Escalate` (renamed from `SyncWithClickup`) is the sole owner of **escalation, de-escalation, ClickUp ticketing, reminders, busin | worker2/Worker/Infrastructure/Errors.php, worker2/Worker/Notification/Email.php, worker2/Worker/Notification/EmailTemplate.php, worker2/Worker/Client/True.php, worker2/Worker/Clickup/ErrorTask.php, worker2/Worker/Clickup.php, worker2/Controller/Index.php, worker2/Config/production.ini, _underscore/Model/Core/Logs/Issue.php, dbchanges2/Core/2026-07-30a - Error escalation cron job.sql, dbchanges2/Logs/2026-08-03a - Issue clickupPriority.sql, dbchanges2/Core/2026-08-04a - Error neglect digest cron job.sql |
22
22
  | [Etilize Catalog Item Import & Refresh](features/etilize-catalog-item-import.md) | Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. | worker2/Worker/Etilize/Items.php |
23
23
  | [Etilize Item Translation Import](features/etilize-item-translation-import.md) | The abstract worker class `_Worker_Etilize_ItemTranslations` imports **non-English** item text from Etilize into the client's `ItemTranslations` table. | worker2/Worker/Etilize/ItemTranslations.php |
24
24
  | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Monitors/RateEntitlement.php, worker2/Worker/Notification/Email.php, worker2/Worker/Rate.php, dbchanges2/Core/2026-05-21 - Monitors.sql, dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql |
@@ -7,7 +7,7 @@ client: shared
7
7
  type: feature
8
8
  status: active
9
9
  updated: 2026-08-04
10
- owners: ["jcardinal"]
10
+ owners: ["jcardinal", "dfranks"]
11
11
  files:
12
12
  - worker2/Worker/Infrastructure/Errors.php
13
13
  - worker2/Worker/Notification/Email.php
@@ -20,6 +20,7 @@ files:
20
20
  - _underscore/Model/Core/Logs/Issue.php
21
21
  - dbchanges2/Core/2026-07-30a - Error escalation cron job.sql
22
22
  - dbchanges2/Logs/2026-08-03a - Issue clickupPriority.sql
23
+ - dbchanges2/Core/2026-08-04a - Error neglect digest cron job.sql
23
24
  related:
24
25
  - ../../_underscore/features/error-reporting-issue-event.md
25
26
  - ../../../1.0/apps/tools/features/errors-curation-console.md
@@ -28,6 +29,7 @@ related:
28
29
  - ./clickup-project-routing.md
29
30
  - ./notification-email.md
30
31
  - ./notification-email-template.md
32
+ - ../../../clients/compass-usa/features/mits-sales-order-transmission-alerting.md
31
33
  ---
32
34
 
33
35
  ## Summary
@@ -46,13 +48,93 @@ through this one action, so there is exactly one place to mute or debug alerting
46
48
  - **Volume** — occurrences in a **1-hour** window: **NORMAL 5 / HIGH 10 / URGENT 20** (retuned
47
49
  2026-08-04 from 20/50/100 over 10 minutes — a 10-minute window never fired for the
48
50
  low-and-steady failures that actually matter).
49
- - **Neglect** — hours an *unacknowledged* ClickUp task has sat: **1 / 4 / 24**.
51
+ - **Neglect** — hours an *unacknowledged* ClickUp task has sat: **NORMAL 24 / HIGH 48. Capped at
52
+ HIGH — there is no URGENT rung.**
50
53
  - **Sustained-24h** — an issue still occurring a day later escalates on duration alone.
51
54
  - **Floor** — the Issue's declared `minimumUrgency` applies under *all* axes.
52
55
 
53
56
  The neglect axis exists because volume alone cannot see a rare-but-serious error, nor a serious
54
57
  error that everyone is ignoring.
55
58
 
59
+ #### ⚠ Neglect is capped at HIGH and slowed to 24h/48h (decided + fixed 2026-08-04)
60
+
61
+ `NEGLECT_NORMAL_HOURS` 1 → **24**, `NEGLECT_HIGH_HOURS` 4 → **48**, and
62
+ **`NEGLECT_URGENT_HOURS` and its branch were removed** from `resolveNeglectUrgency()`. This
63
+ **supersedes** the earlier 1/4/24 ladder with an URGENT rung.
64
+
65
+ **Why:** neglect answers *"is anyone on this"* — a question about the **team's response**, never
66
+ about how much damage the error is doing. Only the volume axes measure damage, so only they may
67
+ claim URGENT. Left uncapped on the old clock, any error that fired once and went unacknowledged
68
+ for an afternoon became HIGH, then URGENT the next day. That is how the queue filled up:
69
+ **48 of 68 issues sat at HIGH**, and issue **3H (id 60) was HIGH off a single lifetime
70
+ occurrence** — volume 0 in the 60-minute window, sustained 1 against a threshold of 50,
71
+ `minimumUrgency` LOW, with the entire HIGH coming from its ClickUp task sitting unacknowledged
72
+ for 4.93 hours.
73
+
74
+ **The neglect axis is a FLOOR, not a ceiling.** `resolveNeglectUrgency()` is one of four inputs
75
+ to `highestUrgency()`, which takes the **max** — so a neglected issue that starts firing still
76
+ escalates on volume: two days unacknowledged contributes HIGH, and 20 occurrences in the window
77
+ take it straight to URGENT regardless. Capping the axis lowers the floor *neglect alone* can
78
+ raise; it takes nothing away from the occurrence count. Upward moves apply the same run (only
79
+ **downward** moves go through the `REQUIRED_QUIET_RUNS` dwell), so volume takeover is same-run.
80
+
81
+ **Intended side effect, accepted:** `SPRINT_PROMOTION_URGENCY` is `'URGENT'`, so with neglect
82
+ capped at HIGH, **neglect alone can no longer promote an issue into the sprint** — only a genuine
83
+ occurrence rate can.
84
+
85
+ **Deploy effect.** All 50 then-HIGH issues had open unacknowledged tasks younger than 24h, so
86
+ their neglect contribution becomes LOW and they recompute from volume/sustained/floor within ~3
87
+ runs of the worker2 deploy. Expect a **burst of ClickUp priority PUTs** on the first runs as
88
+ `clickupPriority` re-syncs.
89
+
90
+ ### Daily neglect digest email (`Infrastructure/Errors/NeglectDigest`)
91
+
92
+ A second worker action — `public NeglectDigest()`, with `loadNeglectedIssues()` and
93
+ `buildNeglectDigestBody()` — registered in `Core.CronJobs` as action
94
+ `Infrastructure/Errors/NeglectDigest` at **`0 8 * * *`** (08:00 CT: after the overnight cleanup
95
+ jobs, before the working day), `maxExecutionTime` 300. Migration:
96
+ `dbchanges2/Core/2026-08-04a - Error neglect digest cron job.sql`.
97
+
98
+ **Why a digest exists at all — the governing principle:** neglect means *"nobody is looking"*, so
99
+ **every signal that requires somebody to look is self-defeating.** It is also the necessary
100
+ counterpart to capping the axis: with neglect slowed and capped, an ignored issue no longer
101
+ announces itself by climbing the urgency scale, so something has to come and tell you.
102
+
103
+ Sends via `_Worker::runTask('Notification/EmailTemplate/Send')` with the TOGA Technology stored
104
+ template — the **same path as the technical escalation email**. Recipient is `[_underscore]
105
+ error_escalation_email`, gated by the same `error_escalation_enabled` key, so non-production mails
106
+ nobody. The body is a **fragment** for the template's `{body}` (600px cell; the template owns font
107
+ and width), exactly like `buildTechnicalEmailBody()`.
108
+
109
+ Three design decisions, each load-bearing:
110
+
111
+ - **It sends even when the list is empty.** An empty digest states *"nothing is neglected"*;
112
+ silence cannot be distinguished from a broken notifier — which is precisely the failure that hid
113
+ the escalation email being dead for days while every run reported success. The **subject
114
+ reflects the state** ("No neglected error issues" vs "2 error issues nobody has picked up") so
115
+ it is triageable from the inbox list without opening it.
116
+ - **One email covering every issue, never one per issue.** A per-issue alert for a condition that
117
+ by nature affects many issues at once is how a channel becomes wallpaper — and the reminder
118
+ ladder already carries the per-issue nagging.
119
+ - **Floored at `NEGLECT_NORMAL_HOURS`**, not "everything open". That constant is where the cron
120
+ itself starts treating an issue as neglected, so the digest reports only things that have
121
+ crossed a line. A task opened this morning is not neglected, it is new. Ordered **oldest
122
+ first** so the worst case is the first line read.
123
+
124
+ Verified against production: the query returns **2 rows** today (1B and 1I, both 24h
125
+ unacknowledged).
126
+
127
+ **Rejected alternatives:**
128
+
129
+ - *Mutating the ClickUp task **title** to say "neglected"* — the title is deliberately just the
130
+ reference and is never rewritten; rewriting churns the activity feed and breaks the
131
+ quote-the-reference contract, and it only helps someone already looking at the task.
132
+ - *A new `Issue` column (`dtNeglectedSince`, `neglectLevel`)* — fully derivable from
133
+ `IssueClickupTask.dtCreated` + `Issue.dtAcknowledged`, so storing it is denormalised state that
134
+ can drift, and it still needs something to read it out.
135
+ - *A ClickUp custom field* — **deferred, not rejected**: it writes every run and duplicates what
136
+ the digest says. Revisit only if the digest proves insufficient.
137
+
56
138
  ### Fast up, slow down
57
139
 
58
140
  Escalation applies **the same run** a band is crossed. De-escalation requires the count to sit
@@ -107,6 +189,15 @@ ClickUp is the acknowledge/resolve system — developers already live there, and
107
189
  Tools would just be ignored. **Any** status change on an error task counts as acknowledgement,
108
190
  which is safe only because the automation never sets status itself.
109
191
 
192
+ > **`dtAcknowledged` is set by a status change OR by an assignee being added** — both, and this was
193
+ > already implemented (confirmed 2026-08-04, no change needed). `worker2/Worker/Clickup.php:856`
194
+ > routes the ClickUp `taskUpdated` webhook into
195
+ > `_Worker_Clickup_ErrorTask::handleAssigneeChange()`, which calls `markAcknowledged()` (and also
196
+ > feeds the per-area ownership learner). Both routes are subject to the 60-second creation grace
197
+ > window below. Production: 8 of 71 issues acknowledged. **Worth stating explicitly, because the
198
+ > acknowledgement definition is what both the neglect axis and the daily digest key off**, and it
199
+ > is not visible from the escalation-cron side.
200
+
110
201
  > **60-second creation grace window (fix, 2026-08-04).** ClickUp fires a status webhook for a
111
202
  > task's *initial* status, so the automation was acknowledging its **own** newly created tasks —
112
203
  > stamping `dtAcknowledged` ~1s after `dtCreated` and permanently disabling the neglect axis for
@@ -175,6 +266,32 @@ healthy — cannot recur silently.
175
266
  - **Occurrences** and **Last Occurrence** custom fields are synced, and task bodies open with a
176
267
  **"Debug in Tools"** deep link.
177
268
 
269
+ #### Tools deep links address the issue by REFERENCE, not id
270
+
271
+ `TOOLS_ISSUE_PATH` changed from `'/errors/issue?issueId='` to **`'/errors/issue?issue='`**, and
272
+ `buildToolsIssueUrl()` now emits the **reference**, `rawurlencode`d, falling back to the id only
273
+ when a row has no reference. **Two parameters, one meaning each** — deliberately not one parameter
274
+ that accepts either:
275
+
276
+ | Parameter | Means | Use |
277
+ | --- | --- | --- |
278
+ | `?issue=4KP` | `Issue.reference` — what everything else quotes (ClickUp title, escalation email, digest, production error page) | **all new links** |
279
+ | `?issueId=9` | `Issue.id` | the original form, kept working **indefinitely** |
280
+
281
+ `?issueId=` stays supported forever because links built from it are already sitting in ClickUp
282
+ tasks and emails sent months ago that cannot be rewritten.
283
+
284
+ > **Why separate parameters rather than one that accepts either.** References are **base 32 over
285
+ > an alphabet that includes digits**, so an ordinary reference — `'68'`, `'137'`, `'400'` — is all
286
+ > digits and **cannot be told apart from a primary key by inspection**. A single combined parameter
287
+ > would have to guess; naming the parameter after the column removes the ambiguity entirely. (An
288
+ > earlier combined `?issue=` with reference-first / id-fallback precedence was written and then
289
+ > replaced by this.)
290
+
291
+ `?issue=` also accepts the **event** form — `?issue=4KP-12` strips the event half and lands on the
292
+ issue — because that is what people copy out of an error page or a ClickUp comment. An unknown
293
+ reference leaves the id at 0 and renders the existing "no such issue" view.
294
+
178
295
  ### Area-level owner learning (observation only)
179
296
 
180
297
  `IssueAreaOwner` learns an owner after **5 in a row** and ships **observation-only** — a hint in
@@ -231,6 +348,12 @@ this controller needs the same treatment.**
231
348
 
232
349
  ## Gotchas / known issues
233
350
 
351
+ - **An issue held at HIGH purely by neglect never de-escalates on its own.**
352
+ `applyDeEscalationDwell()` needs the computed target to sit **below** the stored urgency for
353
+ `REQUIRED_QUIET_RUNS` (3) consecutive runs, but the neglect axis keeps recomputing the target
354
+ **at** the stored level — so the dwell counter never starts. That is why issue 3H showed
355
+ `consecutiveQuietWindows = 0` after five hours of total silence. **Acknowledging the ClickUp task
356
+ is what releases it**, and it then drops within 3 cron runs (~3 min).
234
357
  - **Never send email inline inside this cron's open transaction — enqueue it.** `_Email::send()`
235
358
  registers a second connection and toggles the read host, and a throw from in there was only ever
236
359
  `error_log`'d. Use `_Worker::runTask` so a failure becomes a visible failed `WorkerJobs` row.
@@ -263,6 +386,22 @@ this controller needs the same treatment.**
263
386
  issue gets a **neutral** subject rather than another client's raw error text.
264
387
  - **`IssueEmailAddress.clientId = 0` means "all clients"** and requires an explicit
265
388
  confirmation flag in the Tools console; list views render it as **"ALL CLIENTS"**.
389
+ - **A recipient scoped to a specific `clientId` delivers NOTHING for occurrences recorded without
390
+ client attribution.** `loadRecentClientIds()` maps an occurrence whose `clientId` is NULL to
391
+ **`0`**, and `resolveRecipientsForClient()` resolves scope `0` **plus** the erroring client. So
392
+ the rule is: scope to **`0`** when the `issueKey` is raised from a **single client-specific code
393
+ path** (a Compass-only cron, say); **never** scope to `0` for an `issueKey` raised from shared
394
+ code, which would leak one client's alerts to another's recipients.
395
+ - **Configuring a recipient is never retroactive.** `shouldNotify()` notifies on first occurrence
396
+ (`notificationCount` 0), on escalation, or when the backed-off reminder interval has elapsed —
397
+ and returns **false when the current window has zero events**. The first email therefore lands on
398
+ the **next live occurrence**, not on the backlog. Do not read silence right after a config change
399
+ as a failure.
400
+ - **Adding a business recipient is purely additive — it cannot switch ClickUp ticketing off.**
401
+ Routing is **both**, not either: `if ($recipients !== null)` emails the business recipients and
402
+ `if ($issue->createsClickupTask)` tickets the dev team, evaluated **independently**. The in-code
403
+ comment marking this records the earlier `elseif` form, where adding one recipient silently
404
+ disabled an issue's dev-team ticket.
266
405
  - **The ClickUp webhook has no HMAC signature verification** (pre-existing, unfixed). A forged
267
406
  POST can mark issues acknowledged — freezing the neglect axis — or resolve episodes. That is
268
407
  alert suppression against the error-reporting system itself. Fix before relying on this
@@ -273,6 +412,38 @@ this controller needs the same treatment.**
273
412
 
274
413
  ## Change history
275
414
 
415
+ - 2026-08-04 (verified against `origin/_production` + the prod Logs cluster) — Recorded the
416
+ **recipient-routing semantics** a developer needs before configuring a business alert: routing is
417
+ **both** business email and dev-team ClickUp, evaluated independently (adding a recipient is
418
+ purely additive); `IssueEmailAddress.clientId` scoping means a client-scoped row delivers nothing
419
+ for occurrences with NULL `clientId` (mapped to `0`), so single-client `issueKey`s scope to `0`
420
+ and shared-code `issueKey`s never do; and `shouldNotify()` makes configuration
421
+ **non-retroactive** — the first email lands on the next live occurrence. Also noted that prod
422
+ `Logs.IssueEmailAddress` was still empty, making Compass's MITS rejection the platform's first
423
+ business-routed issue. No code change. (dfranks)
424
+ - 2026-08-04 (latest, **uncommitted/undeployed** at time of writing) — **Decided + fixed: the
425
+ neglect urgency axis is capped at HIGH and slowed to 24h/48h**, superseding the 1/4/24 ladder
426
+ recorded earlier the same day (`NEGLECT_NORMAL_HOURS` 1→24, `NEGLECT_HIGH_HOURS` 4→48,
427
+ `NEGLECT_URGENT_HOURS` and its branch removed). Neglect measures the **team's response**, not the
428
+ damage the error is doing, so only the volume axes may claim URGENT; uncapped on the old clock a
429
+ single-occurrence error became HIGH in an afternoon and URGENT the next day — 48 of 68 issues were
430
+ at HIGH and issue 3H (id 60) was HIGH off one lifetime occurrence. The axis remains a **floor, not
431
+ a ceiling** (`highestUrgency()` takes the max, so volume still escalates immediately). Accepted
432
+ side effect: `SPRINT_PROMOTION_URGENCY` is URGENT, so neglect alone can no longer promote into the
433
+ sprint. **Built:** a **daily neglect digest** (`Infrastructure/Errors/NeglectDigest`,
434
+ `Core.CronJobs` at `0 8 * * *`, migration `dbchanges2/Core/2026-08-04a`) — the counterpart to the
435
+ cap, since a capped axis no longer announces an ignored issue; it **sends even when empty** (a
436
+ broken notifier is indistinguishable from silence), is **one email for all issues**, and floors at
437
+ `NEGLECT_NORMAL_HOURS`, oldest first. Rejected: rewriting the ClickUp title, a derived
438
+ `dtNeglectedSince`/`neglectLevel` column; deferred: a ClickUp custom field. **Built:**
439
+ `TOOLS_ISSUE_PATH` now emits `?issue=<reference>` (rawurlencoded, id fallback) with `?issueId=`
440
+ kept working indefinitely — separate parameters because base-32 references can be all digits and
441
+ are indistinguishable from a primary key. **Discovered:** assignee-based acknowledgement was
442
+ already implemented (`Clickup.php:856` → `handleAssigneeChange()` → `markAcknowledged()`), and
443
+ that a neglect-held HIGH never de-escalates until the task is acknowledged. Verification:
444
+ `php -l` clean on every changed file; the digest SQL run against production (2 rows); neglect
445
+ impact quantified across all 68 issues; `0 8 * * *` checked against existing daily job
446
+ conventions. (jcardinal)
276
447
  - 2026-08-04 (later, **uncommitted/undeployed** at time of writing) — **Fixed: escalation email had
277
448
  never been sent, ever**, for two independent reasons. (A) `processTechnicalIssue()` returned early
278
449
  when the ClickUp task would read identically, and that return sat *above* the `if ($shouldNotify)`
@@ -39,7 +39,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
39
39
  - **togatech** (TOGA Technology Website) — 4 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
40
40
  - **websocket** (WebSocket Server) — 2 doc(s) → [standalone/apps/websocket/INDEX.md](standalone/apps/websocket/INDEX.md)
41
41
  - **forward** (Forwarder) — 3 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
42
- - **claude** (Claude Harness) — 3 doc(s) → [standalone/apps/claude/INDEX.md](standalone/apps/claude/INDEX.md)
42
+ - **claude** (Claude Harness) — 4 doc(s) → [standalone/apps/claude/INDEX.md](standalone/apps/claude/INDEX.md)
43
43
 
44
44
  ## Clients
45
45
 
@@ -8,6 +8,7 @@
8
8
  | [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql, dbchanges2/Client/2026-07-15a - ExcludeFeeItemsFromItemFulfillmentsForSalesOrdersView.sql |
9
9
  | [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
10
10
  | [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, worker/crons/toga2/compasscanada/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
11
+ | [Compass MITS Sales-Order Transmission — Rejection Alerting (Issue/Event, not email-in-cron)](features/mits-sales-order-transmission-alerting.md) | 2.0 | When MITS **rejects** a Compass sales order transmitted by the 1.0 cron `1_transmit_compass_sales_orders_to_mits.php`, the alert is no longer an email built ins | worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php, worker2/Worker/Infrastructure/Errors.php, _underscore/Model/Core/Logs/Issue.php, _underscore/Model/Core/Logs/IssueEmailAddress.php, dbchanges2/Logs/2026-08-04a - MITS rejection business recipients.sql |
11
12
  | [Compass MR/MA Order Auto-Approval & Status Gate](features/mr-ma-order-approval-and-status.md) | 2.0 | Compass **MR** and **MA** sales orders are system-generated from the MITS / Office Depot EDI pipeline (they do not originate as user-entered SA orders) and must | _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
12
13
  | [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
13
14
  | [Compass Cross-Kit Bundle Corruption — Detection & Repair](workflows/cross-kit-bundle-corruption.md) | 2.0 | A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle (kit) line items and **fees/warranties** to the **wrong kit**, pe | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts |
@@ -0,0 +1,89 @@
1
+ ---
2
+ title: Compass MITS Sales-Order Transmission — Rejection Alerting (Issue/Event, not email-in-cron)
3
+ framework: "2.0"
4
+ project: _Underscore
5
+ client: compass-usa
6
+ type: client-feature
7
+ status: active
8
+ updated: 2026-08-04
9
+ owners: ["dfranks"]
10
+ files:
11
+ - worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php
12
+ - worker2/Worker/Infrastructure/Errors.php
13
+ - _underscore/Model/Core/Logs/Issue.php
14
+ - _underscore/Model/Core/Logs/IssueEmailAddress.php
15
+ - dbchanges2/Logs/2026-08-04a - MITS rejection business recipients.sql
16
+ related:
17
+ - mits-po-transmission-to-vendors.md
18
+ - ../../../2.0/apps/worker2/features/error-escalation-cron.md
19
+ - ../../../2.0/apps/_underscore/features/error-reporting-issue-event.md
20
+ - ../../../1.0/apps/library/features/error-capture-1-0.md
21
+ ---
22
+
23
+ ## Summary
24
+
25
+ When MITS **rejects** a Compass sales order transmitted by the 1.0 cron
26
+ `1_transmit_compass_sales_orders_to_mits.php`, the alert is no longer an email built inside the
27
+ cron. It is a **business exception** raised into the shared Logs Issue/Event pipeline, and its
28
+ recipients are **rows in `Logs.IssueEmailAddress`**.
29
+
30
+ **Consequence for anyone handed a ticket like "add <person> to the MITS error emails": there is
31
+ no PHP to change.** It is a one-row data change delivered as a `dbchanges2/Logs/` migration.
32
+
33
+ ## How it works
34
+
35
+ - The cron raises
36
+ `throw new App_Exception_Business('COMPASS_SALES_ORDER_MITS_TRANSMIT_REJECTED', …, URGENCY_HIGH)`
37
+ on a MITS rejection. (Before 2026-08-03 this block was a hardcoded `new App_Email_Agilant()`
38
+ with `addTo('devteam@togatech.com')`.)
39
+ - Capture creates/dedupes a `Logs.Issue` keyed on the `issueKey`, and the worker2
40
+ `Infrastructure/Errors/Escalate` cron does all outbound notification — business email to the
41
+ configured recipients **and**, independently, the dev-team ClickUp task. See
42
+ [error-escalation-cron](../../../2.0/apps/worker2/features/error-escalation-cron.md).
43
+ - Recipients live in `Logs.IssueEmailAddress` (FK `issueId` → `Logs.Issue`), scoped by
44
+ `clientId` (`0` = all clients).
45
+
46
+ ### Adding a recipient (the correct pattern)
47
+
48
+ Write a `dbchanges2/Logs/YYYY-MM-DD<letter>` migration that:
49
+
50
+ 1. **Looks the issue up by `issueKey`**, never by its `id`. The id is assigned on the issue's
51
+ **first occurrence** and is therefore not stable across environments.
52
+ 2. Uses **`clientId = 0`**. This `issueKey` is raised from a Compass-only code path, and an
53
+ occurrence recorded without client attribution resolves against scope `0` only — a row scoped
54
+ to the Compass `clientId` can deliver nothing. (Never use `0` for an `issueKey` raised from
55
+ shared code — that is a cross-tenant leak.)
56
+ 3. Is **idempotent** via `INSERT … WHERE NOT EXISTS`, mirroring the unique key
57
+ `uq_issue_client_address_type`.
58
+
59
+ Configuration is **never retroactive**: `shouldNotify()` fires on the first occurrence, on
60
+ escalation, or when the backed-off reminder interval elapses, and returns false for a window with
61
+ zero events. The first email lands on the **next live rejection**, not on existing history.
62
+
63
+ ## Gotchas / known issues
64
+
65
+ - **Do not "modernize" this alert back into the cron.** Migrating it to
66
+ `_Model_Client_EmailTemplate::send` (or any in-cron email) reverts the move to the Issue/Event
67
+ pipeline and removes the recipient configuration surface entirely.
68
+ - **Never re-introduce the payload dump into the alert.** The original email included the
69
+ transmission payload, which carries ship-to addressee, attention line, street address and phone
70
+ number. The Issue message is stored in a table **shared across every client**, displayed to
71
+ internal staff in the Tools `/errors` console, pushed to ClickUp, and emailed — so the payload
72
+ was deliberately removed. Alerts here name the order, not the person.
73
+ - **The MITS cron holds its API credentials inline in the file.** Treat that file as
74
+ secret-bearing when copying snippets anywhere (tickets, PR bodies, knowledge docs).
75
+ - **Adding a recipient does not turn ClickUp ticketing off.** Routing is both-and, not
76
+ either-or — see the escalation-cron doc for the `elseif` bug this used to be.
77
+ - As of 2026-08-04 prod `Logs.IssueEmailAddress` is empty, so this is the **first business-routed
78
+ issue on the platform**: `processBusinessIssue()` will execute in production for the first time
79
+ when this issue next fires. Watch that run rather than assuming it works.
80
+
81
+ ## Change history
82
+ - 2026-08-04 — Documented that MITS rejection alerting moved (worker `5521854b`, 2026-08-03) from a
83
+ hardcoded in-cron `App_Email_Agilant` to `App_Exception_Business` +
84
+ `Logs.IssueEmailAddress` recipients, so adding an alert recipient is a data change, not a code
85
+ change. Recorded the deliberate removal of the PII-bearing payload dump from the alert, the
86
+ look-up-by-`issueKey` / `clientId = 0` / `NOT EXISTS` migration pattern, and that recipient
87
+ configuration only takes effect on the next live occurrence. (dfranks)
88
+ </content>
89
+ </invoke>
@@ -16,10 +16,11 @@ project: _Underscore
16
16
  client: compass-usa
17
17
  type: profile
18
18
  status: active
19
- updated: 2026-07-16
20
- owners: [jcardinal, bala, tcox, apeterson]
19
+ updated: 2026-08-04
20
+ owners: [jcardinal, bala, tcox, apeterson, dfranks]
21
21
  files: []
22
22
  related:
23
+ - features/mits-sales-order-transmission-alerting.md
23
24
  - features/asn-to-item-fulfillment.md
24
25
  - features/cost-centers.md
25
26
  - features/approval-decision-flow.md
@@ -75,6 +76,10 @@ separate, related client (see its own profile).
75
76
  `worker/crons/toga2/compass/workflow/3b_import_strategic_systems_advance_shipping_notices.php`.
76
77
 
77
78
  ## Key features (this client)
79
+ - [MITS Sales-Order Transmission — Rejection Alerting](features/mits-sales-order-transmission-alerting.md)
80
+ — MITS rejection alerts are `App_Exception_Business` Issues with recipients in
81
+ `Logs.IssueEmailAddress`, **not** an email in the cron: adding an alert recipient is a
82
+ `dbchanges2/Logs/` data change with no PHP.
78
83
  - [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) — the
79
84
  `_Model_Compass_AdvanceShippingNotice::postPost` handler.
80
85
  - [Cost Centers (Unit Locations, numeric-only)](features/cost-centers.md) — what a Compass
@@ -5,3 +5,4 @@
5
5
  | [Harness Distribution — How a knowledge.js Fix Reaches Teammates](workflows/harness-distribution.md) | There are **two copies** of `knowledge.js` on a developer's machine, and the skills use only one of them. | claude/scripts/install.js, claude/knowledge.js |
6
6
  | [Knowledge Base Publish / Push Pipeline](workflows/knowledge-publish-pipeline.md) | How `/capture` and `/session-save` get knowledge docs from your working tree into `agilantsolutions/claude` on `_main`. | claude/knowledge.js |
7
7
  | [MCP Tool Usage — "Talos" Means the Internal Knowledge Base, and Run Your Own SELECTs](workflows/mcp-tool-usage.md) | Two MCP connections are available in every session, and **Claude is expected to reach for them on its own judgement — nobody should have to say the word "MCP".* | claude/.claude/skills/kickoff/SKILL.md, claude/.claude/skills/capture/SKILL.md |
8
+ | [Ticket-Branch Creation Safety — How /work-ticket Armed Pushes to Production Branches](workflows/ticket-branch-creation-safety.md) | For roughly a month, `/work-ticket` created ticket branches whose **git upstream was the production branch**. | claude/skills/work-ticket/SKILL.md |
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: Ticket-Branch Creation Safety — How /work-ticket Armed Pushes to Production Branches
3
+ framework: "standalone"
4
+ repo: claude
5
+ project: Claude Harness
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-08-04
10
+ owners: ["dfranks"]
11
+ files:
12
+ - claude/skills/work-ticket/SKILL.md
13
+ related:
14
+ - ./harness-distribution.md
15
+ - ../../../2.0/apps/worker2/workflows/ticket-to-pseudocode-planning.md
16
+ ---
17
+
18
+ ## Summary
19
+
20
+ For roughly a month, `/work-ticket` created ticket branches whose **git upstream was the
21
+ production branch**. Claude's own pushes were unaffected, but a developer using VS Code's
22
+ **Commit + Sync** mid-session pushed ticket code straight to `_main`/`_production` — no PR, no
23
+ review. Two such pushes landed in two days before it was found.
24
+
25
+ The mechanism is generic git behavior, so it is worth understanding beyond this one skill.
26
+
27
+ ## Root cause
28
+
29
+ Branch creation was `git checkout -b TRUE-XXXX "origin/$DEFAULT"`. Git's
30
+ **`branch.autoSetupMerge` defaults to `true`**, so branching from a *remote-tracking ref* adopts
31
+ that ref as the new branch's **upstream**. The ticket branch therefore tracked
32
+ `origin/_main` / `origin/_production` until the skill's later `git push -u` re-pointed it.
33
+
34
+ **Trigger asymmetry is why it hid.** Claude pushes with `git push -u origin <TICKET>` — an
35
+ explicit refspec, which ignores the upstream and was always safe. VS Code's Commit + Sync resolves
36
+ the destination **from the upstream**, so any developer commit made before the skill's first push
37
+ went to the production branch.
38
+
39
+ **A safety fix created the exposure.** Commit `6d155f7` (2026-07-09) moved branch creation from
40
+ *after* implementation to *before* any code is written — itself a genuine fix, because a stale
41
+ local `_production` 12 commits behind nearly reverted upstream work. But it turned a seconds-long
42
+ armed window into the **entire implementation phase**. Correctness fixes can lengthen an existing
43
+ window; check what else depends on the timing you moved.
44
+
45
+ ## The fixes
46
+
47
+ 1. **Skill:** branch creation uses **`--no-track`** *and* publishes the empty branch immediately
48
+ (`git push -u origin TRUE-XXXX`), so the branch has a correct upstream before any code exists.
49
+ The later push is a plain `git push origin <TICKET>`.
50
+ 2. **Global git config:** `branch.autoSetupMerge=simple` (a checkout can no longer adopt a
51
+ production upstream, even outside the skill) plus `push.autoSetupRemote=true`.
52
+ 3. **`pre-push` hook** in each app repo rejecting
53
+ `refs/heads/(_main|_production|_beta|_alpha|master)`, override `ALLOW_PROD_PUSH=1`. This also
54
+ catches Claude, because Claude shells out to real `git`.
55
+ 4. **Deny rules** in `.claude/settings.json`.
56
+ 5. Existing branches still tracking a production branch were **disarmed** individually.
57
+
58
+ `rework-ticket` was never affected — it checks out an existing remote branch.
59
+
60
+ ## Recovery: getting an unreviewed commit off an unprotected default branch
61
+
62
+ Do **not** force-push. Instead:
63
+
64
+ 1. **Revert via PR** on the default branch.
65
+ 2. **Rebuild the ticket branch as a NEW commit** on the post-revert base.
66
+
67
+ > **The revert-remerge trap:** if you keep the original commit and merge that branch later, the
68
+ > original commit is already an **ancestor** of the default branch, so the merge is a silent no-op
69
+ > and the change **never comes back**. The revert wins and nothing warns you.
70
+
71
+ ## Gotchas / known issues
72
+
73
+ - **Everything installed here is local and does not survive a fresh clone.** Hooks and git config
74
+ are per-machine; a teammate or a new laptop is unprotected until they redo it.
75
+ - **Standing exposure (not fixed):** `dbchanges2/_main`, `worker/_production`,
76
+ `worker2/_production` and `api2/_production` have **no branch protection and no rulesets**.
77
+ Server-side protection is the only durable fix and needs an admin GitHub account.
78
+ - **Diagnosing this class of incident:** `git reflog` on the remote-tracking branch shows
79
+ `update by push` entries — that is how both incidents were confirmed and how the other four
80
+ repos were cleared. Pre-existing `_beta` pushes are normal manual practice; check dates before
81
+ calling something an incident.
82
+ - Auditing "did this ever happen elsewhere?" requires sweeping **every** repo the skill touches,
83
+ not just the one that surfaced the bug.
84
+
85
+ ## Change history
86
+ - 2026-08-04 — Documented the `branch.autoSetupMerge` root cause, the Claude-vs-VS-Code trigger
87
+ asymmetry that hid it for a month, and the fixes (`--no-track` + immediate branch publish,
88
+ global `autoSetupMerge=simple`, per-repo `pre-push` hook, settings deny rules). Recorded the
89
+ revert-then-rebuild recovery pattern and its revert-remerge no-op trap, plus the standing lack
90
+ of server-side branch protection on four app repos. (dfranks)
91
+ </content>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.511",
3
+ "version": "1.0.513",
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",