toga-ai 1.0.895 → 1.0.896

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.
@@ -44,7 +44,7 @@
44
44
  | [Background Email-Template Worker (_Worker_Notification_EmailTemplate)](features/notification-email-template.md) | Worker action that dispatches a stored client-defined `EmailTemplates` row by UUID off-thread; open for sending a complete pre-authored transactional template a |
45
45
  | [DB-Driven Notification (Internal) Email](features/notification-email.md) | Worker action that wraps an ad-hoc subject/body in the client's DB-stored branded shell and sends it; open for internal/system notification email (merge alerts, |
46
46
  | [NYCHH Asset-Tag Backfill (worker2)](features/nychh-asset-tag-backfill.md) | Backfills NYCHH unit asset tags + MAC addresses from NetSuite serial (inventory-number) records; open when the fulfillment sync misses a tag added to a serial a |
47
- | [OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)](features/oneuptime-incident-clickup-sync.md) | worker2 polls OneUptime every 15 min for open incidents and creates/auto-closes a ClickUp task per incident; open for the incident→ClickUp sync, its claim-first |
47
+ | [OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)](features/oneuptime-incident-clickup-sync.md) | worker2 polls OneUptime every 15 min for open incidents and creates/auto-closes a ClickUp task per incident; open for the incident→ClickUp sync, its claim-fir |
48
48
  | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | OneUptime push-metric monitor pattern for worker2 (2.0): a self-contained cron action decides pass/fail and POSTs a string token to a OneUptime Incoming Request |
49
49
  | [Platform Cache Cleanup (_Worker_Platform_Cache — Clean + Truncate)](features/platform-cache-cleanup.md) | `_Worker_Platform_Cache` maintenance of the shared Cache cluster behind api2 cross-client retrieval; open when touching cache reaping/truncate or the Clean cron |
50
50
  | [QA/QC Branch Automation (auto-mirror + rebuild-from-ledger revert)](features/qa-qc-branch-automation.md) | worker2-centralized GitHub branch automation (mirror, ledger-revert, the single guarded door to `_production`); open when touching QA/QC branch merges or revert |
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)
2
+ title: OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)
3
3
  framework: "2.0"
4
4
  repo: worker2
5
5
  project: Worker
@@ -23,20 +23,20 @@ related:
23
23
  - ../../_underscore/features/component-model-namespace-registration.md
24
24
  ---
25
25
 
26
- worker2 polls OneUptime every 15 min for open incidents and creates/auto-closes a ClickUp task per incident; open for the incident→ClickUp sync, its claim-first dedup, or the auto-close pass.
26
+ worker2 polls OneUptime every 15 min for open incidents and creates/auto-closes a ClickUp task per incident; open for the incident→ClickUp sync, its claim-first dedup, or the auto-close pass.
27
27
 
28
28
  ## Summary
29
29
 
30
- An internal/shared TOGA ops feature: worker2 polls the OneUptime API every 15 minutes for **currently-open** incidents and ensures a ClickUp task exists for each, so operational incidents become actionable tickets automatically without duplicates. This is the **first *reader* of the OneUptime API** in worker2 — until now OneUptime was **push-only** (workers POST metrics/heartbeats to Incoming-Request monitors; see [OneUptime push-metric monitors](./oneuptime-worker2-monitoring.md)). The model was "dumb reporter, smart monitor"; this adds the reader half.
30
+ An internal/shared TOGA ops feature: worker2 polls the OneUptime API every 15 minutes for **currently-open** incidents and ensures a ClickUp task exists for each, so operational incidents become actionable tickets automatically without duplicates. This is the **first *reader* of the OneUptime API** in worker2 — until now OneUptime was **push-only** (workers POST metrics/heartbeats to Incoming-Request monitors; see [OneUptime push-metric monitors](./oneuptime-worker2-monitoring.md)). The model was "dumb reporter, smart monitor"; this adds the reader half.
31
31
 
32
32
  ## How it works
33
33
 
34
34
  ### Entry points
35
35
 
