toga-ai 1.0.393 → 1.0.394

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: Database Changes
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-07-20
9
+ updated: 2026-07-21
10
10
  owners: [jcardinal, mhammontree, bala]
11
11
  files:
12
12
  - Core/
@@ -173,6 +173,13 @@ its own header.)
173
173
  the relevant clients list `<module>` in their `_modules.txt`.
174
174
  5. Never edit or re-date an already-applied file — add a new dated file instead. Never put new
175
175
  work in a `HISTORIC` folder.
176
+ 6. **Never seed a `uuid` column with MySQL `UUID()`.** The 2.0 standard requires a fully-random
177
+ **v4** UUID; `UUID()` is time/MAC-based (v1) and violates it. Since a migration can't call
178
+ `_String::generateUuid()`, **pre-generate a v4 UUID literal** (e.g. a v4 generator) and
179
+ hardcode the literal string in the `VALUES` clause. One earlier migration
180
+ (`Core/2026-05-08 … CronJobs_WorkerCleanup_insert`) used `UUID()` — that is a known violation,
181
+ not a precedent to copy. First applied correctly in
182
+ `Core/2026-07-21b - Insert - SprintLockScheduled CronJob.sql`.
176
183
 
177
184
  ## Bulk data loads — batch, and stage large sets in a temp table
178
185
 
@@ -285,3 +292,10 @@ platform-wide infrastructure, not a single application. It has no code dependenc
285
292
  folder names mirror the DB families (`Core`, `Logs`, `Client_<Tenant>`, `Logs_<Tenant>`)
286
293
  defined in `2.0/apps/_underscore/architecture.md`, and its change files create/alter the
287
294
  tables that `_Model_*` classes map to.
