@checkstack/catalog-backend 1.3.1 → 1.4.0
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.
- package/CHANGELOG.md +136 -0
- package/drizzle/0003_tan_spot.sql +17 -0
- package/drizzle/0004_heavy_sharon_carter.sql +13 -0
- package/drizzle/0005_normal_shaman.sql +60 -0
- package/drizzle/0006_optimal_gamora.sql +43 -0
- package/drizzle/meta/0003_snapshot.json +479 -0
- package/drizzle/meta/0004_snapshot.json +495 -0
- package/drizzle/meta/0005_snapshot.json +592 -0
- package/drizzle/meta/0006_snapshot.json +592 -0
- package/drizzle/meta/_journal.json +28 -0
- package/package.json +15 -12
- package/src/ai/catalog-add-system-to-group.test.ts +51 -0
- package/src/ai/catalog-add-system-to-group.ts +68 -0
- package/src/ai/catalog-create-group.test.ts +62 -0
- package/src/ai/catalog-create-group.ts +71 -0
- package/src/ai/catalog-create-system.test.ts +62 -0
- package/src/ai/catalog-create-system.ts +78 -0
- package/src/ai/catalog-delete-group.test.ts +83 -0
- package/src/ai/catalog-delete-group.ts +77 -0
- package/src/ai/catalog-delete-system.test.ts +84 -0
- package/src/ai/catalog-delete-system.ts +77 -0
- package/src/ai/catalog-remove-system-from-group.test.ts +55 -0
- package/src/ai/catalog-remove-system-from-group.ts +74 -0
- package/src/ai/catalog-update-group.test.ts +85 -0
- package/src/ai/catalog-update-group.ts +88 -0
- package/src/ai/catalog-update-system.test.ts +87 -0
- package/src/ai/catalog-update-system.ts +93 -0
- package/src/ai/catalog.projection.test.ts +37 -0
- package/src/ai/register-ai-tools.ts +35 -0
- package/src/automations.test.ts +2 -1
- package/src/catalog-gitops-kinds.test.ts +288 -0
- package/src/index.ts +149 -0
- package/src/router.test.ts +107 -0
- package/src/router.ts +200 -26
- package/src/schema.ts +124 -38
- package/src/services/entity-service.test.ts +28 -0
- package/src/services/entity-service.ts +154 -1
- package/src/services/environment-membership.test.ts +66 -0
- package/src/services/environment-membership.ts +40 -0
- package/src/services/pg-errors.test.ts +24 -0
- package/src/services/pg-errors.ts +21 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,141 @@
|
|
|
1
1
|
# @checkstack/catalog-backend
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9dcc848: Plugin-owned AI tools: every domain plugin contributes its own AI tools (chat assistant + automation AI action), and `ai-backend` is platform-only.
|
|
8
|
+
|
|
9
|
+
Every plugin-specific AI tool is owned by the plugin whose domain it acts on, registered via that plugin's own `aiToolExtensionPoint` / `aiToolProjectionExtensionPoint` from its init - the same path an external plugin author uses. `ai-backend` no longer imports or depends on any capability plugin's `*-common`; the dependency direction is strictly plugin -> ai-platform. Pure helpers (`computeFieldDiff`, capability-summary, `ScriptContextKind`) live in `@checkstack/ai-common`.
|
|
10
|
+
|
|
11
|
+
Tools shipped:
|
|
12
|
+
|
|
13
|
+
- Health checks and automations: full CRUD - `healthcheck.propose` / `automation.propose` and `*.update` (`mutate`, deep-validated) and `*.delete` (`destructive`, always confirm-gated). `healthcheck.propose`'s dry-run calls the new deep `validateConfiguration` so propose-time validation matches apply-time. Assertions are validated against the collector's result schema and the canonical operator vocabulary. Capability-catalog tools (`ai.listCapabilities`, `ai.getCapabilitySchema`), script context tools (`ai.getScriptContext`, `ai.testScript`), and notify-subscriber tools (`healthcheck.notifySystemSubscribers` / `...GroupSubscribers`).
|
|
14
|
+
- Catalog: `catalog.createSystem` / `updateSystem` / `createGroup` / `updateGroup` (`mutate`), `catalog.deleteSystem` / `deleteGroup` (`destructive`), membership tools (`mutate`), plus `catalog.listSystems` / `listGroups` read projections.
|
|
15
|
+
- Incident: `incident.create` / `update` / `addUpdate` / `resolve` / `addLink` (`mutate`), `incident.delete` / `removeLink` (`destructive`), and `incident.get` / `incident.list` read projections.
|
|
16
|
+
- Maintenance: `maintenance.create` / `update` / `addUpdate` / `close` / `addLink` (`mutate`), `maintenance.delete` / `removeLink` (`destructive`), and `maintenance.list` / `get` read projections.
|
|
17
|
+
- Read projections for SLO (`slo.listObjectives`), dependency (`dependency.list`), incident (`incident.list`), healthcheck (`healthcheck.status`), and anomaly (`anomaly.explain`), each gated by the source procedure's own access rule and routed as the principal.
|
|
18
|
+
- Documentation grounding: `ai.searchDocs` / `ai.getDoc` over a build-time bundled docs index (BM25-ish ranking), so the assistant grounds how-to answers in Checkstack's own docs offline.
|
|
19
|
+
- URL introspection: `ai.probeUrl`, an SSRF-guarded read tool the assistant uses to inspect a real endpoint before drafting a health check. Update tools compute a before -> after field diff rendered on the confirm card (approve mode) or an "Applied" card (auto mode), so a change is never silent.
|
|
20
|
+
|
|
21
|
+
`ai_analyze` automation action (automation-backend, with an editor connection picker + audited tool calls): runs a bounded AI agent on the run context as the automation's `runAs` service account, so it can never exceed that identity's permissions; destructive tools are never offered; mutating tools auto-apply through the service account's client. Produces an `automation.analysis` artifact downstream actions can branch on. The agent loop is exposed as a headless `aiAgentRunnerRef` service so automation-backend can drive it without depending on ai-backend.
|
|
22
|
+
|
|
23
|
+
`notification.notifyForSubscription` is now callable by user / application principals holding `notification.send` (previously service-only). Every tool routes through the user-scoped client, so handler-side authorization is enforced exactly as a direct UI/RPC action; the resolver gate plus the propose/apply re-check at propose AND apply are the additional authority. A systemic authz regression test asserts every registered tool falls into exactly one safe authorization category.
|
|
24
|
+
|
|
25
|
+
A new `ai_transport` enum value `automation` records the AI action's tool calls in the `ai_tool_calls` audit log. No new durable state beyond that; each tool is a thin, deterministic wrapper over an existing RPC, so every pod behaves identically.
|
|
26
|
+
|
|
27
|
+
This is a beta minor.
|
|
28
|
+
|
|
29
|
+
- 9dcc848: Redesign the catalog into a group-first browse view and tabbed management tables, with inline health rollups.
|
|
30
|
+
|
|
31
|
+
- Browse view: the catalog home is a real read-only, scale-built experience - collapsible group sections (with member counts) plus a synthetic Ungrouped section, a shared toolbar (search, group/health/tag filters, density toggle), URL-backed view state (shareable deep links), polished empty states, and a manager-only "Manage catalog" link. Per-system status badges render through the existing `SystemStateBadgesSlot`; filtering is client-side over the loaded set.
|
|
32
|
+
- Management: redesigned as tabbed data tables (Systems / Groups / Environments) replacing the two-column drag-to-assign layout. Systems get multi-select + a bulk bar, inline health, and group + environment membership as removable chips with type-ahead pickers (portaled so they are never clipped); Groups get inline rename and member chips; Environments get a name / members / field-count table (CRUD gated by `catalog.environment.manage`). GitOps-locked rows stay read-only. Drag-and-drop (and `@dnd-kit` on this page) is removed; the management page also shares the browse toolbar.
|
|
33
|
+
- Inline health rollups: a new platform contract `CatalogBrowseHealthSlot` (`@checkstack/catalog-common`) - an additive optional slot catalog-frontend only consumes (a headless data boundary feeding group rollups + the health filter), with a catalog-owned `CatalogHealthStatus` vocabulary so catalog gains no health-plugin dependency. Group headers show a rollup pill derived from the reported status DATA (a system absent from the map is `"unknown"`, never healthy); all-healthy groups start collapsed. The health filter is wired on both toolbars and enables once a filler reports. healthcheck-frontend fills the slot by reusing dashboard-frontend's `SystemBadgeDataProvider`. When no health source is installed the slot is unfilled and the catalog stays fully functional.
|
|
34
|
+
|
|
35
|
+
This is a beta minor.
|
|
36
|
+
|
|
37
|
+
- 9dcc848: Add environments as a first-class catalog primitive, with per-environment health-check fan-out, config templating, per-environment reactive health, and script run-context exposure.
|
|
38
|
+
|
|
39
|
+
- Catalog primitive: an environment is a sibling of groups - a named, instance-global record carrying free-form custom fields (baseUrl, region, tier, ...) that any system can belong to many-to-many. New `environments` + `systems_environments` tables, `EnvironmentSchema` + create/update schemas, `EntityService` environment CRUD and membership joins, RPC endpoints gated by a new `catalogAccess.environment` access rule, a GitOps `Environment` kind + `System.environments` extension, and frontend management (an `EnvironmentEditor`, an Environments management panel, and a per-system environment picker). The Environments card's Add/Edit/Delete affordances are gated on `catalogAccess.environment.manage`.
|
|
40
|
+
- Per-environment fan-out: run identity becomes `(systemId, configurationId, environmentId)`. Runs, aggregates, and state transitions gain a nullable `environmentId`. The health-check assignment gains an `environmentIds` selector with three modes (All / Specific / None; `null` and `[]` are distinct). The queue executor resolves the effective environment set via the catalog `resolveSystemEnvironments` read and executes one isolated run per environment.
|
|
41
|
+
- Config templating: a new `x-templatable` config-field marker renders a string field through the template engine at execute time, against `{ environment, check, system }`. A shared `renderTemplatableConfig` and a `renderTemplatePreview` helper (re-exported from `@checkstack/template-engine`) keep editor previews identical to the run-time render. The HTTP collector's `url`, `headers[].value`, and `body` are templatable, rendered per environment (the strategy client build moves inside the per-env loop); the `url`'s `.url()` validation moves post-render. Secrets resolve before templating; a field marked both secret and `x-templatable` is rejected at plugin load. `DynamicForm` shows a live "Preview" line, and the catalog `EnvironmentPreviewPicker` ("Preview as: <environment>") drives it in the collector editor (only when the schema has a templatable field).
|
|
42
|
+
- Script run-context: `CollectorRunContext` gains an optional `environment` field (`{ id, name, fields }`, metadata only). Shell collectors receive `CHECKSTACK_ENV_ID` / `_NAME` / `CHECKSTACK_ENV_<FIELD>` vars; inline TS collectors read `globalThis.context.environment`; the editor test panel mirrors both. The env-less path is unchanged.
|
|
43
|
+
- Per-environment reactive health (see BREAKING below), env-keyed read/write paths, env-qualified serialization locks, an optional `trigger.payload.environmentId`, per-environment isolation, and an `ENVIRONMENT_RESOLUTION_FAILED` signal when catalog resolution degrades to a single env-less run.
|
|
44
|
+
|
|
45
|
+
BREAKING CHANGES: the reactive `health` entity's id-shape and cardinality change. It now encodes two views: per-environment (id `"<systemId>::<environmentId>"`) and a system rollup (id `"<systemId>"`, the worst status across environments + env-less runs). The rollup PRESERVES the pre-existing system-level contract - dashboards, status badges, and automations referencing health by `systemId` keep working without re-authoring - but the entity's contract surface changed (new id-shape, higher cardinality, new payload field), so it is flagged breaking. `getBulkHealthState` parses env-qualified ids and keys results by the original id.
|
|
46
|
+
|
|
47
|
+
State and scale: membership and custom fields live only in catalog Postgres and are re-read every tick via the cross-plugin RPC; env-keyed health reads from shared `health_check_runs` / aggregates / transitions (compute-on-read). Every pod resolves the same effective set and the same per-environment health. No pod-local environment state.
|
|
48
|
+
|
|
49
|
+
Also: `unwrapSchema` in `zod-config.ts` loops instead of single-pass-stripping so multi-layer wrappers (`.optional().default()`) still resolve `x-templatable` meta. The env-less `{{ environment.* }}` run notice logs at `debug` (a legitimate recurring configuration), while the post-render HTTP `.url()` check still fails a genuinely-broken empty render with a clear "Rendered URL is invalid" error.
|
|
50
|
+
|
|
51
|
+
This is a beta minor.
|
|
52
|
+
|
|
53
|
+
- 9dcc848: Align workspace dependency versions and migrate React Router to v7.
|
|
54
|
+
|
|
55
|
+
BREAKING CHANGES (React Router v7): All frontend packages now depend on `react-router-dom@^7.16.0`. Previously the workspace declared four divergent ranges (`^6.20.0`, `^6.22.0`, `^7.1.1`, `^7.14.2`), which resolved both `react-router@6` and `react-router@7` into a single bundle. Everything is now unified on v7. The public imports the app uses (`BrowserRouter`, `Routes`, `Route`, `Link`, `NavLink`, `MemoryRouter`, `useNavigate`, `useParams`, `useSearchParams`, `useLocation`) are unchanged between v6 and v7, so no source rewrites were required - but any out-of-tree plugin still on react-router v6 should upgrade to v7 (see the React Router v6 -> v7 upgrade guide) to share the host's single router instance via the import map.
|
|
56
|
+
|
|
57
|
+
Other unified ranges (no API change): `react` -> `^18.3.1`, the `@orpc/*` family (`contract`, `server`, `client`, `tanstack-query`, `openapi`, `zod`) -> `^1.14.4`, and `better-auth` -> `^1.6.13`.
|
|
58
|
+
|
|
59
|
+
Removed the pre-rename `@orpc/react-query` leftover from `@checkstack/frontend-api`; its `createRouterUtils` / `RouterUtils` / `ProcedureUtils` now come from `@orpc/tanstack-query` (the package already in use).
|
|
60
|
+
|
|
61
|
+
Stale in-range runtime deps pulled up to current published versions: `hono` `^4.12.23`, `@tanstack/react-query` (+devtools) `^5.100.14`, `date-fns` `^4.4.0`, `jose` `^6.2.3`, `tar` `^7.5.16`, `semver` `^7.8.1`, `@xyflow/react` `^12.11.0`.
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- 9dcc848: Write-path hardening: post-commit side effects can no longer fail a committed write, multi-row mutations are now atomic, and retry-duplication is blocked at the database.
|
|
66
|
+
|
|
67
|
+
**Platform-level (automatic for all current and future plugins):**
|
|
68
|
+
|
|
69
|
+
- signal-backend: `SignalService` (broadcast / sendToUser / sendToUsers / sendToAuthorizedUsers) is now resilient by construction - a transient event-bus/queue failure is caught and logged instead of thrown. Real-time signals are best-effort UI nudges; the authoritative data is already committed by the time a mutation broadcasts, so a signal-transport blip must never turn a successful write into a client-visible error. Every plugin's broadcasts inherit this without per-call-site `try/catch` (which would inevitably be forgotten and regress). This mirrors `createCachedScope`, which already makes cache invalidation non-throwing - so the cache + signal halves of the "post-commit side effect fails the response" class are both closed at the platform seam. Durable side effects (events/hooks that drive automations, queue jobs) intentionally still surface failures. Documented in `developer-guide/backend/signals.md`.
|
|
70
|
+
|
|
71
|
+
**Atomic multi-write mutations (each previously committed row-by-row in autocommit, so a mid-sequence failure left partial/orphaned state):**
|
|
72
|
+
|
|
73
|
+
- slo-backend: `createObjective` now inserts the objective and its 1:1 streak row in one transaction; the post-create reconcile/status/notify steps are best-effort and can no longer fail the (committed) create.
|
|
74
|
+
- incident-backend: `createIncident`, `updateIncident`, `addUpdate`, and `resolveIncident` wrap their row + system-link + timeline writes in a transaction (no more wiped system associations on a failed re-insert, or status flips with no matching timeline entry).
|
|
75
|
+
- maintenance-backend: same for `createMaintenance`, `updateMaintenance`, `addUpdate`, `closeMaintenance`.
|
|
76
|
+
- automation-backend: `cancelRun` marks the run cancelled and tears down its wait locks + durable state in one transaction - previously a failure after the status update could leave a wait lock behind, letting a later trigger event resume an already-cancelled run.
|
|
77
|
+
- healthcheck-backend: `ingestSatelliteResult` commits the run row and its hourly-aggregate increment together (no orphaned run, no aggregate without a backing run). NOTE: this guarantees run/aggregate consistency but does not yet make a _duplicate satellite delivery_ idempotent - that needs a dedupe key on the high-volume runs table and is tracked as a follow-up.
|
|
78
|
+
|
|
79
|
+
**Retry-duplication blocked at the DB (paired with the SQLSTATE 23505 -> 409 mapping shipped separately):**
|
|
80
|
+
|
|
81
|
+
- catalog-backend: new unique indexes on `groups.name`, `environments.name` (consistent with `systems.name`), on `system_links (system_id, url)`, and on `system_contacts (system_id, user_id)` + `(system_id, email)` (NULLs are distinct, so user vs mailbox contacts don't interfere). Name uniqueness is CASE-INSENSITIVE: the three name indexes are functional `lower(name)` indexes (the existing `systems.name` index is rebuilt this way too), so "Api" and "api" collide while the stored value keeps its original casing. The systems pre-write name check (`getSystemByName`) is case-folded to match. Migration `0005` de-dupes any pre-existing rows first - names are preserved by suffixing later case-insensitive duplicates (" (2)", " (3)", ...), redundant contact/link rows are removed keeping the earliest. (Link URLs stay case-sensitive - URL paths are; contact emails are deduped exact-match.)
|
|
82
|
+
- incident-backend / maintenance-backend: unique index on `incident_links (incident_id, url)` / `maintenance_links (maintenance_id, url)`, with a de-dupe step in the migration.
|
|
83
|
+
|
|
84
|
+
**Behavior change:** creating a group/environment with a duplicate name, or attaching a duplicate contact/link, now returns `409 Conflict` instead of silently creating a duplicate. The migrations resolve existing duplicates on upgrade.
|
|
85
|
+
|
|
86
|
+
This is a beta patch.
|
|
87
|
+
|
|
88
|
+
- 9dcc848: Assorted bug fixes and small hardening across the platform.
|
|
89
|
+
|
|
90
|
+
- announcement-backend: `updateAnnouncement` now invalidates the active-announcements and admin-list caches (it was missing the `invalidateAllActive` / `invalidateListAll` calls), so an edited announcement no longer stays stale up to the 45s TTL.
|
|
91
|
+
- anomaly-backend: anomaly/drift state transitions (confirmations, recoveries, self-resolutions) now log at `debug` instead of info/warn - they are already surfaced via the `ANOMALY_STATE_CHANGED` signal, so logging them louder just added noise; genuine failure paths stay `warn`.
|
|
92
|
+
- backend: the `/api/:pluginId/*` dispatcher now populates `requestHeaders` on the per-request RPC context, so a handler that re-enters the router as the originating user (e.g. an AI tool's user-scoped client) can forward the caller's session cookie / bearer - previously the loopback failed with "Authentication required". Guarded by a real end-to-end integration test. The HTTP server idle timeout is also raised (default 255s, configurable via `CHECKSTACK_SERVER_IDLE_TIMEOUT_SECONDS`, clamped 0-255, reset on each streamed chunk) so long AI chat SSE turns are not severed mid-stream.
|
|
93
|
+
- backend: a request for an unknown plugin id (`/api/<unknown>/...`) now returns `404 Not Found` instead of `500` (and logs at warn, not error, since it is a client request) - an unknown _procedure_ on a known plugin already 404'd. The in-app docs namespace `/checkstack/*` now serves Starlight's own `404.html` with a real 404 status for a missing doc, instead of falling through to the SPA catch-all and 200-ing the app shell. Both guarded by tests.
|
|
94
|
+
- automation-common: remove polynomial-time backtracking from `toShellEnvKey`'s underscore-trim (CodeQL `js/polynomial-redos`); a negative look-behind anchors the trailing run, keeping the trim linear.
|
|
95
|
+
- common + script-packages-common: the pure transport-safe sandbox-policy schema (`sandboxPolicySchema` and its sub-schemas + inferred types) moved to `@checkstack/common` (the neutral base), removing two inverted deps that existed only to reach the shape; `@checkstack/backend-api` continues to re-export it. The schema is no longer exported from `@checkstack/script-packages-common`. Pure refactor, no behavior change.
|
|
96
|
+
- catalog-backend: reject duplicate system names (a `CONFLICT` on create/rename, enforced by a pre-write check AND a new DB unique index on `systems.name`, migration 0004 which first resolves pre-existing duplicates by suffixing).
|
|
97
|
+
- catalog-frontend: detail-page cleanups (use `<NotFound />` not `<AccessDenied />` on the not-found branch, a readable key/value metadata list via `normalizeMetadata`, runtime locale via `formatDate`); and stop the browse view re-rendering on every health report (adopt a new statuses report only when a value actually changed, via `healthStatusesEqual`, so rows stay stable and interactive).
|
|
98
|
+
- healthcheck-backend: fix the daily-rollup retention step failing with an `ON CONFLICT` mismatch (SQLSTATE 42P10) after `environmentId` joined the `health_check_aggregates` unique constraint - the rollup now groups by (day, environmentId, sourceId) and uses a single exported conflict-target constant (`DAILY_AGGREGATE_CONFLICT_TARGET`) kept in lock-step with the schema by a unit test.
|
|
99
|
+
- automation-frontend: the service-account picker's "Learn more" links are now absolute URLs to the deployed Astro docs site (they 404ed as in-app relative paths). The Monaco script editor double-init crash is fixed (serialized cold init, a guarded `monacoGuard` accessor, theme/type effects gated on `apiReady`).
|
|
100
|
+
- auth-frontend: bound the desktop user-menu popover height (`max-h-[var(--radix-popover-content-available-height)]` + `overflow-y-auto`) so it no longer clips on short viewports, and fold the standalone `Account > Profile` item into a focusable name/email header (`profileHref` on `UserMenu`); the now-empty `Account` group no longer renders.
|
|
101
|
+
- satellite-frontend: picked up via the sidebar-nav migration (account-only user menu).
|
|
102
|
+
|
|
103
|
+
(Related UI fixes - the Monaco editor following the app theme, the `DynamicOptionsField` no-flash fix, the shared `Spinner`, GFM tables, and the user-menu popover bound - land their `@checkstack/ui` bump in the UI/perf changesets where `@checkstack/ui` is already minored.)
|
|
104
|
+
|
|
105
|
+
This is a beta patch.
|
|
106
|
+
|
|
107
|
+
- Updated dependencies [9dcc848]
|
|
108
|
+
- Updated dependencies [9dcc848]
|
|
109
|
+
- Updated dependencies [9dcc848]
|
|
110
|
+
- Updated dependencies [9dcc848]
|
|
111
|
+
- Updated dependencies [9dcc848]
|
|
112
|
+
- Updated dependencies [9dcc848]
|
|
113
|
+
- Updated dependencies [9dcc848]
|
|
114
|
+
- Updated dependencies [9dcc848]
|
|
115
|
+
- Updated dependencies [9dcc848]
|
|
116
|
+
- Updated dependencies [9dcc848]
|
|
117
|
+
- Updated dependencies [9dcc848]
|
|
118
|
+
- Updated dependencies [9dcc848]
|
|
119
|
+
- Updated dependencies [9dcc848]
|
|
120
|
+
- Updated dependencies [9dcc848]
|
|
121
|
+
- Updated dependencies [9dcc848]
|
|
122
|
+
- Updated dependencies [9dcc848]
|
|
123
|
+
- Updated dependencies [9dcc848]
|
|
124
|
+
- @checkstack/ai-backend@0.1.0
|
|
125
|
+
- @checkstack/ai-common@0.1.0
|
|
126
|
+
- @checkstack/auth-backend@0.5.0
|
|
127
|
+
- @checkstack/auth-common@0.8.0
|
|
128
|
+
- @checkstack/backend-api@0.21.0
|
|
129
|
+
- @checkstack/automation-backend@0.5.0
|
|
130
|
+
- @checkstack/notification-common@1.3.0
|
|
131
|
+
- @checkstack/catalog-common@2.3.0
|
|
132
|
+
- @checkstack/common@0.13.0
|
|
133
|
+
- @checkstack/command-backend@0.2.0
|
|
134
|
+
- @checkstack/gitops-backend@0.5.0
|
|
135
|
+
- @checkstack/gitops-common@0.6.0
|
|
136
|
+
- @checkstack/cache-api@0.3.9
|
|
137
|
+
- @checkstack/cache-utils@0.2.14
|
|
138
|
+
|
|
3
139
|
## 1.3.1
|
|
4
140
|
|
|
5
141
|
### Patch Changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
CREATE TABLE "environments" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"name" text NOT NULL,
|
|
4
|
+
"description" text,
|
|
5
|
+
"metadata" json DEFAULT '{}'::json,
|
|
6
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
7
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
8
|
+
);
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
CREATE TABLE "systems_environments" (
|
|
11
|
+
"system_id" text NOT NULL,
|
|
12
|
+
"environment_id" text NOT NULL,
|
|
13
|
+
CONSTRAINT "systems_environments_system_id_environment_id_pk" PRIMARY KEY("system_id","environment_id")
|
|
14
|
+
);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
ALTER TABLE "systems_environments" ADD CONSTRAINT "systems_environments_system_id_systems_id_fk" FOREIGN KEY ("system_id") REFERENCES "systems"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
17
|
+
ALTER TABLE "systems_environments" ADD CONSTRAINT "systems_environments_environment_id_environments_id_fk" FOREIGN KEY ("environment_id") REFERENCES "environments"("id") ON DELETE cascade ON UPDATE no action;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Before enforcing uniqueness, resolve any pre-existing duplicate system names
|
|
2
|
+
-- (the column had no unique constraint until now). Keep the earliest system with
|
|
3
|
+
-- each name unchanged and suffix the later ones (" (2)", " (3)", ...) by
|
|
4
|
+
-- created_at/id order, so no data is lost and the unique index can be created.
|
|
5
|
+
UPDATE "systems" AS s
|
|
6
|
+
SET "name" = s."name" || ' (' || r.rn || ')'
|
|
7
|
+
FROM (
|
|
8
|
+
SELECT "id", row_number() OVER (PARTITION BY "name" ORDER BY "created_at", "id") AS rn
|
|
9
|
+
FROM "systems"
|
|
10
|
+
) AS r
|
|
11
|
+
WHERE s."id" = r."id" AND r.rn > 1;
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE UNIQUE INDEX "systems_name_unique" ON "systems" USING btree ("name");
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
-- Before enforcing the new uniqueness constraints, resolve any pre-existing
|
|
2
|
+
-- duplicates (these columns had no unique constraint until now). Names are
|
|
3
|
+
-- preserved by suffixing later duplicates (" (2)", " (3)", ...); redundant
|
|
4
|
+
-- contact/link rows are removed, keeping the earliest by created_at/id. Mirrors
|
|
5
|
+
-- the systems_name_unique migration (0004).
|
|
6
|
+
|
|
7
|
+
-- environments.name
|
|
8
|
+
UPDATE "environments" AS e
|
|
9
|
+
SET "name" = e."name" || ' (' || r.rn || ')'
|
|
10
|
+
FROM (
|
|
11
|
+
SELECT "id", row_number() OVER (PARTITION BY "name" ORDER BY "created_at", "id") AS rn
|
|
12
|
+
FROM "environments"
|
|
13
|
+
) AS r
|
|
14
|
+
WHERE e."id" = r."id" AND r.rn > 1;
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
CREATE UNIQUE INDEX "environments_name_unique" ON "environments" USING btree ("name");--> statement-breakpoint
|
|
17
|
+
|
|
18
|
+
-- groups.name
|
|
19
|
+
UPDATE "groups" AS g
|
|
20
|
+
SET "name" = g."name" || ' (' || r.rn || ')'
|
|
21
|
+
FROM (
|
|
22
|
+
SELECT "id", row_number() OVER (PARTITION BY "name" ORDER BY "created_at", "id") AS rn
|
|
23
|
+
FROM "groups"
|
|
24
|
+
) AS r
|
|
25
|
+
WHERE g."id" = r."id" AND r.rn > 1;
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE UNIQUE INDEX "groups_name_unique" ON "groups" USING btree ("name");--> statement-breakpoint
|
|
28
|
+
|
|
29
|
+
-- system_contacts (system_id, user_id) — NULL user_id rows (mailbox contacts)
|
|
30
|
+
-- are distinct in btree and unaffected; only duplicate user contacts are pruned.
|
|
31
|
+
DELETE FROM "system_contacts" WHERE "id" IN (
|
|
32
|
+
SELECT "id" FROM (
|
|
33
|
+
SELECT "id", row_number() OVER (PARTITION BY "system_id", "user_id" ORDER BY "created_at", "id") AS rn
|
|
34
|
+
FROM "system_contacts"
|
|
35
|
+
WHERE "user_id" IS NOT NULL
|
|
36
|
+
) AS r WHERE r.rn > 1
|
|
37
|
+
);
|
|
38
|
+
--> statement-breakpoint
|
|
39
|
+
CREATE UNIQUE INDEX "system_contacts_system_user_unique" ON "system_contacts" USING btree ("system_id","user_id");--> statement-breakpoint
|
|
40
|
+
|
|
41
|
+
-- system_contacts (system_id, email) — NULL email rows (user contacts) are distinct.
|
|
42
|
+
DELETE FROM "system_contacts" WHERE "id" IN (
|
|
43
|
+
SELECT "id" FROM (
|
|
44
|
+
SELECT "id", row_number() OVER (PARTITION BY "system_id", "email" ORDER BY "created_at", "id") AS rn
|
|
45
|
+
FROM "system_contacts"
|
|
46
|
+
WHERE "email" IS NOT NULL
|
|
47
|
+
) AS r WHERE r.rn > 1
|
|
48
|
+
);
|
|
49
|
+
--> statement-breakpoint
|
|
50
|
+
CREATE UNIQUE INDEX "system_contacts_system_email_unique" ON "system_contacts" USING btree ("system_id","email");--> statement-breakpoint
|
|
51
|
+
|
|
52
|
+
-- system_links (system_id, url)
|
|
53
|
+
DELETE FROM "system_links" WHERE "id" IN (
|
|
54
|
+
SELECT "id" FROM (
|
|
55
|
+
SELECT "id", row_number() OVER (PARTITION BY "system_id", "url" ORDER BY "created_at", "id") AS rn
|
|
56
|
+
FROM "system_links"
|
|
57
|
+
) AS r WHERE r.rn > 1
|
|
58
|
+
);
|
|
59
|
+
--> statement-breakpoint
|
|
60
|
+
CREATE UNIQUE INDEX "system_links_system_url_unique" ON "system_links" USING btree ("system_id","url");
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- Make catalog display-name uniqueness CASE-INSENSITIVE. The name indexes added
|
|
2
|
+
-- in 0004/0005 were on the raw column, so "Api" and "api" were distinct. Rebuild
|
|
3
|
+
-- them as functional `lower(name)` indexes; the stored value keeps its original
|
|
4
|
+
-- casing. Before recreating each, resolve pre-existing case-insensitive
|
|
5
|
+
-- duplicates by suffixing the later ones (" (2)", " (3)", ...) so no data is lost
|
|
6
|
+
-- and the unique index can be created.
|
|
7
|
+
|
|
8
|
+
DROP INDEX "environments_name_unique";--> statement-breakpoint
|
|
9
|
+
DROP INDEX "groups_name_unique";--> statement-breakpoint
|
|
10
|
+
DROP INDEX "systems_name_unique";--> statement-breakpoint
|
|
11
|
+
|
|
12
|
+
-- environments.name (case-insensitive)
|
|
13
|
+
UPDATE "environments" AS e
|
|
14
|
+
SET "name" = e."name" || ' (' || r.rn || ')'
|
|
15
|
+
FROM (
|
|
16
|
+
SELECT "id", row_number() OVER (PARTITION BY lower("name") ORDER BY "created_at", "id") AS rn
|
|
17
|
+
FROM "environments"
|
|
18
|
+
) AS r
|
|
19
|
+
WHERE e."id" = r."id" AND r.rn > 1;
|
|
20
|
+
--> statement-breakpoint
|
|
21
|
+
CREATE UNIQUE INDEX "environments_name_unique" ON "environments" USING btree (lower("name"));--> statement-breakpoint
|
|
22
|
+
|
|
23
|
+
-- groups.name (case-insensitive)
|
|
24
|
+
UPDATE "groups" AS g
|
|
25
|
+
SET "name" = g."name" || ' (' || r.rn || ')'
|
|
26
|
+
FROM (
|
|
27
|
+
SELECT "id", row_number() OVER (PARTITION BY lower("name") ORDER BY "created_at", "id") AS rn
|
|
28
|
+
FROM "groups"
|
|
29
|
+
) AS r
|
|
30
|
+
WHERE g."id" = r."id" AND r.rn > 1;
|
|
31
|
+
--> statement-breakpoint
|
|
32
|
+
CREATE UNIQUE INDEX "groups_name_unique" ON "groups" USING btree (lower("name"));--> statement-breakpoint
|
|
33
|
+
|
|
34
|
+
-- systems.name (case-insensitive)
|
|
35
|
+
UPDATE "systems" AS s
|
|
36
|
+
SET "name" = s."name" || ' (' || r.rn || ')'
|
|
37
|
+
FROM (
|
|
38
|
+
SELECT "id", row_number() OVER (PARTITION BY lower("name") ORDER BY "created_at", "id") AS rn
|
|
39
|
+
FROM "systems"
|
|
40
|
+
) AS r
|
|
41
|
+
WHERE s."id" = r."id" AND r.rn > 1;
|
|
42
|
+
--> statement-breakpoint
|
|
43
|
+
CREATE UNIQUE INDEX "systems_name_unique" ON "systems" USING btree (lower("name"));
|