36
- - `worker2/Component/Api/Oneuptime/Oneuptime.php` — `_Component_Api_Oneuptime`, the OneUptime API read client, modeled on `_Component_Api_Togaiq`. Reads a new `[oneuptime]` config group (endpoint, api_key, project_id) via `_Config`; `isConfigured()` guard; `send()` wraps `_ApiRequest` with `APIKey` + `ProjectID` headers; `getActiveIncidents()` returns normalized open incidents (filters out resolved **in PHP**).
37
- - `worker2/Worker/Monitor/Oneuptime.php` — `_Worker_Monitor_Oneuptime::SyncIncidents(): string`, the cron action. Returns a structured summary string on every path (`incidentsPolled` / `created` / `skipped` / `failed`, plus `closed` / `closeFailed`).
36
+ - `worker2/Component/Api/Oneuptime/Oneuptime.php` — `_Component_Api_Oneuptime`, the OneUptime API read client, modeled on `_Component_Api_Togaiq`. Reads a new `[oneuptime]` config group (endpoint, api_key, project_id) via `_Config`; `isConfigured()` guard; `send()` wraps `_ApiRequest` with `APIKey` + `ProjectID` headers; `getActiveIncidents()` returns normalized open incidents (filters out resolved **in PHP**).
37
+ - `worker2/Worker/Monitor/Oneuptime.php` — `_Worker_Monitor_Oneuptime::SyncIncidents(): string`, the cron action. Returns a structured summary string on every path (`incidentsPolled` / `created` / `skipped` / `failed`, plus `closed` / `closeFailed`).
38
38
 
39
- Canonical worker2 scheduled-3rd-party-API-job shape (see [Creating Worker Actions](./creating-worker-actions.md)): a `Core.CronJobs` row (cron expr, Central tz) → CronScheduler Lambda enqueues → `Controller/Index::worker()` dispatches action `Monitor/Oneuptime/SyncIncidents` to `_Worker_Monitor_Oneuptime::SyncIncidents`. HTTP goes through `_ApiRequest`; secrets come from `_Config::oneuptime()` reading `Config/<env>.ini`; ClickUp uses `_Component_Api_Clickup::send()`.
39
+ Canonical worker2 scheduled-3rd-party-API-job shape (see [Creating Worker Actions](./creating-worker-actions.md)): a `Core.CronJobs` row (cron expr, Central tz) → CronScheduler Lambda enqueues → `Controller/Index::worker()` dispatches action `Monitor/Oneuptime/SyncIncidents` to `_Worker_Monitor_Oneuptime::SyncIncidents`. HTTP goes through `_ApiRequest`; secrets come from `_Config::oneuptime()` reading `Config/<env>.ini`; ClickUp uses `_Component_Api_Clickup::send()`.
40
40
 
41
41
  ### Open-only
42
42
 
@@ -44,14 +44,14 @@ Only currently-open incidents are synced. The list route returns all incidents a
44
44
 
45
45
  ### Claim-first (unique-key) dedup
46
46
 
47
- For each open incident, INSERT a ledger row into `OneUptimeIncidentTasks` keyed on the OneUptime incident id **before** calling ClickUp. A unique-key violation means a task already exists → skip. On success, create the ClickUp task via `_Component_Api_Clickup::send('POST','/list/{listId}/task', …)` and attach the returned id to the ledger row; on failure, **delete the claim and re-throw**. Established worker2 way to prevent duplicate ClickUp tasks under overlapping cron runs — pattern copied from `_Worker_Infrastructure_Errors::createClickupTask`. `INSERT … WHERE NOT EXISTS` is **insufficient** under REPEATABLE READ; the UNIQUE key is the concurrency arbiter.
47
+ For each open incident, INSERT a ledger row into `OneUptimeIncidentTasks` keyed on the OneUptime incident id **before** calling ClickUp. A unique-key violation means a task already exists → skip. On success, create the ClickUp task via `_Component_Api_Clickup::send('POST','/list/{listId}/task', …)` and attach the returned id to the ledger row; on failure, **delete the claim and re-throw**. Established worker2 way to prevent duplicate ClickUp tasks under overlapping cron runs — pattern copied from `_Worker_Infrastructure_Errors::createClickupTask`. `INSERT … WHERE NOT EXISTS` is **insufficient** under REPEATABLE READ; the UNIQUE key is the concurrency arbiter.
48
48
 