295
+
296
+ ## Change history
297
+ - 2026-07-21 — Added rule #6 to *Adding a new change*: never seed a `uuid` column with MySQL
298
+ `UUID()` (time/MAC-based v1, violates the 2.0 v4-UUID standard); pre-generate a v4 UUID
299
+ literal and hardcode it in `VALUES`. `Core/2026-05-08 … CronJobs_WorkerCleanup_insert` is a
300
+ known violation, not a precedent; first applied correctly in
301
+ `Core/2026-07-21b - Insert - SprintLockScheduled CronJob.sql`. (jcardinal)
@@ -28,7 +28,7 @@
28
28
  | [Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus programmatically. | .claude/skills/plan-ticket/scripts/talos.js |
29
29
  | [Talos Pricing Automation (worker2 Cron — AWS Actuals, Calibration, Monthly Report)](features/talos-pricing-automation.md) | The worker2 half of the **Talos Pricing Platform** (see the talos `pricing-cogs-model` and tools `talos-pricing-ui` docs for the other halves). | worker2/Worker/Talos/Pricing.php, worker2/Database/TalosPricingCrons.sql |
30
30
  | [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` runs a fully automated, cron-driven pipeline that ingests raw Teams transcripts into the **Talos / TOGa IQ** AWS Bedrock knowledge ba | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Team/2026-06-30a, dbchanges2/Team/2026-06-30b, dbchanges2/Team/2026-06-30c, dbchanges2/Team/2026-06-30d, dbchanges2/Team/2026-06-30e, dbchanges2/Core/2026-06-30a, dbchanges2/Core/2026-07-02a, dbchanges2/Team/2026-07-02a, dbchanges2/Team/2026-07-08a, dbchanges2/Team/2026-07-09a, dbchanges2/Team/2026-07-10a |
31
- | [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php |
31
+ | [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php, dbchanges2/Core/CronJobs (SprintLockScheduled seed) |
32
32
  | [Teams Meeting Transcript Export](features/teams-transcript-export.md) | > **SUPERSEDED (2026-07-09) — the S3-staging model below is history.** `Export` is now a thin > **GRAPH-DIRECT** cron poller: it no longer archives raw VTT to ` | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
33
33
  | [VAPI Webhook Handler (worker2 — AI-BDR end-of-call processing)](features/vapi-webhook-handler.md) | `_Worker_Vapi` ([worker2/Worker/Vapi.php](worker2/Worker/Vapi.php)) is the **PHP side of the AI-BDR call loop** — the webhook that receives VAPI's end-of-call r | worker2/Worker/Vapi.php, worker2/Worker/Ai/Bdr/Vapi.php |
34
34
  | [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
@@ -111,11 +111,29 @@ No Lambda/routing changes — just create the file:
111
111
  2. Insert a `Core.CronJobs` row:
112
112
  ```sql
113
113
  INSERT INTO Core.CronJobs (uuid, isActive, name, schedule, maxExecutionTime, action, parameters)
114
- VALUES (UUID(), 1, 'Daily Report Generation', '0 6 * * *', 300, 'Reports/Daily/Generate', NULL);
114
+ VALUES ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', 1, 'Daily Report Generation', '0 6 * * *', 300, 'Reports/Daily/Generate', NULL);
115
+ -- uuid MUST be a pre-generated v4 UUID literal, NOT MySQL UUID() (v1) — see dbchanges2 architecture.md rule #6
115
116
  ```
116
117
  `schedule` is evaluated in **Central time**; `maxExecutionTime` is the watchdog timeout.
117
118
  3. CronScheduler fires it at the next matching minute — no code wiring needed.
118
119
 
120
+ Adding a recurring job is **pure data**: inserting the `Core.CronJobs` row (uuid, isActive,
121
+ name, schedule, maxExecutionTime, action, parameters) is all that's needed. The CronScheduler
122
+ Lambda runs every minute, matches active schedules via `croniter`, and enqueues a `WorkerJobs`
123
+ job. There is **no Lambda/code change** beyond the row — but the referenced `action` must exist
124
+ in the deployed worker code, so ship the code deploy and the dbchanges2 cron-row migration
125
+ **together**.
126
+
127
+ > **Biweekly / non-cron schedules — run often, self-gate in PHP.** Cron (5-field) can express
128
+ > "every Wednesday" but **not** "every *other* Wednesday" (or any period cron can't name).
129
+ > Don't try to encode it in the schedule. Instead schedule the job at the coarser recurring
130
+ > interval it fits (e.g. `0 10 * * 3`) and have the action **self-gate against a date/state
131
+ > table** at the top of the method — run the real work only when the gate says today qualifies,
132
+ > otherwise return a skip message (captured to `Core.WorkerJobs.output`). Proven by
133
+ > `Team/Sprint/SprintLockScheduled`, which fires every Wednesday but only runs `SprintLock()`
134
+ > when a `Sprints` row ends yesterday — see
135
+ > [team-sprint-management](./team-sprint-management.md).
136
+
119
137
  ## Test any action directly
120
138
 
121
139
  ```bash
@@ -175,6 +193,12 @@ be reattempted.
175
193
  commit-before-SQS transaction pattern that the worker relies on.
176
194
 
177
195
  ## Change history
196
+ - 2026-07-21 — Documented that adding a recurring job is **pure data** (a `Core.CronJobs` row;
197
+ CronScheduler Lambda matches via croniter every minute, no code wiring beyond the row — but
198
+ the action must be deployed), and the **biweekly self-gating pattern**: for schedules cron
199
+ can't express (e.g. every-other-Wednesday), run at a coarser cron interval and gate in PHP
200
+ against a date/state table, returning a skip otherwise. Proven by
201
+ `Team/Sprint/SprintLockScheduled`. (jcardinal)
178
202
  - 2026-07-21 — `WorkerJobs.failureReason` column renamed to **`output`** and repurposed to store
179
203
  successful execution results as well as failure text; success now writes the composed
180
204
  "Successfully Executed" message (return value `json_encode`d for arrays/objects). (jcardinal)
@@ -6,10 +6,11 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-24
9
+ updated: 2026-07-21
10
10
  owners: ["jcardinal"]
11
11
  files:
12
12
  - worker2/Worker/Team/Sprint.php
13
+ - dbchanges2/Core/CronJobs (SprintLockScheduled seed)
13
14
  related:
14
15
  - ../architecture.md
15
16
  - ./creating-worker-actions.md
@@ -64,6 +65,10 @@ A sprint runs ~14 days. Across the cycle these actions fire (scheduled as crons)
64
65
  2. **~9:00 AM** — `SprintEnd` captures and scores the just-finished sprint, emails reports,
65
66
  and (optionally) sends release notes.
66
67
  3. **~9:45 AM (after launch ceremony)** — `SprintLock` freezes the new sprint's plan.
68
+ In production this is now invoked via the scheduled self-gating wrapper
69
+ `SprintLockScheduled` (see below), not by a cron that targets `SprintLock` directly —
70
+ because the launch day is **every other Wednesday** and cron cannot express a biweekly
71
+ schedule.
67
72
  4. **Daily during the sprint** — `SprintDaily` emails leadership a progress dashboard.
68
73
 
69
74
  Most methods accept an optional `$sprint` number; when omitted they resolve the relevant
@@ -89,6 +94,21 @@ alert. With `$generateSprintLockReport`, emits an Excel lock report of committed
89
94
  unplanned/stretch points and tasks. When `$allowUnplannedTasks` is false, tasks appearing
90
95
  after lock with an UNPLANNED work type are flagged.
91
96
 
97
+ ### `SprintLockScheduled(): string`
98
+ A thin **scheduled wrapper around `SprintLock()`** that solves the biweekly-schedule problem.
99
+ The launch/lock ceremony runs every *other* Wednesday, but cron has no biweekly expression, so
100
+ its `CronJobs` row fires **every** Wednesday (`0 10 * * 3`, 10:00 AM Central) and this method
101
+ **self-gates in PHP** against the `Sprints` table: it queries for a sprint whose `dateEnd` =
102
+ **yesterday**. If one exists, today is a new-sprint start day → it calls `self::SprintLock()`.
103
+ Otherwise it returns a skip message (captured to `Core.WorkerJobs.output`) and does nothing.
104
+ `SprintLock()` itself is unchanged — this only decides *whether* to run it today. Action path
105
+ `Team/Sprint/SprintLockScheduled` → `_Worker_Team_Sprint::SprintLockScheduled`.
106
+
107
+ > **Deploy coupling.** The worker2 code deploy and the dbchanges2 migration that seeds the
108
+ > `Core.CronJobs` row must be **released together** — the cron row references the
109
+ > `Team/Sprint/SprintLockScheduled` action, which must exist in the deployed code or the
110
+ > scheduled job fails when it fires.
111
+
92
112
  ### `SprintDaily($sprint = null, …)`
93
113
  In-sprint **leadership dashboard**, read-only with respect to metrics. Optionally re-captures
94
114
  ClickUp data, then builds a multi-sheet workbook:
@@ -210,6 +230,13 @@ backfills `workTypeAtLock` the first time a task is seen).
210
230
 
211
231
  ## Change history
212
232
 
233
+ - 2026-07-21 — Added `SprintLockScheduled()` action + a `Core.CronJobs` seed
234
+ (`0 10 * * 3`, every Wednesday 10 AM Central, action `Team/Sprint/SprintLockScheduled`,
235
+ maxExecutionTime 600) to run **Sprint Lock automatically at the start of each biweekly
236
+ sprint**. Cron can't express "every other Wednesday", so the job runs every Wednesday and
237
+ self-gates in PHP: it calls the unchanged `SprintLock()` only when a `Sprints` row has
238
+ `dateEnd = yesterday` (i.e. today starts a new sprint), else returns a skip. Worker code and
239
+ the dbchanges2 cron-row migration must deploy together. (jcardinal)
213
240
  - 2026-06-24 — Fixed a duplicate-key crash (`Tasks.sprintId_clickupIdentifier`) in the
214
241
  ClickUp→DB sync: ClickUp pagination can return the same task on multiple pages, and the
215
242
  loop was overloading one map (`$lookupTaskByClickUpId`) for both update-vs-insert and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.393",
3
+ "version": "1.0.394",
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",