49
49
  ### Close pass (auto-close on incident resolution)
50
50
 
51
51
  The sync was originally **create-only**; a ClickUp task opened for an incident was never closed once the incident resolved, so a recurrence (a *new* OneUptime incident with the same monitor title) opened a second task that **looked like a duplicate** but was not. `SyncIncidents()` now runs a **close pass first** each cycle, independent of how many incidents are currently open, via `closeResolvedIncidents()`:
52
52
 
53
53
  - SELECT ledger rows `WHERE clickupIdentifier IS NOT NULL AND dtClosed IS NULL`.
54
- - For each, call `_Component_Api_Oneuptime::getIncidentResolvedState(string $incidentId): ?bool` — `true` = resolved, `false` = confirmed still open, `null` = unknown. **Only a definite `true` closes a task**; `null` means "leave alone" so an ambiguous read never closes a live incident's ticket. Uses a new per-incident route (`ROUTE__INCIDENT_ITEM`, `/api/incident/{id}/get-item`, still a PLACEHOLDER) because resolved incidents no longer appear in `getActiveIncidents()` and must be re-checked individually. A 2xx response missing `currentIncidentState.isResolvedState` logs a warning (incident id only, no body/PII) so a wrong placeholder shape is detectable rather than silently returning `null` forever.
54
+ - For each, call `_Component_Api_Oneuptime::getIncidentResolvedState(string $incidentId): ?bool` — `true` = resolved, `false` = confirmed still open, `null` = unknown. **Only a definite `true` closes a task**; `null` means "leave alone" so an ambiguous read never closes a live incident's ticket. Uses a new per-incident route (`ROUTE__INCIDENT_ITEM`, `/api/incident/{id}/get-item`, still a PLACEHOLDER) because resolved incidents no longer appear in `getActiveIncidents()` and must be re-checked individually. A 2xx response missing `currentIncidentState.isResolvedState` logs a warning (incident id only, no body/PII) so a wrong placeholder shape is detectable rather than silently returning `null` forever.
55
55
  - To close, PUT `/task/{id}` with the list's **closed-type status NAME** (ClickUp has no generic close verb), resolved per-list via `GET /list/{id}` `statuses[]` where `type == 'closed'`, cached per run in `$closedStatusByList`. Then stamp `dtClosed = NOW()`.
56
56
  - `dtClosed` is stamped **only after a successful close**, so a failure retries next run. Per-incident failures are caught (`Throwable`) + `error_log` + counted, not fatal; whole-run failures (e.g. no sprint list) still re-throw for queue retry.
57
57
  - All new ClickUp calls in the close path are throttled with `usleep(_Worker_Clickup_Project::RATE_LIMIT_DELAY_US)` (120ms = 500/min), matching `_Worker_Infrastructure_Errors`.
@@ -60,36 +60,36 @@ Helpers added: `closeResolvedIncidents()`, `closeClickupTask()`, `resolveClosedS
60
60
 
61
61
  ### Target list = current sprint list
62
62
 
63
- The ClickUp task lands in the **current sprint** list, resolved by `GET /folder/{id}` and matching the list whose `start_date`/`due_date` window contains now (mirrors `_Worker_Team_Sprint::SprintLock` — see [Team Sprint Management](./team-sprint-management.md) and the sprint-promotion note in [Error Escalation Cron](./error-escalation-cron.md)). Folder id is read from `[clickup] sprint_folder_id` with a constant fallback (`90020432579`).
63
+ The ClickUp task lands in the **current sprint** list, resolved by `GET /folder/{id}` and matching the list whose `start_date`/`due_date` window contains now (mirrors `_Worker_Team_Sprint::SprintLock` — see [Team Sprint Management](./team-sprint-management.md) and the sprint-promotion note in [Error Escalation Cron](./error-escalation-cron.md)). Folder id is read from `[clickup] sprint_folder_id` with a constant fallback (`90116585845 — Dev Sprints; repointed by TRUE-82052 on 2026-09-21`).
64
64
 
65
65
  ### Data model
66
66
 
67
- `OneUptimeIncidentTasks` (CREATE on the **Team** cluster — chosen over Logs; `dbchanges2/Team/2026-08-10a`): `id`, `uuid char(36)`, `dtCreated`, `oneuptimeIncidentId varchar(64)`, `clickupIdentifier varchar(64) NULL`, `dtClosed DATETIME NULL` (added `dbchanges2/Team/2026-08-12a`, `AFTER clickupIdentifier`). The **UNIQUE KEY on `oneuptimeIncidentId`** is the dedup contract, same rationale as `IssueClickupTasks`. `dtClosed` marks a ledger row whose ClickUp task has been closed following incident resolution (NULL = still open / not yet closed).
67
+ `OneUptimeIncidentTasks` (CREATE on the **Team** cluster — chosen over Logs; `dbchanges2/Team/2026-08-10a`): `id`, `uuid char(36)`, `dtCreated`, `oneuptimeIncidentId varchar(64)`, `clickupIdentifier varchar(64) NULL`, `dtClosed DATETIME NULL` (added `dbchanges2/Team/2026-08-12a`, `AFTER clickupIdentifier`). The **UNIQUE KEY on `oneuptimeIncidentId`** is the dedup contract, same rationale as `IssueClickupTasks`. `dtClosed` marks a ledger row whose ClickUp task has been closed following incident resolution (NULL = still open / not yet closed).
68
68
 
69
69
  ### Provisioning / deploy
70
70
 
71
71
  - `dbchanges2/Core/2026-08-10b` inserts the `Core.CronJobs` row: action `Monitor/Oneuptime/SyncIncidents`, schedule `*/15 * * * *`, `maxExecutionTime` 120, seeded **`isActive = 0`**. Flip to 1 only after (a) the ledger table exists, (b) the `[oneuptime]` config group is populated, (c) the incident-list route is confirmed.
72
- - `[oneuptime]` config group (endpoint, api_key, project_id) added to `worker2/Config/production.ini` with placeholders — **values filled by the developer, not committed**. Credentials live only in the ini files.
72
+ - `[oneuptime]` config group (endpoint, api_key, project_id) added to `worker2/Config/production.ini` with placeholders — **values filled by the developer, not committed**. Credentials live only in the ini files.
73
73
 
74
74
  ### Configuration / credentials
75
75
 
76
- - `[oneuptime]` — `endpoint`, `api_key`, `project_id`. The hosted OneUptime SaaS base is `https://oneuptime.com` (confirmed from `worker/.ebextensions/045_oneuptime_agent.config`); the REST API path is appended to that base.
77
- - `[clickup]` — `sprint_folder_id` (fallback constant `90020432579`) plus the ClickUp token. ClickUp/OneUptime token **values** live in `worker2/Config/<env>.ini` — location only, never the value.
76
+ - `[oneuptime]` — `endpoint`, `api_key`, `project_id`. The hosted OneUptime SaaS base is `https://oneuptime.com` (confirmed from `worker/.ebextensions/045_oneuptime_agent.config`); the REST API path is appended to that base.
77
+ - `[clickup]` — `sprint_folder_id` (fallback constant `90116585845 — Dev Sprints; repointed by TRUE-82052 on 2026-09-21`) plus the ClickUp token. ClickUp/OneUptime token **values** live in `worker2/Config/<env>.ini` — location only, never the value.
78
78
 
79
79
  ## Gotchas
80
80
 
81
81
  - **Same-title ClickUp tickets from OneUptime are expected on incident recurrence.** Dedup is **per-incident-id, not per-title**: the `oneuptimeIncidentId` UNIQUE key + claim-first insert only prevents duplicates for the *same* incident id. Two tasks with the same monitor title minutes apart are two *different* OneUptime incidents, not a dedup failure. The symptom that looked like duplication was actually the missing auto-close: incident #1 resolved but its task never closed, then a fresh incident opened a second task. Auto-close closes #1 on resolution so a recurrence (#2) is the only open ticket.
82
- - **ClickUp has no generic "close" verb.** A task is closed by PUTting the exact NAME of the status whose `type == 'closed'`, which is **list-specific** — resolved per-list from `GET /list/{id}` `statuses[]` and cached per run.
82
+ - **ClickUp has no generic "close" verb.** A task is closed by PUTting the exact NAME of the status whose `type == 'closed'`, which is **list-specific** — resolved per-list from `GET /list/{id}` `statuses[]` and cached per run.
83
83
  - Throttle every ClickUp call in the close path with `usleep(RATE_LIMIT_DELAY_US)` (120ms) or rate-limit failures degrade into `closeFailed` counts.
84
- - **Both OneUptime routes remain PLACEHOLDERS** — the incident-list route (`/api/incident/get-list`) *and* the new per-incident route (`/api/incident/{id}/get-item`, `ROUTE__INCIDENT_ITEM`) are unverified against the live OneUptime API. Auto-close **fails safe** (only a definite resolved `true` closes anything; `null` leaves the task alone) but won't fire until the route/field shape is confirmed. Do not enable the cron (`isActive = 1`) until the routes and returned fields are verified.
85
- - **OneUptime cannot filter open-only server-side here** — resolution filtering happens in PHP, so the returned field set must actually carry the resolved/open state.
84
+ - **Both OneUptime routes remain PLACEHOLDERS** — the incident-list route (`/api/incident/get-list`) *and* the new per-incident route (`/api/incident/{id}/get-item`, `ROUTE__INCIDENT_ITEM`) are unverified against the live OneUptime API. Auto-close **fails safe** (only a definite resolved `true` closes anything; `null` leaves the task alone) but won't fire until the route/field shape is confirmed. Do not enable the cron (`isActive = 1`) until the routes and returned fields are verified.
85
+ - **OneUptime cannot filter open-only server-side here** — resolution filtering happens in PHP, so the returned field set must actually carry the resolved/open state.
86
86
  - Ship the worker2 code deploy and both dbchanges2 migrations **together**; the cron row is inert until the action is deployed and the ledger table exists.
87
- - **Deploy order for the auto-close change:** run `dbchanges2/Team/2026-08-12a` (the `dtClosed` column) **before** the worker2 code deploys — the new close-pass queries reference `dtClosed`.
87
+ - **Deploy order for the auto-close change:** run `dbchanges2/Team/2026-08-12a` (the `dtClosed` column) **before** the worker2 code deploys — the new close-pass queries reference `dtClosed`.
88
88
 
89
89
  ## Related
90
90
 
91
- - [OneUptime push-metric monitors](./oneuptime-worker2-monitoring.md) — the push-only reporter half.
92
- - [Error Escalation Cron](./error-escalation-cron.md) — the `_Worker_Infrastructure_Errors` claim-first pattern this copies, and sprint promotion.
93
- - [Team Sprint Management](./team-sprint-management.md) — the current-sprint-list resolution mirrored here.
94
- - [Creating Worker Actions](./creating-worker-actions.md) — the scheduled-3rd-party-API-job shape.
91
+ - [OneUptime push-metric monitors](./oneuptime-worker2-monitoring.md) — the push-only reporter half.
92
+ - [Error Escalation Cron](./error-escalation-cron.md) — the `_Worker_Infrastructure_Errors` claim-first pattern this copies, and sprint promotion.
93
+ - [Team Sprint Management](./team-sprint-management.md) — the current-sprint-list resolution mirrored here.
94
+ - [Creating Worker Actions](./creating-worker-actions.md) — the scheduled-3rd-party-API-job shape.
95
95
  - [Component/model namespace registration](../../_underscore/features/component-model-namespace-registration.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.895",
3
+ "version": "1.0.896",
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",