diff --git a/.github/workflows/agent-release.yml b/.github/workflows/agent-release.yml index 22782ed0..3e36e538 100644 --- a/.github/workflows/agent-release.yml +++ b/.github/workflows/agent-release.yml @@ -309,9 +309,9 @@ jobs: : "${POSTGRES_USER:=ctops}" : "${POSTGRES_DB:=ctops}" TIERS=$(docker compose exec -T db psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At \ - -c "SELECT name || ': ' || licence_tier FROM organisations WHERE deleted_at IS NULL ORDER BY created_at;" 2>/dev/null || true) + -c "SELECT name || ': ' || licence_tier FROM instance_settings WHERE deleted_at IS NULL ORDER BY created_at;" 2>/dev/null || true) if [ -z "$TIERS" ]; then - echo "Licence tier: unknown (no organisation configured yet)" + echo "Licence tier: unknown (no instance configured yet)" else echo "Licence tier:" while IFS= read -r line; do printf ' %s\n' "$line"; done <<< "$TIERS" diff --git a/CLAUDE.md b/CLAUDE.md index 487e83a3..78387299 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -94,7 +94,7 @@ export type NewHost = typeof hosts.$inferInsert // Use `unknown` and narrow, or define proper types // Server Actions must be explicitly typed -export async function getHosts(orgId: string): Promise {} +export async function getHosts(instanceId: string): Promise {} // All server actions live in apps/web/lib/actions/ // Named by domain: hosts.ts, certificates.ts, alerts.ts etc. @@ -107,8 +107,8 @@ export async function getHosts(orgId: string): Promise {} ```typescript // Reading data — TanStack Query calling a Server Action const { data, isLoading } = useQuery({ - queryKey: ['hosts', orgId], - queryFn: () => getHosts(orgId), + queryKey: ['hosts', instanceId], + queryFn: () => getHosts(instanceId), }) // Writing data — TanStack Query mutation calling a Server Action @@ -131,7 +131,7 @@ All exported from `apps/web/lib/db/schema/index.ts` Every table MUST have: ```typescript id: text('id').primaryKey().$defaultFn(() => createId()), // cuid2 -organisationId: text('organisation_id').notNull().references(() => organisations.id), +instanceId: text('instance_id').notNull().references(() => instances.id), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), deletedAt: timestamp('deleted_at', { withTimezone: true }), // soft delete @@ -148,7 +148,7 @@ Resources with state use both a current status column AND a status history table Never overwrite status — append to history. ### Foundational patterns (always respect these) -1. **Multi-tenancy** — every resource row has `organisation_id` +1. **Instance scoping** — every resource row has `instance_id` 2. **Soft deletes** — `deleted_at` on every table, never hard delete 3. **Tags as first-class** — flexible `key:value` tagging on any resource 4. **Event spine** — significant state changes append to the `events` table @@ -179,7 +179,7 @@ Note: LDAP/AD is in the community tier — paywalling it would block corporate a **RBAC Roles:** ``` super_admin Full system access, billing, global config -org_admin User/team management, integrations, org config +instance_admin User/team management, integrations, instance config engineer Read/write on assigned resource groups, alert management read_only View dashboards, alerts, inventory (management, auditors) agent Non-human, register host + submit metrics only @@ -316,14 +316,14 @@ Include `contextHolder` in component JSX. ## Development Roadmap Phases ``` -Phase 0 — Foundation Monorepo, Next.js, auth, orgs, Docker Compose +Phase 0 — Foundation Monorepo, Next.js, auth, instances, Docker Compose Phase 1 — Agent & Inventory Agents, hosts, basic metrics, gRPC, ingest Phase 2 — Monitoring Checks, alerting, graphs, TimescaleDB Phase 3 — Certificates Discovery, expiry alerts, CSR workflows Phase 4 — Service Accounts Identity, SSH keys, LDAP sync Phase 5 — Tooling Air-gap bundlers, runbooks, scheduled tasks Phase 6 — Enterprise SSO, audit log, advanced RBAC, compliance packs -Phase 7 — Cloud SaaS Multi-tenant hardening, billing, hosted offering +Phase 7 — Cloud SaaS Instance-scoped hardening, billing, hosted offering ``` Each phase must be **independently deployable and useful** before the next begins. @@ -380,7 +380,7 @@ The docs site is built with **VuePress 2** (`@vuepress/theme-default`). The `app - No linting errors — fix before committing - Run `pnpm run build` after every significant change and fix all errors - Server Actions must validate input with Zod before touching the database -- All database queries must be scoped by `organisationId` +- All database queries must be scoped by `instanceId` - Never expose internal IDs in URLs without verifying the requesting user has access - All mutations must be wrapped in try/catch with meaningful error messages diff --git a/ORGANISATION_REMOVAL_TASKS.md b/ORGANISATION_REMOVAL_TASKS.md index d17f9d04..ec8e7f44 100644 --- a/ORGANISATION_REMOVAL_TASKS.md +++ b/ORGANISATION_REMOVAL_TASKS.md @@ -1098,19 +1098,42 @@ and deploy validation scope. ## Task 16 - Final Residue Sweep And Validation -Status: Not started +Status: Completed -Completed by: +Completed by: Codex -PR: +PR: Pending -Summary: +Summary: Removed the remaining old organisation naming from active web, agent, +ingest, workflow, docs, tests, integration contracts, and launch assertions. +Renamed the remaining role/string residue to instance terminology, added a data +migration for existing `org_admin` values, changed CT-CVE and password-manager +contracts to instance-shaped claims, removed the legacy +`CT_OPS_ORG_TOKEN` fallback, and corrected stale test/doc residue found by the +final scan. -Files changed: +Files changed: `.github/workflows/agent-release.yml`; `agent/**`; +`apps/ingest/**`; `apps/web/**`; `apps/docs/**`; `docs/**`; top-level project +docs and trackers. Validation: -Follow-up: +- `pnpm --dir apps/web db:validate` — passed. +- `pnpm --dir apps/web type-check` — passed. +- `pnpm --dir apps/web lint` — passed with existing warnings only. +- `pnpm --dir apps/web test:unit` — passed, 348 tests. +- `pnpm --dir apps/web exec playwright test --list` — passed, 125 tests listed. +- `BETTER_AUTH_URL=http://localhost:3100 E2E_DISABLE_AGENT_CACHE_PREWARM=1 pnpm --dir apps/web exec playwright test settings/activation-token.spec.ts settings/agent-enrolment.spec.ts --project=chromium` — blocked by local environment: `POSTGRES_PASSWORD environment variable is required when DATABASE_URL is not set`. +- `pnpm --dir apps/docs build` — passed. +- `go test ./...` in `agent` — passed. +- `go test ./...` in `apps/ingest` — passed. +- `rg -n "orgId|organisationId|organisation_id|org_id|organisations|org_admin|CT_OPS_ORG_TOKEN|installOrg|installOrganisation|ct_ops_organization|org_scope_mismatch|orgSlug|tenant" apps/web apps/ingest agent .github docs apps/docs README.md SECURITY.md CLAUDE.md TASK.md PROGRESS.md PENTEST.md --glob '!**/node_modules/**' --glob '!apps/web/lib/db/migrations/0007_instance_role_residue.sql' --glob '!apps/web/lib/db/migrations/meta/**'` — no matches. +- Broad residue scan excluding the tracker and the transitional migration only + reports the allowed matches recorded below. + +Follow-up: Coordinate the activation-token field rename with `ct-portal` before +shipping the purchase flow if the portal has not already been updated to accept +`installInstanceId` / `installInstanceName`. ### Required work @@ -1144,7 +1167,9 @@ Follow-up: ## Remaining Allowed Matches -Record any allowed residue here during Task 4. Leave this empty until then. - | Path | Match | Reason | | --- | --- | --- | +| `apps/web/lib/db/migrations/0007_instance_role_residue.sql` | `org_admin` | Transitional data migration that converts persisted legacy roles to `instance_admin`. | +| `apps/web/lib/certificates/fetch.ts`, certificate checker UI/tests | `organization`, `organizationalUnit`, `issuerOrganization`, `Organization` | X.509 certificate subject/issuer field names and UI labels, unrelated to CT-Ops legacy organisations. | +| `pnpm-lock.yaml` | `Org` inside integrity hashes | Base64 package integrity data, not source or product terminology. | +| `apps/web/app/(dashboard)/hosts/groups/groups-client.tsx` | `Organise` | Plain English UI copy, not organisation identity or schema. | diff --git a/PENTEST.md b/PENTEST.md index b1301cc0..5165b461 100644 --- a/PENTEST.md +++ b/PENTEST.md @@ -35,7 +35,7 @@ permission to test them: - Protocol definitions and shared packages under `proto/` and `packages/` - Default security surfaces shipped by the project, including authentication, session handling, authorisation, agent enrolment, certificate tooling, - notification delivery, and tenant isolation + notification delivery, and instance isolation ## Allowed Test Activities @@ -43,7 +43,7 @@ The following test categories are in scope when executed carefully and within the rules below: - Authentication, session, and access-control testing -- Tenant-boundary and organisation-isolation validation +- Instance-boundary validation validation - Input validation, injection, SSRF, deserialisation, and file-handling tests - Deployment and container-hardening review - Agent, ingest, and API protocol security testing @@ -81,7 +81,7 @@ targets and activities are out of scope: - Physical attacks, phishing, vishing, pretexting, or credential stuffing - Volumetric denial-of-service, resource-exhaustion floods, or destructive availability testing -- Any attempt to access data belonging to other users, customers, or tenants +- Any attempt to access data belonging to other users, customers, or instances beyond the minimum proof needed to demonstrate isolation failure - Attacks that require already having root or equivalent privileged access on a host, unless the assessment is specifically scoped to post-compromise impact diff --git a/PROGRESS.md b/PROGRESS.md index 387cf04a..95af5d89 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -9,7 +9,7 @@ **Phase 5 — Tooling + release hardening (in progress)** ## Current Status -🟢 Phase 5 progressing — tooling has expanded beyond Directory User Lookup and SSL Certificate Checker into Jenkins/GitLab air-gap bundle generation, recursive plugin dependency resolution, offline install bundles, host-mediated bundle transfer, and better transfer/download status. Since the last update the platform has also had a substantial release-hardening pass: local auth email verification, login lockout, self-service password reset for local accounts, shared database-backed auth and abuse throttles across replicas, trusted-origin mutation checks, org-authenticated server actions, enrolment-token limits, certificate-checker SSRF and parsing hardening, signed-update enforcement, terminal SSH credential enforcement, heartbeat/JWT tightening, agent script limits, ingest gRPC caps, config-file permission validation, digest-pinned customer bundles, installer checksum verification, Password Manager release descriptor pinning, and broader E2E/process coverage. +🟢 Phase 5 progressing — tooling has expanded beyond Directory User Lookup and SSL Certificate Checker into Jenkins/GitLab air-gap bundle generation, recursive plugin dependency resolution, offline install bundles, host-mediated bundle transfer, and better transfer/download status. Since the last update the platform has also had a substantial release-hardening pass: local auth email verification, login lockout, self-service password reset for local accounts, shared database-backed auth and abuse throttles across replicas, trusted-origin mutation checks, instance-authenticated server actions, enrolment-token limits, certificate-checker SSRF and parsing hardening, signed-update enforcement, terminal SSH credential enforcement, heartbeat/JWT tightening, agent script limits, ingest gRPC caps, config-file permission validation, digest-pinned customer bundles, installer checksum verification, Password Manager release descriptor pinning, and broader E2E/process coverage. --- @@ -71,12 +71,12 @@ - `pnpm --dir apps/web exec playwright test --list tests/e2e/tooling/password-manager.spec.ts` - `pnpm --dir apps/web exec node tests/e2e/runner.mjs tests/e2e/tooling/password-manager.spec.ts --grep "read-only vault roles"` (blocked locally: `testcontainers` could not find a working container runtime) -### Session 130 — Password Manager tenant audit view +### Session 130 — Password Manager instance audit view **Standards-oriented audit visibility** (`apps/web/app/(dashboard)/password-manager/password-manager-client.tsx`, `apps/web/lib/password-manager/client.ts`, `apps/web/tests/e2e/fixtures/password-manager.ts`, `apps/web/tests/e2e/tooling/password-manager.spec.ts`) - Added Password Manager client support for the standalone API's redacted - tenant audit event listing and audit integrity status routes. -- Added an unlocked workspace Audit tab with tenant-wide filters for vault, + instance audit event listing and audit integrity status routes. +- Added an unlocked workspace Audit tab with instance-wide filters for vault, actor, event type, object type, outcome, and time range. - Rendered public-safe audit evidence only: timestamp, actor, action, target, outcome, summary, and integrity status, with E2E assertions that forensic or @@ -167,9 +167,9 @@ **Persistence, security, and audit** - Added `calendar_events`, `calendar_event_hosts`, and - `calendar_event_participants` with org-scoped RLS, indexes, idempotent create + `calendar_event_participants` with instance-scoped RLS, indexes, idempotent create support via `client_request_id`, and occurrence-exception uniqueness. -- Calendar mutations validate input on the server, enforce org ownership for +- Calendar mutations validate input on the server, enforce instance ownership for linked hosts/users, rate-limit writes, and emit audit events. - Added sidebar/command-palette entries, docs, E2E fixture cleanup, and route warmup coverage. @@ -370,7 +370,7 @@ **Vault member selector and public-key lookup** (`apps/web/app/(dashboard)/password-manager/page.tsx`, `apps/web/app/(dashboard)/password-manager/password-manager-client.tsx`, `apps/web/lib/password-manager/client.ts`, `apps/web/tests/e2e/fixtures/password-manager.ts`, `apps/web/tests/e2e/tooling/password-manager.spec.ts`) - Replaced the manual CT-Ops user ID and pasted public-key JSON add-member - controls with a searchable organisation-user selector that disables existing + controls with a searchable instance-user selector that disables existing members and users who have not completed Password Manager setup. - Added a Password Manager client call for the new vault-scoped `/member-recipients` lookup, caching returned public-key envelopes in browser @@ -464,12 +464,12 @@ - Added a Password Manager Playwright mock fixture that intercepts the hosted `/password-manager-api/` traffic, simulates session launch/setup/unlock, vault and entry CRUD, member sharing and revocation, key rotation, audit - hooks, session expiry, and organisation switches, and records outbound + hooks, session expiry, and instance switches, and records outbound request bodies plus headers for leak checks. - Added a targeted hosted Password Manager E2E spec that covers launch, browser-only setup, relaunch + unlock, vault create/delete, entry create/reveal/copy/export/edit/delete, member add/remove, rotation prompts, - session refresh expiry handling, and organisation switching from the CT-Ops + session refresh expiry handling, and instance switching from the CT-Ops hosted route shell. - Added network and browser-surface assertions proving the hosted flow does not send unlock passwords, plaintext entry values, or plaintext-shaped key fields @@ -559,15 +559,15 @@ **CT-Ops Password Manager launch route** (`apps/web/app/api/password-manager/launch-assertion/route.ts`, `apps/web/lib/password-manager/launch-assertion.ts`, `apps/web/lib/password-manager/launch-assertion.test.mjs`) - Added `POST /api/password-manager/launch-assertion`, gated by trusted - mutation origins, an active organisation session, Tooling access, and a + mutation origins, an active instance session, Tooling access, and a per-user launch rate limit. - Added a shared Password Manager launch-assertion helper that reads the CT-Ops launch config from runtime env, parses the configured Ed25519 PKCS#8 private key, and signs short-lived `EdDSA` JWTs carrying the required Password Manager claims including issuer, audience, product, CT-Ops instance, - organisation, user identity, `iat`, `exp`, and `jti`. -- Included optional organisation display names in the assertion when CT-Ops can - resolve them from the organisations table, while keeping failure responses + instance, user identity, `iat`, `exp`, and `jti`. +- Included optional instance display names in the assertion when CT-Ops can + resolve them from the instances table, while keeping failure responses generic and never proxying Password Manager API traffic or session cookies. **Validation** @@ -729,7 +729,7 @@ ### Session 97 — Durable licence verifier key rotation **Licence verifier continuity** (`apps/web/lib/licence.ts`, `apps/web/lib/actions/settings.ts`, `apps/web/lib/actions/licence-guard.ts`, `apps/web/lib/seat-admission.ts`) -- Added persisted licence verifier public key storage on the organisation row so each saved licence continues to validate against the public key that originally verified it. +- Added persisted licence verifier public key storage on the instance row so each saved licence continues to validate against the public key that originally verified it. - Added `LICENCE_PUBLIC_KEY_PATH` support and mounted `./licence-keys/current.pem` into the customer bundle for newly activated licences, with the baked production key retained as a fallback. - Added a migration for `licence_verifier_public_key` and `licence_verifier_public_key_fingerprint`. @@ -804,7 +804,7 @@ **CT-CVE connector setup/status surface** (`apps/web/app/(dashboard)/settings/integrations/ct-cve/page.tsx`, `apps/web/lib/integrations/ct-cve/setup-status.ts`) - Added an admin-only Settings -> Integrations -> CT-CVE page showing connector configured state, inbound signed-token scopes/counts, outbound inventory targets, recent health/finding/inventory timestamps, and connector errors without exposing token ids or secrets. -- Added a sanitised setup overview helper that summarises `CT_CVE_SERVICE_TOKENS`, `CT_CVE_INVENTORY_PUSH_TARGETS`, and durable connection status for the active organisation. +- Added a sanitised setup overview helper that summarises `CT_CVE_SERVICE_TOKENS`, `CT_CVE_INVENTORY_PUSH_TARGETS`, and durable connection status for the active instance. - Added the CT-CVE tab alongside LDAP and SMTP integration settings. **Validation** @@ -833,7 +833,7 @@ ### Session 88 — CT-CVE durable connection status **CT-CVE connector status persistence** (`apps/web/lib/integrations/ct-cve/connection-status.ts`, `apps/web/app/api/integrations/ct-cve/v1/connection-health/route.ts`) -- Added durable, org-scoped CT-CVE connector status persisted through `system_config`, including last inventory push, finding ingest, health check, and connector error timestamps. +- Added durable, instance-scoped CT-CVE connector status persisted through `system_config`, including last inventory push, finding ingest, health check, and connector error timestamps. - Updated signed connection health to return and refresh the stored status instead of returning process-local placeholder timestamps. - Updated CT-CVE finding ingestion and inventory snapshot pushes to maintain the durable status and clear stale connector errors after successful data flow. @@ -843,7 +843,7 @@ ### Session 87 — CT-CVE inventory export connector **CT-CVE outbound inventory snapshots** (`apps/web/lib/integrations/ct-cve/inventory-export.ts`) -- Added CT Ops inventory snapshot construction for CT-CVE, scoped by organisation and limited to active hosts plus current software package rows. +- Added CT Ops inventory snapshot construction for CT-CVE, scoped by instance and limited to active hosts plus current software package rows. - Included contract metadata, stable package fingerprints, Linux distro/package manager metadata, bounded host/package page sizes, and opaque cursors for follow-up inventory pages. - Added a signed outbound push helper for `POST /api/v1/ct-ops/inventory-snapshots` using the CT-CVE `inventory:write` service-token contract. - Corrected CT Ops connection health to require `connection:read` rather than `findings:write`. @@ -854,12 +854,12 @@ ### Session 86 — CT-CVE inbound connector boundary **CT-CVE connector foundation** (`apps/web/lib/integrations/ct-cve/service-token.ts`, `apps/web/app/api/integrations/ct-cve/v1/connection-health/route.ts`) -- Added signed CT-CVE service-token verification for inbound CT Ops connector requests, including `Authorization: CT-ServiceToken`, body SHA-256 checks, HMAC signatures, timestamp skew enforcement, nonce replay protection, token revocation, org binding, and scope checks. -- Added the first signed CT Ops connector endpoint, `GET /api/integrations/ct-cve/v1/connection-health?orgId=...`, with per-token rate limiting and contract-shaped status output. +- Added signed CT-CVE service-token verification for inbound CT Ops connector requests, including `Authorization: CT-ServiceToken`, body SHA-256 checks, HMAC signatures, timestamp skew enforcement, nonce replay protection, token revocation, instance binding, and scope checks. +- Added the first signed CT Ops connector endpoint, `GET /api/integrations/ct-cve/v1/connection-health?instanceId=...`, with per-token rate limiting and contract-shaped status output. - Documented the `CT_CVE_SERVICE_TOKENS` configuration shape for early connector deployments. **Validation** -- Added focused unit coverage for valid signatures, stale timestamps, content hash mismatches, invalid signatures, replayed nonces, scope checks, and org binding. +- Added focused unit coverage for valid signatures, stale timestamps, content hash mismatches, invalid signatures, replayed nonces, scope checks, and instance binding. - Validation run: `node --experimental-strip-types --test lib/integrations/ct-cve/service-token.test.mjs`, targeted ESLint for the new connector files, `pnpm --dir apps/web type-check`, `pnpm --dir apps/web db:validate`, and `pnpm --dir apps/web test:unit`. ### Session 85 — Licensing UI and docs @@ -938,7 +938,7 @@ **Additive role model** (`apps/web/lib/auth/`, `apps/web/lib/actions/`, `apps/web/lib/db/schema/`, `apps/web/lib/db/migrations/0053_bright_black_cat.sql`) - Added persisted `roles` arrays to users and invitations while keeping the legacy single `role` column as a derived compatibility value so existing role-gated flows keep working during the transition. - Normalised session/auth loading and guard checks to treat permissions as the union of assigned roles, with explicit precedence preserved for the legacy `role` field and `pending` users continuing to carry no assigned roles. -- Updated organisation creation, invitation acceptance, invitation restore, and role-update flows to write the new additive role shape and to keep last-super-admin protections working when `super_admin` membership is removed, deactivated, or deleted. +- Updated instance creation, invitation acceptance, invitation restore, and role-update flows to write the new additive role shape and to keep last-super-admin protections working when `super_admin` membership is removed, deactivated, or deleted. **Team management UI** (`apps/web/app/(dashboard)/team/`, `apps/web/tests/e2e/team/`) - Reworked the People page to display all assigned roles as badges, support multi-role assignment from the member role menu, and support multi-role invitations from the invite dialog. @@ -952,7 +952,7 @@ **Confirmed finding model and reports** (`apps/web/lib/db/schema/vulnerabilities.ts`, `apps/web/lib/actions/vulnerabilities.ts`, `apps/web/app/(dashboard)/reports/vulnerabilities/`) - Added explicit vulnerability finding confidence and match-reason persistence so confirmed Linux package matches are distinguishable from probable/future best-effort matches. - Defaulted host and global vulnerability reports to confirmed findings while adding a report confidence filter for operators who want to inspect probable matches. -- Added a migration for `host_vulnerability_findings.confidence`, `match_reason`, and supporting org/status/confidence lookup. +- Added a migration for `host_vulnerability_findings.confidence`, `match_reason`, and supporting instance/status/confidence lookup. - Added a host Overview vulnerability assessment card showing affected/clear/stale/not-assessed status, confirmed finding counts, critical/high split, last inventory scan time, last vulnerability feed sync time, and a direct link to host findings. **Red Hat and matcher accuracy** (`apps/ingest/internal/vuln/`) @@ -1011,7 +1011,7 @@ **Vulnerability operations UI** (`apps/web/app/(dashboard)/settings/vulnerabilities/`, `apps/web/lib/actions/vulnerabilities.ts`) - Added an admin-only **Administration → Vulnerabilities** page that shows vulnerability feed/API connection status, upstream API URLs, last attempt/success times, pulled record counts, and recent connection errors without exposing secrets. - Added a live CVE catalog view backed by `vulnerability_cves`, including pulled CVE counts, severity/KEV summaries, affected-package rule counts, source filtering, and CVE/title search so users can confirm API data is present independently of host findings. -- Added a bounded, rate-limited server action for the management snapshot, with organisation admin authorization and org-scoped open finding counts. +- Added a bounded, rate-limited server action for the management snapshot, with instance admin authorization and instance-scoped open finding counts. - Added sync policy visibility plus expected feed rows for NVD, CISA KEV, Debian, Ubuntu OSV, Alpine SecDB, and Red Hat, so the page shows the APIs CT-Ops is supposed to contact even before the first sync attempt. Ingest now stores the attempted upstream URL in source metadata for accurate display after environment overrides. - Added an admin NVD API key control to **Administration → Vulnerabilities**. The key is stored encrypted in `system_config` as `vulnerability_nvd_api_key`; ingest uses it when `NVD_API_KEY` is not set, while keeping the environment variable as the deployment-level override. @@ -1028,7 +1028,7 @@ - Added distro-aware version comparison plus asynchronous host matching after feed syncs and software inventory ingestion; unsupported package sources are left unassessed rather than treated as safe. **Persistence and reporting** (`apps/web/lib/db/schema/`, `apps/web/lib/actions/vulnerabilities.ts`, `apps/web/app/(dashboard)/reports/vulnerabilities/`) -- Added normalized CVE catalog, vulnerability source state, affected package ranges, and per-host finding tables, scoped by organisation for host findings. +- Added normalized CVE catalog, vulnerability source state, affected package ranges, and per-host finding tables, scoped by instance for host findings. - Added the Pro-gated **Reports → Vulnerabilities** page with filters for CVE, package, severity, KEV, fix availability, host group, distro, and package source. - Added an **Inventory → Vulnerabilities** host detail tab showing CVE findings for a selected host. @@ -1039,8 +1039,8 @@ ### Session 73 — Build document builder **Build Docs product area** (`apps/web/app/(dashboard)/build-docs/`, `apps/web/lib/actions/build-docs.ts`, `apps/web/lib/db/schema/build-docs.ts`) -- Added a new Build Docs workspace for organisation-level build document templates, reusable snippets, structured reorderable sections, screenshot/image assets, browser preview, and PDF/DOCX exports. -- Added immutable template versions, snippet provenance on inserted sections, document/section revision snapshots, Postgres full-text search vectors, and org-scoped RLS-backed database tables. +- Added a new Build Docs workspace for instance-level build document templates, reusable snippets, structured reorderable sections, screenshot/image assets, browser preview, and PDF/DOCX exports. +- Added immutable template versions, snippet provenance on inserted sections, document/section revision snapshots, Postgres full-text search vectors, and instance-scoped RLS-backed database tables. - Added filesystem-backed image storage by default plus S3-compatible storage settings and storage adapter support for object storage deployments. - Replaced the placeholder Runbooks route with a redirect to Build Docs and added Build Docs to navigation and the command palette. @@ -1094,9 +1094,9 @@ ### Session 68 — Centralised authz guards **Shared authz guard layer** (`apps/web/lib/auth/guards.ts`, `apps/web/lib/actions/`, `apps/web/lib/eslint/`, `SECURITY.md`) -- Added shared web authz helpers for active-user, same-org, admin-role, and writable-role checks so role and organisation enforcement lives in one place instead of being reimplemented ad hoc across server actions. -- Migrated the remaining raw `session.user` authorisation checks in org settings, networks, notes, certificates, terminal, task schedules, software inventory, notifications, users, and security actions onto the shared helpers or the existing `requireOrgAccess` / `requireOrgAdminAccess` wrappers. -- Added a local ESLint rule that rejects direct `session.user.organisationId` / `session.user.role` comparisons and role-list `includes()` checks in web auth/action code, and marked security finding `I-10` complete. +- Added shared web authz helpers for active-user, same-instance, admin-role, and writable-role checks so role and instance enforcement lives in one place instead of being reimplemented ad hoc across server actions. +- Migrated the remaining raw `session.user` authorisation checks in instance settings, networks, notes, certificates, terminal, task schedules, software inventory, notifications, users, and security actions onto the shared helpers or the existing `requireInstanceAccess` / `requireInstanceAdminAccess` wrappers. +- Added a local ESLint rule that rejects direct `session.user.instanceId` / `session.user.role` comparisons and role-list `includes()` checks in web auth/action code, and marked security finding `I-10` complete. **Validation** - Added focused unit coverage for the new guards and the new ESLint rule. @@ -1105,8 +1105,8 @@ ### Session 67 — Administration information architecture **Administration layout** (`apps/web/components/shared/sidebar.tsx`, `apps/web/app/(dashboard)/settings/`) -- Replaced the old Administration sidebar grouping of Team plus nested Settings with high-level areas: People, Organisation, Agents, Monitoring, Integrations, Security, and System. -- Split the settings UI into focused pages with tabs: Organisation profile/licence, Agents enrolment/defaults/tag rules/software inventory, Monitoring alert defaults/notification policy/metric retention, Integrations LDAP/SMTP, and Security mTLS/terminal access. +- Replaced the old Administration sidebar grouping of Team plus nested Settings with high-level areas: People, Instance, Agents, Monitoring, Integrations, Security, and System. +- Split the settings UI into focused pages with tabs: Instance profile/licence, Agents enrolment/defaults/tag rules/software inventory, Monitoring alert defaults/notification policy/metric retention, Integrations LDAP/SMTP, and Security mTLS/terminal access. - Kept compatibility redirects for the old Alert Defaults, LDAP, and Tag Rules settings URLs, and updated in-app links plus onboarding/agent docs to point at the new locations. **Validation** @@ -1159,7 +1159,7 @@ ### Session 62 — Central SMTP relay settings **Notification email delivery** (`apps/web/lib/actions/notification-settings.ts`, `apps/web/app/(dashboard)/settings/settings-client.tsx`, `apps/web/lib/notifications/`) -- Moved SMTP relay configuration out of per-alert channels and into organisation Settings as a central SMTP relay. +- Moved SMTP relay configuration out of per-alert channels and into instance Settings as a central SMTP relay. - Added admin-only save/test actions for the relay; SMTP passwords are encrypted before storage and redacted from client responses. - Backend validation now restricts SMTP ports and rejects private/reserved relay hosts when the relay is enabled. - Alert email channels now store only channel name and recipient addresses; relay host, credentials, encryption, and sender identity come from central settings. @@ -1176,7 +1176,7 @@ **Agent operating rules** (`AGENTS.md`) - Added an E2E Database Harness section that points agents to `apps/docs/docs/development/testing.md` as the source of truth for the web E2E harness, avoiding duplicated setup details that can drift -- Added rules requiring the database-backed harness when tests depend on real SQL, migrations, constraints, auth/session rows, organisation scoping, cascades, or persisted state +- Added rules requiring the database-backed harness when tests depend on real SQL, migrations, constraints, auth/session rows, instance scoping, cascades, or persisted state - Added rules requiring explicit, relevant seed data for tests instead of relying on leaked state - Added Progress Tracking guidance: when new feature work satisfies all or part of a requirement, update `PROGRESS.md` and state what is complete versus outstanding - Added Completion Cleanup guidance: remove temporary worktrees only after commit, push, release, and any relevant image/artifact publication, and never delete worktrees with uncommitted user work @@ -1184,7 +1184,7 @@ **E2E harness docs** (`apps/docs/docs/development/testing.md`) - Expanded the Playwright/Testcontainers documentation with agent-ready detail on how the tmpfs TimescaleDB/Postgres database is created, how `DATABASE_URL` is injected before Next.js starts, and how migrations run -- Documented `getTestDb()` for direct SQL seeding, when to seed through SQL versus UI/API paths, and how the baseline `seedOrgAndUser()` fixture works +- Documented `getTestDb()` for direct SQL seeding, when to seed through SQL versus UI/API paths, and how the baseline `seedInstanceAndUser()` fixture works - Added guidance for feature-specific seed helpers, isolation maintenance, updating `APP_TABLES` for new app tables, single-spec runs, and common troubleshooting **Build state** @@ -1205,16 +1205,16 @@ --- -### Session 59 — Security, auth, and tenant-bound action hardening +### Session 59 — Security, auth, and instance-bound action hardening **Authentication and account controls** (`apps/web/lib/auth/`, `apps/web/app/(auth)/`) - Local email/password sign-ups require email verification before dashboard access by default. - Per-account login lockout added for repeated failed password attempts. - Production auth config is now validated via `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, and trusted origins instead of silently accepting unsafe defaults. -- LDAP login links are scoped to organisation context; team-management mutations derive the actor from the session. +- LDAP login links are scoped to instance context; team-management mutations derive the actor from the session. **Mutation and tenancy boundaries** (`apps/web/lib/actions/`, `apps/web/lib/security/trusted-origins.ts`) -- Server actions now require an authenticated organisation context across agents, alerts, certificates, checks, host groups/settings, LDAP, networks, notes, notifications, service accounts, settings, software inventory, tag rules, tasks, terminal, and users. +- Server actions now require an authenticated instance context across agents, alerts, certificates, checks, host groups/settings, LDAP, networks, notes, notifications, service accounts, settings, software inventory, tag rules, tasks, terminal, and users. - Trusted-origin validation added for mutation routes such as agent bundles, host queries, bundle transfer, and certificate checker calls. - Notification test targets block private/internal addresses. @@ -1344,7 +1344,7 @@ ### Session 51 — Host registration deduplication (hostname / IP overlap) **Ingest-side dedup** (`apps/ingest/internal/handlers/register.go`, `apps/ingest/internal/db/queries/hosts.sql.go`, `agent/internal/registration/registrar.go`) -- Two live hosts in the same org cannot share a hostname or IP — guard now runs at `Register` and at `approveAgent` +- Two live hosts in the same instance cannot share a hostname or IP — guard now runs at `Register` and at `approveAgent` - **Online or revoked match** → reject with `ALREADY_EXISTS` so the admin deletes the stale record first - **Offline / unknown match** → adopt the existing `agents`/`hosts` rows, rotate the new public key onto the existing agent, and preserve approval state — covers reinstall-with-wiped-data-dir cases that previously produced a duplicate "Offline" record - Agents now report non-loopback IPs in `PlatformInfo` at register time so the server can run the overlap check @@ -1364,8 +1364,8 @@ **New download route** (`apps/web/app/api/agent/bundle/route.ts`, `apps/web/lib/agent/bundle.ts`, `apps/web/lib/agent/binary.ts`) - **Settings → Agent Enrolment → Download Install Bundle** — produces a per-OS/arch zip containing the agent binary, install helper (`install.sh` on Linux/macOS, `install.ps1` on Windows), pre-populated `agent.toml`, `SHA256SUMS`, and a `README.md` -- Three token options: generate a fresh single-use token (default 7-day expiry), embed an existing active token, or ship without a token (operator exports `CT_OPS_ORG_TOKEN` before install) -- Gated to `super_admin` / `org_admin`; scoped by `organisationId`; single-use tokens persisted via `agent_enrolment_tokens` with `metadata.source = 'install-bundle'` and `metadata.os` / `metadata.arch` for audit +- Three token options: generate a fresh single-use token (default 7-day expiry), embed an existing active token, or ship without a token (operator exports `CT_OPS_ENROLMENT_TOKEN` before install) +- Gated to `super_admin` / `instance_admin`; scoped by `instanceId`; single-use tokens persisted via `agent_enrolment_tokens` with `metadata.source = 'install-bundle'` and `metadata.os` / `metadata.arch` for audit - Shared binary resolver extracted to `apps/web/lib/agent/binary.ts` so the new route reuses the download route's cache / GitHub-release / baked-binary fallback - Zip built with `jszip` - Closes carrtech-dev/ct-ops#244; PR #250 @@ -1406,7 +1406,7 @@ - Groups: full `memberOf` list with CN + DN, copy button, and client-side filter visible whenever the user has any groups - All LDAP Attributes: searchable table of every returned attribute, with Windows file-time / LDAP generalized-time values rendered as human-readable dates and the raw value shown below; binary values as `[binary NB]`; password-hash attributes excluded for safety - Removed unused sync scaffolding from `ldap_configurations` (`lastSyncAt`, `syncIntervalMinutes`, etc.) and LDAP-sourced columns from `domain_accounts` (`ldapConfigurationId`, `distinguishedName`, `groups`) — the Service Accounts register is now manual-only; live queries go through this tool -- Any authenticated org user can run a lookup; managing LDAP configs remains `org_admin` / `super_admin` +- Any authenticated instance user can run a lookup; managing LDAP configs remains `instance_admin` / `super_admin` **Follow-up fixes during the same afternoon** - **Server-action error surfacing** — `searchLdapDirectory` / `lookupDirectoryUser` wrapped in try/catch/finally so a stale client bundle (e.g. after a deploy) no longer leaves the typeahead silently stuck on the spinner; users see a "please reload" message (PR #242) @@ -1487,7 +1487,7 @@ ### Session 44 — Networks (CIDR-based auto-assignment) **Schema & migrations** (`apps/web/lib/db/schema/networks.ts`, migration 0031) -- New `networks` table — named IP subnets with CIDR range, multi-tenant +- New `networks` table — named IP subnets with CIDR range, instance-scoped - New `host_network_memberships` join table with `is_auto_assigned` flag **Server actions & UI** (`apps/web/lib/actions/networks.ts`, `apps/web/app/(dashboard)/hosts/networks/`) @@ -1496,7 +1496,7 @@ - "Networks" nav item added to sidebar under Hosts **Ingest auto-assignment** (`apps/ingest/internal/`) -- `SyncHostNetworks` matches heartbeat IPs against org network ranges and syncs auto-assignments; stale assignments removed when IPs change +- `SyncHostNetworks` matches heartbeat IPs against instance network ranges and syncs auto-assignments; stale assignments removed when IPs change - Called from heartbeat handler on every tick **Docs** @@ -1568,7 +1568,7 @@ - PRs: carrtech-dev/ct-ops#196 (notifications), #198 (all FKs), #200 (software_scans) **JWT signing key persistence** (`apps/ingest/internal/`) -- Ingest service now persists its JWT signing key in the database (org settings table) on first start +- Ingest service now persists its JWT signing key in the database (instance settings table) on first start - Survives Docker volume resets — agents no longer get 401s after a volume wipe - PR: carrtech-dev/ct-ops#194 @@ -1598,7 +1598,7 @@ **Monitoring reliability** (`agent/internal/heartbeat/heartbeat.go`, `apps/ingest/internal/db/queries/alerts.sql.go`, `apps/web/app/(dashboard)/hosts/[id]/alerts-tab.tsx`) - **CPU spike elimination**: `resultsReady` heartbeats now send a cached `hostMetricsSnapshot` collected on the regular 30s tick rather than re-sampling CPU — prevents near-zero delta windows inflating readings to 100% - **Alert double-evaluation fix**: `GetAlertRulesForHost` now filters `is_global_default = false` — global defaults (templates) were being evaluated alongside their host-specific clones -- **Global defaults visible**: Alerts tab on host detail shows a read-only "Organisation-wide Default Rules" section linking to Settings → Alerts +- **Global defaults visible**: Alerts tab on host detail shows a read-only "Instance-wide Default Rules" section linking to Settings → Alerts - PR: carrtech-dev/ct-ops#183 **Build state** @@ -1614,13 +1614,13 @@ - Linux: dpkg → rpm → pacman → apk (ordered by availability) - macOS: `system_profiler SPApplicationsDataType` + Homebrew - Windows: registry walk (`HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`) -- Snap/Flatpak/Windows Store sources toggleable via org settings +- Snap/Flatpak/Windows Store sources toggleable via instance settings - Streams packages in 500-package chunks via new `SubmitSoftwareInventory` gRPC endpoint - `task_id` injected into context from `runner.go` so handlers use it as `scan_id` **Ingest** (`apps/ingest/internal/handlers/`) - `inventory.go`: JWT-authenticated client-streaming RPC; bulk UNNEST upsert, marks removed rows on `is_last`, completes `task_run_hosts` row -- `software_sweeper.go`: 60s ticker creates `software_inventory` tasks for hosts overdue per org `intervalHours` setting +- `software_sweeper.go`: 60s ticker creates `software_inventory` tasks for hosts overdue per instance `intervalHours` setting - `software.sql.go`: bulk UNNEST upsert, removed-package marking, scan tracking queries **Database** (`apps/web/lib/db/schema/software.ts`, migrations `0028`, `0029`) @@ -1707,10 +1707,10 @@ **TypeScript actions** (`apps/web/lib/actions/notifications.ts`) - `deleteNotification` / `deleteNotifications` — converted from hard delete to soft delete (set `deleted_at`) - All inbox queries (`getNotifications`, `getUnreadCount`, `markAsRead`, `markAllAsRead`, `markBatchReadStatus`) now filter `WHERE deleted_at IS NULL` -- `deleteNotifications(orgId, userId, ids[])` — new batch soft-delete action -- `markBatchReadStatus(orgId, userId, ids[], read)` — new batch read/unread toggle -- `getNotificationStats(orgId, userId, hostId?)` — counts per severity for pie chart; optional `hostId` scopes to a specific host -- `getNotificationsOverTime(orgId, userId, range, hostId?)` — daily or hourly aggregation for line chart; intentionally omits `deleted_at` filter so deleting from the inbox never affects historical trend data; optional `hostId` scopes to a specific host; enforces 90-day maximum retention window +- `deleteNotifications(instanceId, userId, ids[])` — new batch soft-delete action +- `markBatchReadStatus(instanceId, userId, ids[], read)` — new batch read/unread toggle +- `getNotificationStats(instanceId, userId, hostId?)` — counts per severity for pie chart; optional `hostId` scopes to a specific host +- `getNotificationsOverTime(instanceId, userId, range, hostId?)` — daily or hourly aggregation for line chart; intentionally omits `deleted_at` filter so deleting from the inbox never affects historical trend data; optional `hostId` scopes to a specific host; enforces 90-day maximum retention window - `TrendRange` type exported: `'1h' | '6h' | '12h' | '24h' | '7d' | '30d' | '90d'` **UI — Notifications page** (`apps/web/app/(dashboard)/notifications/notifications-client.tsx`) @@ -1736,23 +1736,23 @@ ### Session 34 — Slack, Telegram, and in-app notification channels -**Database** (`apps/web/lib/db/schema/alerts.ts`, `auth.ts`, `organisations.ts`, migration `0026_youthful_anthem`) +**Database** (`apps/web/lib/db/schema/alerts.ts`, `auth.ts`, `instances.ts`, migration `0026_youthful_anthem`) - `notifications` table: per-user rows with subject, body, severity, resourceType, resourceId, read flag, alertInstanceId FK - `notificationsEnabled` column added to `user` table (default true) -- `OrgNotificationSettings` added to `OrgMetadata` JSONB: `inAppEnabled`, `inAppRoles`, `allowUserOptOut` +- `InstanceNotificationSettings` added to `InstanceMetadata` JSONB: `inAppEnabled`, `inAppRoles`, `allowUserOptOut` - `NotificationChannelType` expanded to `'webhook' | 'smtp' | 'slack' | 'telegram'` - `SlackChannelConfig { webhookUrl }` and `TelegramChannelConfig { botToken; chatId }` interfaces added **Go ingest service** (`apps/ingest/internal/`) -- `alerts.sql.go`: `GetEnabledSlackChannels`, `GetEnabledTelegramChannels`, `GetOrgNotificationSettings`, `GetAlertTargetUsers` (role + opt-out filter), `InsertNotificationBatch` (pgx.Batch) -- `notify.go`: `postSlack` (Block Kit JSON), `dispatchSlack`, `postTelegram` (Bot API HTML mode), `dispatchTelegram`, `dispatchInApp` (org settings → user targeting → batch insert) +- `alerts.sql.go`: `GetEnabledSlackChannels`, `GetEnabledTelegramChannels`, `GetInstanceNotificationSettings`, `GetAlertTargetUsers` (role + opt-out filter), `InsertNotificationBatch` (pgx.Batch) +- `notify.go`: `postSlack` (Block Kit JSON), `dispatchSlack`, `postTelegram` (Bot API HTML mode), `dispatchTelegram`, `dispatchInApp` (instance settings → user targeting → batch insert) - `alerts.go`: `notifChannels` struct expanded; all evaluators (`check_status`, `metric_threshold`, `cert_expiry`) call Slack + Telegram + in-app dispatch on fire and resolve **TypeScript actions** (`apps/web/lib/actions/`) - `alerts.ts`: Zod discriminated union extended for Slack/Telegram; `NotificationChannelSafe` union updated (Telegram masks botToken as `hasBotToken`); `updateNotificationChannel` and `sendTestNotification` handle all four types - `notifications.ts`: `getNotifications`, `getUnreadCount`, `markAsRead`, `markAllAsRead`, `deleteNotification` -- `notification-settings.ts`: `getOrgNotificationSettings` (with defaults), `updateOrgNotificationSettings` (admin-only, Zod-validated) -- `profile.ts`: `updateNotificationPreference` (respects org `allowUserOptOut`) +- `notification-settings.ts`: `getInstanceNotificationSettings` (with defaults), `updateInstanceNotificationSettings` (admin-only, Zod-validated) +- `profile.ts`: `updateNotificationPreference` (respects instance `allowUserOptOut`) **UI — Alerts page** (`apps/web/app/(dashboard)/alerts/alerts-client.tsx`) - `AddSlackDialog`, `EditSlackDialog`, `AddTelegramDialog`, `EditTelegramDialog` components following existing dialog pattern @@ -1774,10 +1774,10 @@ - Polls every 30 s **UI — Settings** (`apps/web/app/(dashboard)/settings/settings-client.tsx`) -- "Notification Settings" card: Enable in-app toggle, role checkboxes (super_admin/org_admin/engineer/read_only), Allow user opt-out toggle; admin-only +- "Notification Settings" card: Enable in-app toggle, role checkboxes (super_admin/instance_admin/engineer/read_only), Allow user opt-out toggle; admin-only **UI — Profile** (`apps/web/app/(dashboard)/profile/profile-client.tsx`) -- "Notifications" card: toggle visible when org `inAppEnabled`; disabled with explanatory text when org disallows opt-out +- "Notifications" card: toggle visible when instance `inAppEnabled`; disabled with explanatory text when instance disallows opt-out **Sidebar** (`apps/web/components/shared/sidebar.tsx`) - "Notifications" entry added to Monitoring group (BellPlus icon, `/notifications`) @@ -1847,7 +1847,7 @@ **Per-user authentication** (`apps/web/lib/db/schema/terminal-sessions.ts`, `agent/internal/terminal/session.go`) - New `username` column on `terminal_sessions` table — migration `0025_luxuriant_smasher.sql` - Agent launches PTY via `su -l ` with dropped privileges (not `login`, which varies across distros) -- Organisation-level "Direct Access" toggle (`terminalDirectAccess` in org metadata) allows bypassing username requirement +- Instance-level "Direct Access" toggle (`terminalDirectAccess` in instance metadata) allows bypassing username requirement - UI shows username input on terminal tab; direct access mode skips it **Shell environment hardening** (`agent/internal/terminal/session.go`) @@ -1872,7 +1872,7 @@ - New `TerminalStream` RPC on ingest service for bidirectional terminal I/O **Database schema** (`apps/web/lib/db/schema/terminal-sessions.ts`, migration `0024_flat_blade.sql`) -- `terminal_sessions` table: session ID, host ID, user ID, org ID, status (pending/connected/disconnected/failed), timestamps +- `terminal_sessions` table: session ID, host ID, user ID, instance ID, status (pending/connected/disconnected/failed), timestamps **Ingest: session routing** (`apps/ingest/`) - Pending terminal sessions included in every heartbeat response so agent picks them up @@ -1888,8 +1888,8 @@ - WebSocket connection from browser → Next.js API route → ingest gRPC stream - Container shown during "connecting" state to avoid 0x0 dimension bug with xterm -**Organisation settings** (`apps/web/app/(dashboard)/settings/settings-client.tsx`) -- Terminal enable/disable toggle and port configuration in org settings +**Instance settings** (`apps/web/app/(dashboard)/settings/settings-client.tsx`) +- Terminal enable/disable toggle and port configuration in instance settings **Build state** - `pnpm run build` — zero TypeScript errors ✅ @@ -1956,7 +1956,7 @@ ### Session 26 — General agent task framework with Linux host patching **Database schema** (`apps/web/lib/db/schema/tasks.ts`, migrations `0021`–`0022`) -- `task_runs` table: type, status, config jsonb, `max_parallel`, org/created-by FKs, started/completed timestamps +- `task_runs` table: type, status, config jsonb, `max_parallel`, instance/created-by FKs, started/completed timestamps - `task_run_hosts` table: per-host execution state (pending → running → completed/failed/cancelled/skipped), `raw_output` text accumulator, exit_code, reboot_required, packages_updated jsonb - `max_parallel` enforced at query level — SQL counts active rows before dispatching so concurrent ingest instances cannot over-dispatch @@ -1995,22 +1995,22 @@ ### Session 25 — Host groups with collapsible sidebar navigation **Database schema** (`apps/web/lib/db/schema/host-groups.ts`, migration `0021_chilly_tomas.sql`) -- `host_groups` table: name, description, org FK, standard timestamps -- `host_group_members` table: group FK, host FK, agent FK, org FK — join table with audit timestamps +- `host_groups` table: name, description, instance FK, standard timestamps +- `host_group_members` table: group FK, host FK, agent FK, instance FK — join table with audit timestamps **Server actions** (`apps/web/lib/actions/host-groups.ts`) -- `createHostGroup`, `updateHostGroup`, `deleteHostGroup` — full CRUD, Zod-validated, org-scoped -- `getHostGroups(orgId)` — returns groups with member count -- `getHostGroup(orgId, groupId)` — returns group + full member list +- `createHostGroup`, `updateHostGroup`, `deleteHostGroup` — full CRUD, Zod-validated, instance-scoped +- `getHostGroups(instanceId)` — returns groups with member count +- `getHostGroup(instanceId, groupId)` — returns group + full member list - `addHostToGroup`, `removeHostFromGroup` — membership management **Groups UI** (`apps/web/app/(dashboard)/hosts/groups/`) - `/hosts/groups` list page: create dialog, edit inline, delete with confirmation, member count badge -- `/hosts/groups/[id]` detail page: group metadata header, member list table with remove button, "Add Host" dialog with search/filter over org hosts not already in the group +- `/hosts/groups/[id]` detail page: group metadata header, member list table with remove button, "Add Host" dialog with search/filter over instance hosts not already in the group - Host detail page "Groups" tab: shows current group memberships with inline add and remove **Sidebar restructure** (`apps/web/components/shared/sidebar.tsx`) -- Collapsible parent/child navigation: Hosts → All Hosts + Groups; Settings → Organisation + Agent Enrolment + Alert Defaults + LDAP + System Health +- Collapsible parent/child navigation: Hosts → All Hosts + Groups; Settings → Instance + Agent Enrolment + Alert Defaults + LDAP + System Health - `CollapsibleSidebarItem` component with chevron indicator; auto-expands when a child route is active - Added shadcn `textarea` and `form` UI components @@ -2083,9 +2083,9 @@ ### Session 21 — Phase 4: LDAP directory integration and service account restructure **Service account / directory account split** (`apps/web/app/(dashboard)/service-accounts/`, `apps/web/app/(dashboard)/hosts/[id]/`) -- Local OS users moved from top-level service accounts page to per-host "Users" and "Settings" tabs; these are host-scoped, not org-level inventory +- Local OS users moved from top-level service accounts page to per-host "Users" and "Settings" tabs; these are host-scoped, not instance-level inventory - New "Service Accounts" top-level page targets network/domain accounts sourced from LDAP/AD -- Per-host "Settings" tab: collection toggles (CPU, Memory, Disk on by default; Local Users opt-in); org-level defaults applied to newly enrolled hosts +- Per-host "Settings" tab: collection toggles (CPU, Memory, Disk on by default; Local Users opt-in); instance-level defaults applied to newly enrolled hosts **LDAP / Active Directory integration** (`apps/web/lib/ldap/client.ts`, `apps/web/app/api/auth/ldap/route.ts`, `apps/web/app/(dashboard)/settings/ldap/`) - `ldap_configurations` table: host, port, bind DN/password (AES-256-GCM encrypted at rest using `LDAP_ENCRYPTION_KEY`), base DN, user/group filters, TLS mode — migration `0016` @@ -2116,8 +2116,8 @@ - **Overview** now shows the operational state of infrastructure: Agents (online/offline), Certificates (valid/expiring/expired), Active Alerts (firing/acknowledged), and a Summary panel. All cards link through to their respective detail pages. **New `/api/overview` endpoint** (`apps/web/app/api/overview/route.ts`) -- Returns `agents`, `certificates`, and `alerts` counts scoped to the user's organisation. -- `/api/system/health` stripped of certificate and alert queries — now only queries agents and org config. +- Returns `agents`, `certificates`, and `alerts` counts scoped to the user's instance. +- `/api/system/health` stripped of certificate and alert queries — now only queries agents and instance config. **New `DashboardClient` component** (`apps/web/app/(dashboard)/dashboard/dashboard-client.tsx`) - Polls `/api/overview` every 30 seconds (matching System Health behaviour). @@ -2156,7 +2156,7 @@ ### Session 16 — Phase 3 Certificate Management **Database schema** (`apps/web/lib/db/schema/certificates.ts`, migration `0013_certificates.sql`) -- New `certificates` table with composite unique index on `(org_id, host, port, server_name, fingerprint_sha256)`, expiry and status indexes, soft delete, `source` column (`discovered|imported|issued`) for future CA work, `discoveredByHostId` field (semantically scoped to discovery, not deployment) +- New `certificates` table with composite unique index on `(instance_id, host, port, server_name, fingerprint_sha256)`, expiry and status indexes, soft delete, `source` column (`discovered|imported|issued`) for future CA work, `discoveredByHostId` field (semantically scoped to discovery, not deployment) - New `certificate_events` table for append-only event spine: discovered, renewed, expiring_soon, expired, restored, removed - `CertificateStatus`, `CertificateSource`, `CertificateEventType` TypeScript types @@ -2165,10 +2165,10 @@ - Zod schema in `createCheck` / `updateCheck` accepts the new type **Web: server actions** (`apps/web/lib/actions/certificates.ts`, `apps/web/lib/certificates/expiry.ts`) -- `getCertificates(orgId, filters)` — paginated, filterable by status/host, sortable -- `getCertificate(orgId, certId)` — returns cert + events -- `getCertificateCounts(orgId)` — valid/expiring_soon/expired/invalid tallies -- `deleteCertificate(orgId, certId)` — soft delete +- `getCertificates(instanceId, filters)` — paginated, filterable by status/host, sortable +- `getCertificate(instanceId, certId)` — returns cert + events +- `getCertificateCounts(instanceId)` — valid/expiring_soon/expired/invalid tallies +- `deleteCertificate(instanceId, certId)` — soft delete - `computeExpiryStatus(notAfter, warnDays)` and `formatDaysUntil(date)` helpers **Web: UI** (`apps/web/app/(dashboard)/certificates/`, `apps/web/components/certificates/`) @@ -2192,13 +2192,13 @@ - Wired into heartbeat handler via per-heartbeat `GetChecksForHost` type map **Ingest: cert expiry alert evaluator + sweeper** (`apps/ingest/internal/handlers/alerts.go`, `apps/ingest/cmd/ingest/main.go`) -- `evaluateCertExpiryForCert` — called immediately after persist; loads org's cert_expiry rules, evaluates each +- `evaluateCertExpiryForCert` — called immediately after persist; loads instance's cert_expiry rules, evaluates each - `evaluateCertExpiryRule` — fires/resolves `alert_instances` row keyed by `ruleID + metadata.certificateId`; uses cert's `discovered_by_host_id` as FK-safe `host_id`; dispatches via existing webhook + SMTP pipeline -- `RunCertExpirySweeper` goroutine — ticks every 15 min, sweeps all orgs with cert_expiry rules +- `RunCertExpirySweeper` goroutine — ticks every 15 min, sweeps all instances with cert_expiry rules - Sweeper started from `main.go` **Go queries** (`apps/ingest/internal/db/queries/certificates.sql.go`) -- `UpsertCertificate`, `FindCertsForEndpoint`, `InsertCertificateEvent`, `GetActiveCertAlertInstance`, `InsertCertAlertInstance`, `GetCertExpiryRulesForOrg`, `GetAllOrgsWithCertExpiryRules`, `ListCertificatesExpiringWithin`, `GetCertificateByID` +- `UpsertCertificate`, `FindCertsForEndpoint`, `InsertCertificateEvent`, `GetActiveCertAlertInstance`, `InsertCertAlertInstance`, `GetCertExpiryRulesForInstance`, `GetAllInstancesWithCertExpiryRules`, `ListCertificatesExpiringWithin`, `GetCertificateByID` **Build state** - `pnpm run build` (apps/web) — zero TypeScript errors ✅ @@ -2228,9 +2228,9 @@ - `host_metrics_daily` — 1-day bucket CAGG, refresh policy: every day, covering last 3 days - `getHostMetrics` (in `apps/web/lib/actions/agents.ts`) now queries from `host_metrics_hourly` for 24h range and `host_metrics_daily` for 7d range using raw SQL via `db.execute(sql\`...\`)`; falls back to the raw `host_metrics` table if the view doesn't exist (graceful degradation for plain PostgreSQL) -**Metric retention setting** (`apps/web/lib/db/schema/organisations.ts`, `apps/web/lib/actions/settings.ts`, `apps/web/app/(dashboard)/settings/settings-client.tsx`) -- New `metricRetentionDays` integer column (default 30) on `organisations` table — migration `0011_overrated_mongu.sql` -- `updateMetricRetention(orgId, days)` server action validates 1–3650 days; admin-only +**Metric retention setting** (`apps/web/lib/db/schema/instances.ts`, `apps/web/lib/actions/settings.ts`, `apps/web/app/(dashboard)/settings/settings-client.tsx`) +- New `metricRetentionDays` integer column (default 30) on `instances` table — migration `0011_overrated_mongu.sql` +- `updateMetricRetention(instanceId, days)` server action validates 1–3650 days; admin-only - New "Metric Retention" card in Settings UI with a Select (7 / 14 / 30 / 60 / 90 / 180 days / 1 year); Save button disabled when value matches current DB value **Build state** @@ -2269,7 +2269,7 @@ ### Session 13 — Alert silencing + migration runner root-cause fix **Alert silencing feature** (`apps/web/lib/db/schema/alerts.ts`, `apps/web/lib/actions/alerts.ts`, `apps/web/app/(dashboard)/alerts/alerts-client.tsx`, `apps/web/app/(dashboard)/hosts/[id]/alerts-tab.tsx`, `apps/ingest/internal/db/queries/alerts.sql.go`, `apps/ingest/internal/handlers/alerts.go`) -- New `alert_silences` table — host-scoped or org-wide time windows that suppress alert evaluation; migration `0010_eager_chameleon.sql` generated via `db:generate` +- New `alert_silences` table — host-scoped or instance-wide time windows that suppress alert evaluation; migration `0010_eager_chameleon.sql` generated via `db:generate` - Server actions: `getSilences`, `getActiveSilencesForHost`, `createSilence`, `deleteSilence` - Go ingest: `IsHostSilenced` query short-circuits `evaluateAlerts` so silenced hosts skip rule evaluation entirely - UI: dedicated Silences card on `/alerts` page with Active/Upcoming/Expired badges + add dialog; per-host "Silence Host" button and amber active-silence banner with one-click remove on the host detail Alerts tab @@ -2347,11 +2347,11 @@ **Global alert defaults** (`apps/web/lib/db/schema/alerts.ts`, migration `0009_global_alert_defaults.sql`, `apps/web/lib/actions/alerts.ts`, `apps/web/lib/actions/agents.ts`) - New `isGlobalDefault` boolean column on `alertRules` (default false); migration `0009` adds it -- `getGlobalAlertDefaults(orgId)` — fetches all global-default rules for the org -- `createGlobalAlertDefault(orgId, input)` — creates a rule with `isGlobalDefault = true`; only `metric_threshold` type allowed for defaults -- `deleteGlobalAlertDefault(orgId, ruleId)` — soft-deletes a global default rule -- `applyGlobalDefaultsToHost(orgId, hostId)` — clones each active global-default rule as a host-scoped rule; called from `approveAgent` immediately after manual approval -- `getAlertRules` now excludes global defaults from regular host/org rule listings (prevents duplicates in the Alerts tab) +- `getGlobalAlertDefaults(instanceId)` — fetches all global-default rules for the instance +- `createGlobalAlertDefault(instanceId, input)` — creates a rule with `isGlobalDefault = true`; only `metric_threshold` type allowed for defaults +- `deleteGlobalAlertDefault(instanceId, ruleId)` — soft-deletes a global default rule +- `applyGlobalDefaultsToHost(instanceId, hostId)` — clones each active global-default rule as a host-scoped rule; called from `approveAgent` immediately after manual approval +- `getAlertRules` now excludes global defaults from regular host/instance rule listings (prevents duplicates in the Alerts tab) **Global Alert Defaults settings page** (`apps/web/app/(dashboard)/settings/alerts/`) - New `page.tsx` — admin-only server component; fetches initial defaults, passes to client @@ -2366,9 +2366,9 @@ ### Session 9 — Alert rule builder + alert state machine **Schema** (`apps/web/lib/db/schema/alerts.ts`, migration `0008_alert_rules.sql`) -- `alertRules` table: org-scoped, hostId nullable (null = org-wide), conditionType, config JSONB, severity, enabled -- `alertInstances` table: ruleId, hostId, orgId, status (firing/resolved/acknowledged), message, triggeredAt, resolvedAt, acknowledgedAt/By -- `notificationChannels` table: orgId, name, type='webhook', config JSONB (url + optional secret), enabled +- `alertRules` table: instance-scoped, hostId nullable (null = instance-wide), conditionType, config JSONB, severity, enabled +- `alertInstances` table: ruleId, hostId, instanceId, status (firing/resolved/acknowledged), message, triggeredAt, resolvedAt, acknowledgedAt/By +- `notificationChannels` table: instanceId, name, type='webhook', config JSONB (url + optional secret), enabled **Ingest alert evaluation** (`apps/ingest/internal/db/queries/alerts.sql.go`, `apps/ingest/internal/handlers/alerts.go`, `apps/ingest/internal/handlers/notify.go`) - `GetAlertRulesForHost`, `GetActiveAlertInstance`, `InsertAlertInstance`, `ResolveAlertInstance`, `GetRecentCheckResults`, `GetEnabledWebhookChannels` — Go query functions @@ -2379,9 +2379,9 @@ - `processHeartbeat` signature extended with `hostname string` param; both call sites updated **Server actions** (`apps/web/lib/actions/alerts.ts`) -- `getAlertRules(orgId, hostId?)` — uses `or(eq, isNull)` for org-wide rule inclusion +- `getAlertRules(instanceId, hostId?)` — uses `or(eq, isNull)` for instance-wide rule inclusion - `createAlertRule`, `updateAlertRule`, `deleteAlertRule` (soft delete), `getAlertInstances`, `acknowledgeAlert` -- `getActiveAlertCountsForHosts(orgId, hostIds[])` — GROUP BY for inventory badge +- `getActiveAlertCountsForHosts(instanceId, hostIds[])` — GROUP BY for inventory badge - `getNotificationChannels` (redacts secret → `hasSecret: boolean`), `createNotificationChannel`, `deleteNotificationChannel` **Alerts page** (`apps/web/app/(dashboard)/alerts/page.tsx`, `alerts-client.tsx`) @@ -2393,7 +2393,7 @@ **Host detail Alerts tab** (`apps/web/app/(dashboard)/hosts/[id]/alerts-tab.tsx`) - Host-specific rules section with Add Rule dialog (conditionType selector, check picker / metric config, severity) -- Enable/disable `` and delete per rule; org-wide rules shown read-only in separate card +- Enable/disable `` and delete per rule; instance-wide rules shown read-only in separate card - Active alert count badge pulled via TanStack Query; shown in red if > 0 - Host detail `page.tsx` now passes `currentUserId` to `HostDetailClient` - `host-detail-client.tsx`: new `'alerts'` tab with red count badge; `getAlertInstances` query for badge count @@ -2443,10 +2443,10 @@ ### Session 7 — Ad-hoc agent queries (port and service discovery) **`agent_queries` schema** (`apps/web/lib/db/schema/agent-queries.ts`) -- `agent_queries` table: org_id, host_id, query_type (`list_ports` | `list_services`), status (`pending` | `complete` | `error`), result jsonb, error_message, expires_at (2-minute TTL), requested/completed timestamps +- `agent_queries` table: instance_id, host_id, query_type (`list_ports` | `list_services`), status (`pending` | `complete` | `error`), result jsonb, error_message, expires_at (2-minute TTL), requested/completed timestamps **API routes** (`apps/web/app/api/hosts/[id]/queries/`) -- `POST /api/hosts/[id]/queries` — creates a pending query, returns query ID; auth-guarded with org membership check +- `POST /api/hosts/[id]/queries` — creates a pending query, returns query ID; auth-guarded with instance membership check - `GET /api/hosts/[id]/queries/[queryId]` — polls query status and returns result when complete; 1-second client poll interval **Ingest: push pending queries to open streams** (`apps/ingest/internal/handlers/heartbeat.go`) @@ -2494,9 +2494,9 @@ _(Built between Sessions 3 and 4; not previously documented)_ **Bootstrap install script** (`apps/web/app/api/agent/install/route.ts`, `agent/cmd/agent/main.go`) - `curl -fsSL "https://server/api/agent/install" | sh` downloads the installer without placing enrolment tokens into URLs -- Shell script detects OS/arch, downloads the versioned binary, and installs when `CT_OPS_ORG_TOKEN` is present in the runtime environment -- Agent `--install` accepts either `--token` or `CT_OPS_ORG_TOKEN`, then copies the binary to the system path, writes TOML config, installs the service unit, and starts the service -- Also supports `-address` CLI flag and `CT_OPS_ORG_TOKEN` / `CT_OPS_INGEST_ADDRESS` env vars for config-less operation +- Shell script detects OS/arch, downloads the versioned binary, and installs when `CT_OPS_ENROLMENT_TOKEN` is present in the runtime environment +- Agent `--install` accepts either `--token` or `CT_OPS_ENROLMENT_TOKEN`, then copies the binary to the system path, writes TOML config, installs the service unit, and starts the service +- Also supports `-address` CLI flag and `CT_OPS_ENROLMENT_TOKEN` / `CT_OPS_INGEST_ADDRESS` env vars for config-less operation - Enrolment token dialog now keeps the token separate from the installer command so the secret is not reflected into copied URLs **Multi-platform service install** (`agent/internal/install/install.go`, `agent/cmd/agent/service_windows.go`) @@ -2528,8 +2528,8 @@ _(Built between Sessions 3 and 4; not previously documented)_ - `HeartbeatRequest` gains `check_results` field; `HeartbeatResponse` gains `checks` field **`checks` + `check_results` schema** (`apps/web/lib/db/schema/checks.ts`) -- `checks` table: org/host scoped, check_type, config jsonb, enabled, interval_seconds, soft delete, metadata -- `check_results` hypertable: check_id, host_id, org_id, ran_at (partition key), status, output, duration_ms +- `checks` table: instance/host scoped, check_type, config jsonb, enabled, interval_seconds, soft delete, metadata +- `check_results` hypertable: check_id, host_id, instance_id, ran_at (partition key), status, output, duration_ms - Migration `0006_icy_trish_tilby.sql` — includes TimescaleDB hypertable + 30-day retention, graceful degradation wrapped in `DO $$` block **Ingest handler updates** (`apps/ingest/internal/handlers/heartbeat.go`) @@ -2545,7 +2545,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ - `heartbeat.go` updated to drain check results into each request and update definitions from each response **Web server actions** (`apps/web/lib/actions/checks.ts`) -- `getChecks`, `createCheck`, `updateCheck`, `deleteCheck`, `getCheckResults` — all Zod-validated, org-scoped +- `getChecks`, `createCheck`, `updateCheck`, `deleteCheck`, `getCheckResults` — all Zod-validated, instance-scoped **Checks tab** (`apps/web/app/(dashboard)/hosts/[id]/checks-tab.tsx`) - Expandable check rows: name, type badge, status badge, last run time @@ -2563,7 +2563,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ ### Session 5 — Metric history, TimescaleDB hypertable, metric graphs **`host_metrics` TimescaleDB hypertable** (`apps/web/lib/db/schema/metrics.ts`) -- New table: `id, organisation_id, host_id, recorded_at, cpu_percent, memory_percent, disk_percent, uptime_seconds, created_at` +- New table: `id, instance_id, host_id, recorded_at, cpu_percent, memory_percent, disk_percent, uptime_seconds, created_at` - Migration `0005_wet_photon.sql` creates the table, converts it to a TimescaleDB hypertable on `recorded_at`, and adds a 30-day retention policy. Wrapped in `DO $$` block for graceful degradation if TimescaleDB is not available. **Ingest: persist metric rows** (`apps/ingest/internal/db/queries/metrics.sql.go`) @@ -2574,7 +2574,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ - Replaced `math/rand` with `crypto/rand` — IDs are now cryptographically random **`getHostMetrics` server action** (`apps/web/lib/actions/agents.ts`) -- `getHostMetrics(orgId, hostId, range: '1h'|'24h'|'7d')` — queries `host_metrics` with a computed cutoff timestamp, returns rows ordered by `recorded_at` asc +- `getHostMetrics(instanceId, hostId, range: '1h'|'24h'|'7d')` — queries `host_metrics` with a computed cutoff timestamp, returns rows ordered by `recorded_at` asc **Metrics tab on host detail page** (`apps/web/app/(dashboard)/hosts/[id]/host-detail-client.tsx`) - Fourth tab: Metrics @@ -2584,7 +2584,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ - Refetches every 60 s; only fetches when the Metrics tab is active **Offline period visualisation** (`apps/web/app/(dashboard)/hosts/[id]/host-detail-client.tsx`) -- `getAgentOfflinePeriods(orgId, agentId, range)` server action — walks `agent_status_history` to build `{start, end}` offline windows within the visible time range; looks back one extra hour to capture periods that started before the window +- `getAgentOfflinePeriods(instanceId, agentId, range)` server action — walks `agent_status_history` to build `{start, end}` offline windows within the visible time range; looks back one extra hour to capture periods that started before the window - Chart X-axis domain always extends to `Date.now()` via a sentinel null point so time advances even when no new rows are arriving - `ReferenceArea` rendered for each offline window — light gray tint (`fillOpacity: 0.15`), dark readable "Offline" label - Zero-value boundary points injected at each offline start/end so lines visually drop to 0% during the outage and rise again on reconnect @@ -2614,14 +2614,14 @@ _(Built between Sessions 3 and 4; not previously documented)_ - Allows `offline` agents to reconnect and transition back to `active` **Host detail page** (`apps/web/app/(dashboard)/hosts/[id]/`) -- `page.tsx` — server component, fetches host via `getHost(orgId, hostId)`, 404 if not found +- `page.tsx` — server component, fetches host via `getHost(instanceId, hostId)`, 404 if not found - `host-detail-client.tsx` — tabbed UI: - **Overview tab**: CPU / memory / disk gauges (green ≤ 70 %, amber ≤ 90 %, red > 90 %); system info panel (hostname, OS, version, arch, uptime, IPs); agent info panel (status badge, version, agent ID, last heartbeat, registration date) - **Storage tab**: per-disk table (mount point, device, filesystem, total/used/free, usage %) from `host.metadata.disks` - **Network tab**: interface table (name, MAC, IPs extracted from CIDR, Up/Down badge) from `host.metadata.network_interfaces` **SSE streaming** (`apps/web/app/api/hosts/[id]/stream/route.ts`) -- GET `/api/hosts/{id}/stream` — requires valid session and org membership +- GET `/api/hosts/{id}/stream` — requires valid session and instance membership - Sends initial snapshot immediately on connection - Polls DB every 5 s and pushes `update` events as SSE JSON - Sends `error` event if host not found; closes cleanly on client disconnect (abort signal) @@ -2629,11 +2629,11 @@ _(Built between Sessions 3 and 4; not previously documented)_ **useHostStream hook** (`apps/web/hooks/use-host-stream.ts`) - `'use client'` hook consumed by `HostDetailClient` - Opens `EventSource` to `/api/hosts/{hostId}/stream` -- Writes each `update` event directly into React Query cache key `['host', orgId, hostId]` +- Writes each `update` event directly into React Query cache key `['host', instanceId, hostId]` - Closes on unmount; auto-reconnects on remount **Server action added** -- `getHost(orgId, hostId)` in `lib/actions/agents.ts` — single-host fetch with agent LEFT JOIN +- `getHost(instanceId, hostId)` in `lib/actions/agents.ts` — single-host fetch with agent LEFT JOIN **Agent example config** (`agent/examples/agent.toml`) - Reference config file for operators @@ -2697,13 +2697,13 @@ _(Built between Sessions 3 and 4; not previously documented)_ - `lib/db/schema/index.ts` — updated with three new exports **Server actions** (`lib/actions/agents.ts`) -- `listPendingAgents(orgId)` — pending agents for admin approval -- `approveAgent(orgId, agentId, actorId)` — sets status active + appends history -- `rejectAgent(orgId, agentId, actorId)` — sets status revoked + appends history -- `listHosts(orgId)` — left join with agents, returns `HostWithAgent[]` -- `createEnrolmentToken(orgId, userId, input)` — creates token with label/auto-approve/maxUses/expiry -- `listEnrolmentTokens(orgId)` — active tokens for org -- `revokeEnrolmentToken(orgId, tokenId)` — soft delete +- `listPendingAgents(instanceId)` — pending agents for admin approval +- `approveAgent(instanceId, agentId, actorId)` — sets status active + appends history +- `rejectAgent(instanceId, agentId, actorId)` — sets status revoked + appends history +- `listHosts(instanceId)` — left join with agents, returns `HostWithAgent[]` +- `createEnrolmentToken(instanceId, userId, input)` — creates token with label/auto-approve/maxUses/expiry +- `listEnrolmentTokens(instanceId)` — active tokens for instance +- `revokeEnrolmentToken(instanceId, tokenId)` — soft delete **Web UI** - `app/(dashboard)/hosts/page.tsx` — server component (replaced placeholder, fetches initial data) @@ -2735,7 +2735,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ ### Session 2 — User management, roles, feature flags, licence scaffold **Auth middleware / proxy** -- `proxy.ts` — Next.js 16 renamed `middleware.ts` to `proxy.ts`; already implemented. Checks `better-auth.session_token` cookie; unauthenticated requests to protected routes redirect to `/login`. Full session verification (including org check) done in server components. +- `proxy.ts` — Next.js 16 renamed `middleware.ts` to `proxy.ts`; already implemented. Checks `better-auth.session_token` cookie; unauthenticated requests to protected routes redirect to `/login`. Full session verification (including instance check) done in server components. **Auth session helper** - `lib/auth/session.ts` — `getRequiredSession()` fetches Better Auth session + full DB user row, redirects to `/login` if unauthenticated @@ -2745,21 +2745,21 @@ _(Built between Sessions 3 and 4; not previously documented)_ - `components/shared/feature-gate.tsx` — client component that gates UI behind licence tier; renders fallback/upgrade message if not entitled **Licence validation** -- `lib/licence.ts` — offline RS256 JWT validation with bundled dev public key; validates tier, org, expiry, and signature +- `lib/licence.ts` — offline RS256 JWT validation with bundled dev public key; validates tier, instance, expiry, and signature **Server actions** -- `lib/actions/users.ts` — `getOrgUsers`, `inviteUser` (7-day token), `updateUserRole`, `deactivateUser`, `cancelInvite` -- `lib/actions/settings.ts` — `updateOrgName`, `saveLicenceKey` (validates via licence.ts, persists tier) +- `lib/actions/users.ts` — `getInstanceUsers`, `inviteUser` (7-day token), `updateUserRole`, `deactivateUser`, `cancelInvite` +- `lib/actions/settings.ts` — `updateInstanceName`, `saveLicenceKey` (validates via licence.ts, persists tier) **Pages (real UI, not placeholders)** - `app/(dashboard)/team/page.tsx` + `TeamClient` — member table, role management, invite dialog, pending invites, deactivation -- `app/(dashboard)/settings/page.tsx` + `SettingsClient` — org name editor, licence key entry with tier badge and error feedback +- `app/(dashboard)/settings/page.tsx` + `SettingsClient` — instance name editor, licence key entry with tier badge and error feedback - `app/(dashboard)/profile/page.tsx` + `ProfileClient` — name editor, password change **Database schema additions** -- `lib/db/schema/invitations.ts` — email, role, token, org/user refs, 7-day expiry, soft delete -- `organisations` table extended — `licenceTier`, `licenceKey`, `slug`, `logo` -- `users` table extended — `organisationId`, `role`, `isActive`, `twoFactorEnabled` +- `lib/db/schema/invitations.ts` — email, role, token, instance/user refs, 7-day expiry, soft delete +- `instances` table extended — `licenceTier`, `licenceKey`, `slug`, `logo` +- `users` table extended — `instanceId`, `role`, `isActive`, `twoFactorEnabled` --- @@ -2778,7 +2778,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ **Database** - Drizzle ORM with `postgres` driver -- Schema: `organisations`, `users`, `sessions`, `accounts`, `verifications`, `totp_credential` +- Schema: `instances`, `users`, `sessions`, `accounts`, `verifications`, `totp_credential` - `drizzle.config.ts` pointing at `lib/db/schema/index.ts` - All tables follow CLAUDE.md conventions (id/createdAt/updatedAt/deletedAt/metadata, soft deletes) - Migration scripts: `db:generate`, `db:migrate`, `db:push`, `db:studio` @@ -2793,7 +2793,7 @@ _(Built between Sessions 3 and 4; not previously documented)_ - `/` → redirects to `/login` - `(auth)/login` — login form with Zod validation, React Hook Form - `(auth)/register` — register form, posts to Better Auth -- `(setup)/onboarding` — org creation wizard, creates organisation and links user as `super_admin` +- `(setup)/onboarding` — instance creation wizard, creates instance and links user as `super_admin` - `(dashboard)/dashboard` — overview placeholder **Components** @@ -2867,7 +2867,7 @@ Notification data hygiene is complete. Suggested next steps: - [x] Docker Compose single-node - [x] CI pipeline (GitHub Actions) — pr-checks.yml: lint, type-check, build, go test - [x] Better Auth — email/password + TOTP -- [x] Organisation + user schema +- [x] Instance + user schema - [x] Basic RBAC (roles + permissions) - [x] User management UI - [x] Feature flag system @@ -2897,13 +2897,13 @@ Notification data hygiene is complete. Suggested next steps: - [x] Check types — port, process, http (shell/file deferred) - [x] Ad-hoc agent queries (list_ports, list_services — used in check creation UI) - [x] TimescaleDB continuous aggregates (host_metrics_hourly + host_metrics_daily) -- [x] Metric retention policies (configurable per-org in settings, default 30 days) +- [x] Metric retention policies (configurable per-instance in settings, default 30 days) - [x] Metric graphs (Recharts) -- [x] Alert rule builder (check_status + metric_threshold, per-host + org-wide) +- [x] Alert rule builder (check_status + metric_threshold, per-host + instance-wide) - [x] Alert state machine (fire/resolve in ingest; acknowledge in web) - [x] Notification channels (webhook HMAC-SHA256, SMTP, Slack, Telegram, in-app) - [x] In-app notification bell, dropdown, and /notifications page -- [x] Org notification settings (roles, opt-out) + per-user opt-out +- [x] Instance notification settings (roles, opt-out) + per-user opt-out - [x] Notification bulk actions (select-all, bulk mark read/unread, bulk delete) - [x] Notification severity pie chart + trend line chart on /notifications page - [x] Notification charts on host detail Metrics tab (host-scoped) @@ -2951,7 +2951,7 @@ Notification data hygiene is complete. Suggested next steps: - [ ] White labelling ### Phase 7 — Cloud SaaS -- [ ] Multi-tenant hardening +- [ ] Instance-scoped hardening - [ ] Usage metering - [ ] Billing (Stripe) - [ ] Customer portal diff --git a/README.md b/README.md index ae3ddad0..df74ceb0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ CT-Ops is an open-source monitoring and operations platform designed to run **en - **Host groups & tagging** — flexible `key:value` tags on any resource, group-based access control for teams. - **Terminal workspace** — split-pane browser terminal for ad-hoc investigation without leaving the dashboard. - **Air-gap agent bundles** — download a self-contained zip (binary + config + install script) for hosts that can't reach the internet. -- **Instance-scoped RBAC** — `super_admin` → `org_admin` → `engineer` → `read_only` → `agent` role hierarchy. +- **Instance-scoped RBAC** — `super_admin` → `instance_admin` → `engineer` → `read_only` → `agent` role hierarchy. - **Three deployment profiles** — `single` (one host), `standard` (Redpanda), `ha` (clustered) — same codebase, different `docker-compose` files. --- diff --git a/SECURITY.md b/SECURITY.md index 6d3e36ae..4e1135f1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,19 +19,19 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. ## CRITICAL -- [ ] **[C-01] LDAP login can silently merge to an account in ANY organisation (cross-org takeover)** +- [ ] **[C-01] LDAP login can silently merge to an account in ANY instance (cross-instance takeover)** - Location: `apps/web/app/api/auth/ldap/route.ts:~67-75` - - On LDAP bind success, the handler finds an existing user by email with no `organisationId` filter, then links the LDAP account to whatever user row it finds. An attacker operating in Org A with control of an LDAP entry using a victim's email can hijack the Org B account when the LDAP user next authenticates. - - Fix direction: scope `findFirst` for `users` by both `email` and the configured org id (`eq(users.organisationId, config.organisationId)`). Never link across organisations. + - On LDAP bind success, the handler finds an existing user by email with no `instanceId` filter, then links the LDAP account to whatever user row it finds. An attacker operating in Instance A with control of an LDAP entry using a victim's email can hijack the Instance B account when the LDAP user next authenticates. + - Fix direction: scope `findFirst` for `users` by both `email` and the configured instance id (`eq(users.instanceId, config.instanceId)`). Never link across instances. - [ ] **[C-02] User management server actions trust a client-supplied `requesterId`/`invitedById` without role verification** - Location: `apps/web/lib/actions/users.ts` — `inviteUser` (~37), `updateUserRole` (~?), `deactivateUser`, `removeUser` - - The functions take the acting-user id as a parameter but never look it up to verify `role in ('org_admin', 'super_admin')`. Any authenticated user can call the action directly (e.g., via the Next.js server-action RPC endpoint) and escalate, demote, or deactivate others. + - The functions take the acting-user id as a parameter but never look it up to verify `role in ('instance_admin', 'super_admin')`. Any authenticated user can call the action directly (e.g., via the Next.js server-action RPC endpoint) and escalate, demote, or deactivate others. - Fix direction: replace parameter-supplied requester ids with `await auth.api.getSession()` and enforce role at the start of every privileged action. - [ ] **[C-03] Email verification disabled by default in Better Auth** - Location: `apps/web/lib/auth/index.ts:~19` (`requireEmailVerification: false`) - - Anyone can register under any email (including executives of a target org), enabling phishing setup, squatting, and spoofed audit trails. + - Anyone can register under any email (including executives of a target instance), enabling phishing setup, squatting, and spoofed audit trails. - Fix direction: require email verification; ship a verification-email flow before GA. - [ ] **[C-04] LDAP filter injection via unescaped `{{username}}` substitution** @@ -52,17 +52,17 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[C-07] Webhook/notification test-send is SSRF-capable (authenticated)** - Location: `apps/web/lib/actions/alerts.ts:~673-720` (`sendTestNotification` and channel creation) - Webhook/Slack/Telegram/SMTP fields accept arbitrary URLs/hostnames. `sendTestNotification` reaches them server-side with no SSRF guard, enabling internal-network probing and abuse from any authenticated account. - - Fix direction: resolve target host and reject private/reserved IPs; force HTTPS for web hooks; cap size/timeout; rate-limit per org/user. + - Fix direction: resolve target host and reject private/reserved IPs; force HTTPS for web hooks; cap size/timeout; rate-limit per instance/user. - [ ] **[C-08] Missing authentication on *most* server-action read paths** - Location: `apps/web/lib/actions/*.ts` — broad pattern. Examples include `agents.ts` (`listPendingAgents`, `listHosts`, `getHost`, `getHostMetrics`, ...), `alerts.ts` (`getAlertRules`, `getNotificationChannels`, ...), `certificates.ts` (`getCertificates`, `getCertificate`, `getCertificateCounts`, `deleteCertificate`), `checks.ts`, `domain-accounts.ts`, `host-groups.ts`, `host-settings.ts`, `notifications.ts`, `service-accounts.ts`, `software-inventory.ts`, `task-runs.ts`. - - Functions accept `orgId` as an argument and query the DB without verifying the caller is logged in, let alone a member of that org. Any unauthenticated request that can reach the Next.js action RPC endpoint can pull another org's data by guessing/enumerating org ids. - - Fix direction: add `const session = await getRequiredSession(); if (session.user.organisationId !== orgId) throw ...` to every action. Consider removing `orgId` from public parameters and always deriving it from the session. + - Functions accept `instanceId` as an argument and query the DB without verifying the caller is logged in, let alone a member of that instance. Any unauthenticated request that can reach the Next.js action RPC endpoint can pull another instance's data by guessing/enumerating instance ids. + - Fix direction: add `const session = await getRequiredSession(); if (session.user.instanceId !== instanceId) throw ...` to every action. Consider removing `instanceId` from public parameters and always deriving it from the session. -- [x] **[C-09] `deleteCertificate` (and other destructive actions) have no auth/org check** +- [x] **[C-09] `deleteCertificate` (and other destructive actions) have no auth/instance check** - Location: `apps/web/lib/actions/certificates.ts:~118-137` - - Takes `orgId` + `certId` only. An attacker can delete any certificate in any organisation. - - Fix direction: session check + confirm the cert belongs to the session's org before deleting; use soft-delete (`deletedAt`) per the universal table convention. + - Takes `instanceId` + `certId` only. An attacker can delete any certificate in any instance. + - Fix direction: session check + confirm the cert belongs to the session's instance before deleting; use soft-delete (`deletedAt`) per the universal table convention. - [ ] **[C-10] Agent self-update executes server-supplied binary with no signature verification** - Location: `agent/internal/updater/updater.go:~25-100` @@ -93,7 +93,7 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[H-03] LDAP bind password encryption uses a hardcoded, shared salt** - Location: `apps/web/lib/crypto/encrypt.ts:~5-10` (`SALT = 'ct-ops-ldap-encryption-salt'`) - - `scryptSync(secret, SALT, 32)` produces the same key for every ciphertext, every config, every customer. If `BETTER_AUTH_SECRET` leaks once, every stored LDAP password (across all orgs, installations) is decryptable. + - `scryptSync(secret, SALT, 32)` produces the same key for every ciphertext, every config, every customer. If `BETTER_AUTH_SECRET` leaks once, every stored LDAP password (across all instances, installations) is decryptable. - Fix: random per-record salt persisted alongside ciphertext; separate dedicated encryption secret (not reused from auth). - [ ] **[H-04] LDAP bind password decryption uses `BETTER_AUTH_SECRET` as KDF input** @@ -181,7 +181,7 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[H-21] `direct_access = true` terminal mode grants PTY as root with no extra checks** - Location: `agent/internal/terminal/session.go:~82-116` - Direct-access mode bypasses the per-user `su` drop. - - Fix: require org_admin + explicit per-host opt-in + audit log; prefer per-user sessions by default; require MFA for direct-access sessions. + - Fix: require instance_admin + explicit per-host opt-in + audit log; prefer per-user sessions by default; require MFA for direct-access sessions. - [ ] **[H-22] Task script execution has no sandbox / resource limits** - Location: `agent/internal/tasks/script.go:~33-111` @@ -193,9 +193,9 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - A malicious or buggy agent can push huge messages or open unlimited streams until the server OOMs. - Fix: set conservative caps (e.g. 50 MB message, 1k streams per connection); add keepalive and stream deadlines. -- [ ] **[H-24] Missing composite index on `host_metrics(organisation_id, host_id, recorded_at)`** +- [ ] **[H-24] Missing composite index on `host_metrics(instance_id, host_id, recorded_at)`** - Location: `apps/web/lib/db/schema/metrics.ts:~6-24` - - Time-series queries with tenant filters fall back to full table scans on a hypertable that will hold the bulk of all data; any user can degrade the entire cluster by requesting wide time ranges. + - Time-series queries with instance filters fall back to full table scans on a hypertable that will hold the bulk of all data; any user can degrade the entire cluster by requesting wide time ranges. - Fix: add the composite index (or TimescaleDB chunk-aware equivalent); cap the time range allowed in user-facing queries. - [ ] **[H-25] Invite/password-reset/invitation lookups miss rate limits** @@ -279,7 +279,7 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[M-10] Missing rate limiting on expensive actions** - `alerts.sendTestNotification`, `software-inventory.triggerSoftwareScan`, `software-inventory.getSoftwareReport`, `certificates.trackCertificateFromUrl`, `agents.createEnrolmentToken`. - - Fix: per-org token-bucket or leaky-bucket limiter. + - Fix: per-instance token-bucket or leaky-bucket limiter. - [ ] **[M-11] Cert-file path validation too permissive** - Location: `apps/web/lib/actions/checks.ts:~35-40` (`certFileConfigSchema.filePath`) @@ -288,7 +288,7 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[M-12] Notification channel configuration (SMTP, webhook) accepts plain HTTP** - `apps/web/lib/actions/alerts.ts` — verify URL schema validation, TLS enforcement, and port allowlist. - - Fix: require HTTPS (unless explicitly overridden per org); restrict SMTP ports and TLS modes. + - Fix: require HTTPS (unless explicitly overridden per instance); restrict SMTP ports and TLS modes. - [ ] **[M-13] Licence JWT bound only to offline validation; no revocation signal** - Location: `apps/web/lib/licence.ts` @@ -365,7 +365,7 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[M-29] `/api/agent/bundle` autoApprove tokens bypass registration approval** - Location: `apps/web/app/api/agent/bundle/route.ts:~26-31` - - Compromise of an org_admin account = silent agent enrolment at scale. + - Compromise of an instance_admin account = silent agent enrolment at scale. - Fix: require separate dual-approval for `autoApprove` tokens; audit-log + email notification on generation. - [ ] **[M-30] Inventory submission accepts arbitrarily large chunks** @@ -414,7 +414,7 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[L-08] `id` spread over `parsed.data` in create functions** - Examples: `domain-accounts.ts:~140-162, ~185-195`. - - If future schemas add security-sensitive fields (role, orgId) they become mass-assignable. + - If future schemas add security-sensitive fields (role, instanceId) they become mass-assignable. - Fix: destructure explicit fields instead of spreading. - [ ] **[L-09] Cert-refresh sweeper uses `InsecureSkipVerify: true`** @@ -444,15 +444,15 @@ Severity key: **C**ritical / **H**igh / **M**edium / **L**ow / **I**nfo. - [ ] **[I-05] No SBOM produced per release** - [ ] **[I-06] No pen-test scope / engagement doc in repo — add one alongside SECURITY.md** - [ ] **[I-07] Mixed query styles (`db.query.X.findMany` vs. `db.select().from(X)`) complicate audits** -- [ ] **[I-08] Consider Row-Level Security (RLS) in Postgres scoped on `organisation_id`** — defence in depth should any server-side check miss its org filter +- [ ] **[I-08] Consider Row-Level Security (RLS) in Postgres scoped on `instance_id`** — defence in depth should any server-side check miss its instance filter - [ ] **[I-09] Response sanitisation layer** — generic utility for stripping secret-shaped fields before returning -- [x] **[I-10] Centralised authz helpers** — shared guards now enforce role/org checks and ESLint flags raw `session.user` authz comparisons in web actions +- [x] **[I-10] Centralised authz helpers** — shared guards now enforce role/instance checks and ESLint flags raw `session.user` authz comparisons in web actions --- ## Suggested triage ordering -1. Fix `C-01`, `C-02`, `C-04`, `C-05`, `C-06`, `C-08`, `C-09` before the pen test — these are "anyone can take over / read any org". +1. Fix `C-01`, `C-02`, `C-04`, `C-05`, `C-06`, `C-08`, `C-09` before the pen test — these are "anyone can take over / read any instance". 2. Rotate away from the dev licence public key (`C-11`) before any external distribution. 3. Wire rate limiting + CSRF + security headers (`H-01`, `H-02`, `H-27`) — cheap wins that reduce the overall attack surface. 4. Implement signed agent updates and sandboxed task execution (`C-10`, `H-22`). diff --git a/TASK.md b/TASK.md index 8a717651..96033600 100644 --- a/TASK.md +++ b/TASK.md @@ -6,11 +6,11 @@ Complete Phase 0: auth middleware, user management UI, RBAC enforcement, feature ## Scope **In scope:** -- `middleware.ts` — protect dashboard routes, redirect unauthenticated to `/login`, redirect users without org to `/onboarding` -- User management UI — list users in org, invite by email, change role, deactivate +- `middleware.ts` — protect dashboard routes, redirect unauthenticated to `/login`, redirect users without instance to `/onboarding` +- User management UI — list users in instance, invite by email, change role, deactivate - Feature flag system — server-side `hasFeature(session, feature)` based on licence tier - Licence key validation scaffold — offline-capable signed JWT with bundled public key -- System settings page — org name, licence key entry +- System settings page — instance name, licence key entry - Profile page — change name, change password, setup/remove TOTP - Run Drizzle migrations (`drizzle-kit push` against a live DB or generate migration files) - `npm run build` must pass with zero errors and zero lint warnings @@ -23,7 +23,7 @@ Complete Phase 0: auth middleware, user management UI, RBAC enforcement, feature ## Definition of Done - [ ] Unauthenticated users cannot access any `/dashboard/*` route -- [ ] Users without an `organisation_id` are redirected to `/onboarding` after login +- [ ] Users without an `instance_id` are redirected to `/onboarding` after login - [ ] Admin can view list of team members - [ ] Admin can invite a user by email (creates a pending invite record) - [ ] Admin can change a user's role @@ -45,7 +45,7 @@ apps/web/ │ ├── licence.ts # NEW — licence key validation │ └── actions/ │ ├── users.ts # NEW — invite, update role, deactivate -│ └── settings.ts # NEW — update org, save licence key +│ └── settings.ts # NEW — update instance, save licence key ├── app/(dashboard)/ │ ├── team/ │ │ └── page.tsx # MODIFY — real team management UI diff --git a/agent/CHANGELOG.md b/agent/CHANGELOG.md index 15c3c9c6..6f8eacf1 100644 --- a/agent/CHANGELOG.md +++ b/agent/CHANGELOG.md @@ -47,7 +47,7 @@ ### Bug Fixes -* **ingest:** remove org-scoped agent identity ([#1248](https://github.com/carrtech-dev/ct-ops/issues/1248)) ([3be0471](https://github.com/carrtech-dev/ct-ops/commit/3be047199c6e96c3fe1bd6f5320f8911991dbc35)) +* **ingest:** remove instance-scoped agent identity ([#1248](https://github.com/carrtech-dev/ct-ops/issues/1248)) ([3be0471](https://github.com/carrtech-dev/ct-ops/commit/3be047199c6e96c3fe1bd6f5320f8911991dbc35)) ## [0.34.3](https://github.com/carrtech-dev/ct-ops/compare/agent/v0.34.2...agent/v0.34.3) (2026-05-09) diff --git a/agent/cmd/agent/main.go b/agent/cmd/agent/main.go index 35ac6429..4f02b47f 100644 --- a/agent/cmd/agent/main.go +++ b/agent/cmd/agent/main.go @@ -82,10 +82,7 @@ func mergeTags(layers ...[]string) []*agentv1.Tag { } func enrolmentTokenFromEnv() string { - if token := strings.TrimSpace(os.Getenv("CT_OPS_ENROLMENT_TOKEN")); token != "" { - return token - } - return strings.TrimSpace(os.Getenv("CT_OPS_ORG_TOKEN")) + return strings.TrimSpace(os.Getenv("CT_OPS_ENROLMENT_TOKEN")) } func main() { diff --git a/agent/cmd/agent/main_test.go b/agent/cmd/agent/main_test.go index a0d8922a..95396a11 100644 --- a/agent/cmd/agent/main_test.go +++ b/agent/cmd/agent/main_test.go @@ -4,17 +4,8 @@ import "testing" func TestEnrolmentTokenFromEnvUsesCanonicalEnv(t *testing.T) { t.Setenv("CT_OPS_ENROLMENT_TOKEN", " canonical ") - t.Setenv("CT_OPS_ORG_TOKEN", "legacy") if got := enrolmentTokenFromEnv(); got != "canonical" { t.Fatalf("unexpected token: %q", got) } } - -func TestEnrolmentTokenFromEnvFallsBackToLegacyOrgEnv(t *testing.T) { - t.Setenv("CT_OPS_ORG_TOKEN", " legacy ") - - if got := enrolmentTokenFromEnv(); got != "legacy" { - t.Fatalf("unexpected token: %q", got) - } -} diff --git a/agent/internal/config/config.go b/agent/internal/config/config.go index 9cbde2c2..14cdc135 100644 --- a/agent/internal/config/config.go +++ b/agent/internal/config/config.go @@ -90,8 +90,6 @@ func applyEnv(cfg *Config) { } if v := os.Getenv("CT_OPS_ENROLMENT_TOKEN"); v != "" { cfg.Agent.EnrolmentToken = v - } else if v := os.Getenv("CT_OPS_ORG_TOKEN"); v != "" { - cfg.Agent.EnrolmentToken = v } if v := os.Getenv("CT_OPS_DATA_DIR"); v != "" { cfg.Agent.DataDir = v diff --git a/agent/internal/config/config_test.go b/agent/internal/config/config_test.go index 7b416888..20815e11 100644 --- a/agent/internal/config/config_test.go +++ b/agent/internal/config/config_test.go @@ -46,8 +46,8 @@ func TestLoadReadsSecureConfig(t *testing.T) { } } -func TestLoadReadsLegacyOrgTokenEnvFallback(t *testing.T) { - t.Setenv("CT_OPS_ORG_TOKEN", " legacy ") +func TestLoadTrimsCanonicalEnrolmentTokenEnv(t *testing.T) { + t.Setenv("CT_OPS_ENROLMENT_TOKEN", " canonical ") dir := t.TempDir() path := filepath.Join(dir, "agent.toml") @@ -59,7 +59,7 @@ func TestLoadReadsLegacyOrgTokenEnvFallback(t *testing.T) { if err != nil { t.Fatalf("load config: %v", err) } - if cfg.Agent.EnrolmentToken != "legacy" { + if cfg.Agent.EnrolmentToken != "canonical" { t.Fatalf("unexpected enrolment token: %q", cfg.Agent.EnrolmentToken) } } diff --git a/apps/docs/docs/architecture/agent.md b/apps/docs/docs/architecture/agent.md index fb0f7a95..74d8c325 100644 --- a/apps/docs/docs/architecture/agent.md +++ b/apps/docs/docs/architecture/agent.md @@ -16,7 +16,7 @@ The CT-Ops agent is a statically compiled Go binary. It has no runtime dependenc ### First run (no `agent_state.json`) 1. Agent generates an Ed25519 keypair on the host and persists it to `data_dir` -2. Sends a `Register` RPC with the org enrolment token and public key +2. Sends a `Register` RPC with the instance enrolment token and public key 3. Ingest validates the token, inserts a host record, and returns `agent_id` + status 4. If **auto-approve** is enabled on the token: status is `active` immediately 5. If not: status is `pending` — the agent polls every 30 seconds until an admin approves it in the UI @@ -187,11 +187,11 @@ The agent collects the full list of installed software packages and streams them | **macOS** | `system_profiler SPApplicationsDataType` + Homebrew (`brew list`) | | **Windows** | Registry walk under `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` | -Optional sources (Snap, Flatpak, Windows Store) can be enabled per-org in **Settings → Software Inventory**. +Optional sources (Snap, Flatpak, Windows Store) can be enabled per-instance in **Settings → Software Inventory**. ### Scheduling -The ingest service's `SoftwareSweeper` runs every 60 seconds and dispatches a `software_inventory` task to any host whose last scan is older than the org-level `intervalHours` setting. Admins can also trigger an immediate rescan from the host detail Inventory tab. +The ingest service's `SoftwareSweeper` runs every 60 seconds and dispatches a `software_inventory` task to any host whose last scan is older than the instance-level `intervalHours` setting. Admins can also trigger an immediate rescan from the host detail Inventory tab. ### Package tracking diff --git a/apps/docs/docs/deployment/air-gap.md b/apps/docs/docs/deployment/air-gap.md index 9aec5aba..2412d5e4 100644 --- a/apps/docs/docs/deployment/air-gap.md +++ b/apps/docs/docs/deployment/air-gap.md @@ -101,7 +101,7 @@ Agents poll the ingest service for the minimum required version. When a newer bi Hosts that cannot reach the CT-Ops server at install time (jump-host-only networks, change-controlled servers, or hosts behind a one-way data diode) can be enrolled using a **portable install bundle** generated by the CT-Ops UI. -From **Administration → Agents → Enrolment**, an `org_admin` or `super_admin` clicks **Download Install Bundle**, picks the target OS / arch, and downloads a zip containing: +From **Administration → Agents → Enrolment**, an `instance_admin` or `super_admin` clicks **Download Install Bundle**, picks the target OS / arch, and downloads a zip containing: - The agent binary for that platform - `install.sh` (Linux / macOS) or `install.ps1` (Windows) diff --git a/apps/docs/docs/development/testing.md b/apps/docs/docs/development/testing.md index 4882585a..ac91f41f 100644 --- a/apps/docs/docs/development/testing.md +++ b/apps/docs/docs/development/testing.md @@ -75,7 +75,7 @@ Use `getTestDb()` from `tests/e2e/fixtures/db.ts` when a test needs to create or ```ts import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' test('shows seeded hosts', async ({ authenticatedPage: page }) => { const sql = getTestDb() @@ -84,7 +84,7 @@ test('shows seeded hosts', async ({ authenticatedPage: page }) => { INSERT INTO hosts (id, instance_id, hostname, status) VALUES ( 'test-host-1', - (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}), + (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}), 'web-01', 'online' ) @@ -97,7 +97,7 @@ test('shows seeded hosts', async ({ authenticatedPage: page }) => { Use direct SQL seeding when the setup is only test data and does not need to verify a UI or HTTP workflow. Use the product UI or an API request when the creation path itself is part of the behavior under test, or when production code must perform side effects such as password hashing, session creation, token generation, or validation. -The baseline seed is `seedOrgAndUser()` in `tests/e2e/fixtures/seed.ts`. It is intentionally idempotent: it signs up `e2e@example.com` through Better Auth so the password and account rows are correct, creates the baseline `instance_settings` row if needed, promotes the user to admin, and deletes the sign-up session so login tests start cleanly. +The baseline seed is `seedInstanceAndUser()` in `tests/e2e/fixtures/seed.ts`. It is intentionally idempotent: it signs up `e2e@example.com` through Better Auth so the password and account rows are correct, creates the baseline `instance_settings` row if needed, promotes the user to admin, and deletes the sign-up session so login tests start cleanly. When adding feature-specific seed helpers: @@ -128,7 +128,7 @@ For authenticated, database-backed tests, use this shape: ```ts import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' test('feature behavior', async ({ authenticatedPage: page }) => { const sql = getTestDb() @@ -137,7 +137,7 @@ test('feature behavior', async ({ authenticatedPage: page }) => { INSERT INTO example_table (id, instance_id, name) VALUES ( 'example-1', - (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}), + (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}), 'Example' ) ` diff --git a/apps/docs/docs/features/directory-lookup.md b/apps/docs/docs/features/directory-lookup.md index 558f22f8..c6d52661 100644 --- a/apps/docs/docs/features/directory-lookup.md +++ b/apps/docs/docs/features/directory-lookup.md @@ -49,4 +49,4 @@ This is intentional for environments where the directory contains tens of thousa ## Permissions -Any authenticated user in the CT-Ops instance can run directory lookups. Managing LDAP configurations (adding, editing, deleting) is restricted to `org_admin` and `super_admin`. +Any authenticated user in the CT-Ops instance can run directory lookups. Managing LDAP configurations (adding, editing, deleting) is restricted to `instance_admin` and `super_admin`. diff --git a/apps/docs/docs/features/networks.md b/apps/docs/docs/features/networks.md index 60d1b00b..291ab810 100644 --- a/apps/docs/docs/features/networks.md +++ b/apps/docs/docs/features/networks.md @@ -80,6 +80,6 @@ The context menu uses React Flow's `onNodeContextMenu` so the browser's native r | Action | Required Role | |---|---| -| Create / edit / delete a network | `org_admin`, `super_admin` | -| Add / remove a host from a network | `engineer`, `org_admin`, `super_admin` | +| Create / edit / delete a network | `instance_admin`, `super_admin` | +| Add / remove a host from a network | `engineer`, `instance_admin`, `super_admin` | | View networks | All authenticated users | diff --git a/apps/docs/docs/features/notes.md b/apps/docs/docs/features/notes.md index 5a04c5eb..87f49b92 100644 --- a/apps/docs/docs/features/notes.md +++ b/apps/docs/docs/features/notes.md @@ -50,7 +50,7 @@ Readers can mark a note **helpful** or **outdated**. A user can only cast each r Three entry points: 1. **Per-host Notes tab** — everything that resolves to this host. Supports category filters and a "New note" action that attaches the note to the current host. Category templates seed the body when you choose a category on a new note, so runbooks and contacts land with a consistent shape. Changes stream in via the host SSE channel (`notes` event on `/api/hosts/[id]/stream`) so another engineer's edit shows up without a refresh. -2. **Global `/notes` page** — org-wide list with full-text search (`websearch_to_tsquery` over title + body, title weighted higher) and category / author / "mine only" filters +2. **Global `/notes` page** — instance-wide list with full-text search (`websearch_to_tsquery` over title + body, title weighted higher) and category / author / "mine only" filters 3. **Cmd+K palette** — type `/notes` or a note title to jump straight to it ## Overview card @@ -61,11 +61,11 @@ Pinned notes surface as a "Pinned notes" card on the host's Overview tab — abo | Action | Who | |---|---| -| Read shared note | Anyone in the org | +| Read shared note | Anyone in the instance | | Read private note | Author + super admin | | Create note | Any role except `read_only` | -| Edit note | Author + org admin + super admin | -| Delete note (soft) | Author + org admin + super admin | +| Edit note | Author + instance admin + super admin | +| Delete note (soft) | Author + instance admin + super admin | | Toggle private | Author only | Deletion is soft — the note row is marked `deletedAt` and disappears from every list, but the revision history remains. diff --git a/apps/docs/docs/features/operations-calendar.md b/apps/docs/docs/features/operations-calendar.md index 04529292..11024d9e 100644 --- a/apps/docs/docs/features/operations-calendar.md +++ b/apps/docs/docs/features/operations-calendar.md @@ -41,7 +41,7 @@ Deleting or editing the whole series affects the parent recurring event. | Role | View | Create / Edit / Delete | |---|---:|---:| | `super_admin` | ✅ | ✅ | -| `org_admin` | ✅ | ✅ | +| `instance_admin` | ✅ | ✅ | | `engineer` | ✅ | ✅ | | `read_only` | ✅ | ❌ | diff --git a/apps/docs/docs/features/scheduled-tasks.md b/apps/docs/docs/features/scheduled-tasks.md index 7fd40812..a94d34a1 100644 --- a/apps/docs/docs/features/scheduled-tasks.md +++ b/apps/docs/docs/features/scheduled-tasks.md @@ -71,7 +71,7 @@ From the Scheduled Tasks list, click the **Play** icon next to a schedule to tri | Role | Create / Edit / Delete | Toggle enabled | Run now | |---|---|---|---| | `super_admin` | ✅ | ✅ | ✅ | -| `org_admin` | ✅ | ✅ | ✅ | +| `instance_admin` | ✅ | ✅ | ✅ | | `engineer` | ✅ | ✅ | ✅ | | `read_only` | ❌ | ❌ | ❌ | diff --git a/apps/docs/docs/features/tags.md b/apps/docs/docs/features/tags.md index 1241d406..a7da26f5 100644 --- a/apps/docs/docs/features/tags.md +++ b/apps/docs/docs/features/tags.md @@ -9,7 +9,7 @@ GCP semantics). There are four places tags can originate, merged in weakest-to-strongest order: -1. **Organisation defaults** — Administration → Agents → Host defaults → Default Tags. Applied +1. **Instance defaults** — Administration → Agents → Host defaults → Default Tags. Applied to every newly approved host. 2. **Enrolment token** — chosen when creating an install token. Baked into the install bundle so every machine installed with that bundle registers with @@ -20,7 +20,7 @@ There are four places tags can originate, merged in weakest-to-strongest order: on demand. Run last and only fill in keys not already set, so they never override explicit operator intent on the same key. -Merge order: `org defaults → token tags → CLI tags → rules` (last-wins). +Merge order: `instance defaults → token tags → CLI tags → rules` (last-wins). ## Autocomplete and dedupe diff --git a/apps/docs/docs/getting-started/agent-install-bundle.md b/apps/docs/docs/getting-started/agent-install-bundle.md index c2e15cb9..99f039be 100644 --- a/apps/docs/docs/getting-started/agent-install-bundle.md +++ b/apps/docs/docs/getting-started/agent-install-bundle.md @@ -16,7 +16,7 @@ Each zip contains everything needed to install the agent on one OS / architectur ## Who can generate a bundle -Bundle generation is gated to **`super_admin`** and **`org_admin`** roles. Every bundle is scoped to the CT-Ops instance of the requesting user. +Bundle generation is gated to **`super_admin`** and **`instance_admin`** roles. Every bundle is scoped to the CT-Ops instance of the requesting user. ## Generate and download diff --git a/apps/ingest/CHANGELOG.md b/apps/ingest/CHANGELOG.md index c79a4555..daea51bf 100644 --- a/apps/ingest/CHANGELOG.md +++ b/apps/ingest/CHANGELOG.md @@ -61,7 +61,7 @@ ### Bug Fixes -* **ingest:** remove org-scoped agent identity ([#1248](https://github.com/carrtech-dev/ct-ops/issues/1248)) ([3be0471](https://github.com/carrtech-dev/ct-ops/commit/3be047199c6e96c3fe1bd6f5320f8911991dbc35)) +* **ingest:** remove instance-scoped agent identity ([#1248](https://github.com/carrtech-dev/ct-ops/issues/1248)) ([3be0471](https://github.com/carrtech-dev/ct-ops/commit/3be047199c6e96c3fe1bd6f5320f8911991dbc35)) ## [0.9.6](https://github.com/carrtech-dev/ct-ops/compare/ingest/v0.9.5...ingest/v0.9.6) (2026-05-09) @@ -131,7 +131,7 @@ ### Bug Fixes -* **security:** scope host checks to tenant ([#1059](https://github.com/carrtech-dev/ct-ops/issues/1059)) ([c137203](https://github.com/carrtech-dev/ct-ops/commit/c137203ebec8acfc36156f50cf3587896ac3b845)) +* **security:** scope host checks to instance ([#1059](https://github.com/carrtech-dev/ct-ops/issues/1059)) ([c137203](https://github.com/carrtech-dev/ct-ops/commit/c137203ebec8acfc36156f50cf3587896ac3b845)) ## [0.8.6](https://github.com/carrtech-dev/ct-ops/compare/ingest/v0.8.5...ingest/v0.8.6) (2026-05-05) diff --git a/apps/ingest/internal/db/queries/alerts.sql.go b/apps/ingest/internal/db/queries/alerts.sql.go index 77c1f607..26101f3b 100644 --- a/apps/ingest/internal/db/queries/alerts.sql.go +++ b/apps/ingest/internal/db/queries/alerts.sql.go @@ -12,7 +12,7 @@ import ( // AlertRuleRow is a row from the alert_rules table. type AlertRuleRow struct { ID string - HostID *string // nil for org-wide rules + HostID *string // nil for instance-wide rules InstanceID string Name string ConditionType string // "check_status" | "metric_threshold" @@ -42,7 +42,7 @@ type WebhookChannelRow struct { } // GetAlertRulesForHost returns all enabled, non-deleted alert rules scoped to the -// given host OR org-wide (host_id IS NULL, isGlobalDefault = false). +// given host OR instance-wide (host_id IS NULL, isGlobalDefault = false). // Global defaults (is_global_default = true) are excluded — they are cloned as // host-specific rules at approval time and must not be double-evaluated. func GetAlertRulesForHost(ctx context.Context, pool *pgxpool.Pool, instanceID, hostID string) ([]AlertRuleRow, error) { @@ -154,7 +154,7 @@ func GetRecentCheckResults(ctx context.Context, pool *pgxpool.Pool, checkID stri type SmtpChannelRow struct { ID string ConfigJSON string // raw notification_channels.config JSONB as text - RelayConfigJSON string // raw instances.metadata.notificationSettings.smtpRelay JSONB as text; may be empty + RelayConfigJSON string // raw instance_settings.metadata.notificationSettings.smtpRelay JSONB as text; may be empty } // GetEnabledSmtpChannels returns all enabled, non-deleted SMTP notification @@ -165,7 +165,7 @@ func GetEnabledSmtpChannels(ctx context.Context, pool *pgxpool.Pool, instanceID c.config::text, COALESCE(o.metadata->'notificationSettings'->'smtpRelay', '{}'::jsonb)::text FROM notification_channels c - JOIN instances o ON o.id = c.instance_id + JOIN instance_settings o ON o.id = c.instance_id WHERE c.instance_id = $1 AND c.type = 'smtp' AND c.enabled = true @@ -301,41 +301,41 @@ func GetEnabledTelegramChannels(ctx context.Context, pool *pgxpool.Pool, instanc return result, rows.Err() } -// OrgNotificationSettingsRow holds the notification settings from org metadata. -type OrgNotificationSettingsRow struct { +// InstanceNotificationSettingsRow holds the notification settings from instance metadata. +type InstanceNotificationSettingsRow struct { InAppEnabled bool InAppRoles []string AllowUserOptOut bool } -// GetOrgNotificationSettings fetches the in-app notification settings for an org. +// GetInstanceNotificationSettings fetches the in-app notification settings for an instance. // Returns sensible defaults if metadata is not set. -func GetOrgNotificationSettings(ctx context.Context, pool *pgxpool.Pool, instanceID string) (OrgNotificationSettingsRow, error) { +func GetInstanceNotificationSettings(ctx context.Context, pool *pgxpool.Pool, instanceID string) (InstanceNotificationSettingsRow, error) { const q = ` SELECT COALESCE((metadata->'notificationSettings'->>'inAppEnabled')::boolean, true), COALESCE( ARRAY(SELECT jsonb_array_elements_text(metadata->'notificationSettings'->'inAppRoles')), - ARRAY['super_admin','org_admin','engineer'] + ARRAY['super_admin','instance_admin','engineer'] ), COALESCE((metadata->'notificationSettings'->>'allowUserOptOut')::boolean, true) - FROM instances + FROM instance_settings WHERE id = $1 ` - var row OrgNotificationSettingsRow + var row InstanceNotificationSettingsRow err := pool.QueryRow(ctx, q, instanceID).Scan(&row.InAppEnabled, &row.InAppRoles, &row.AllowUserOptOut) if err != nil { // Return defaults on error - return OrgNotificationSettingsRow{ + return InstanceNotificationSettingsRow{ InAppEnabled: true, - InAppRoles: []string{"super_admin", "org_admin", "engineer"}, + InAppRoles: []string{"super_admin", "instance_admin", "engineer"}, AllowUserOptOut: true, }, err } // ARRAY(SELECT ...) returns '{}' (not NULL) when the JSON key is absent, so // COALESCE in SQL never fires. Apply the default here instead. if len(row.InAppRoles) == 0 { - row.InAppRoles = []string{"super_admin", "org_admin", "engineer"} + row.InAppRoles = []string{"super_admin", "instance_admin", "engineer"} } return row, nil } diff --git a/apps/ingest/internal/db/queries/certificates.sql.go b/apps/ingest/internal/db/queries/certificates.sql.go index 9437e0f8..f40a6df2 100644 --- a/apps/ingest/internal/db/queries/certificates.sql.go +++ b/apps/ingest/internal/db/queries/certificates.sql.go @@ -232,8 +232,8 @@ func InsertCertAlertInstance( return returnedID, err } -// GetCertExpiryRulesForOrg returns all enabled cert_expiry alert rules for an org. -func GetCertExpiryRulesForOrg(ctx context.Context, pool *pgxpool.Pool, instanceID string) ([]AlertRuleRow, error) { +// GetCertExpiryRulesForInstance returns all enabled cert_expiry alert rules for an instance. +func GetCertExpiryRulesForInstance(ctx context.Context, pool *pgxpool.Pool, instanceID string) ([]AlertRuleRow, error) { const q = ` SELECT id, host_id, instance_id, name, condition_type, config::text, severity FROM alert_rules @@ -259,9 +259,9 @@ func GetCertExpiryRulesForOrg(ctx context.Context, pool *pgxpool.Pool, instanceI return result, rows.Err() } -// GetAllOrgsWithCertExpiryRules returns distinct org IDs that have at least one +// GetAllInstancesWithCertExpiryRules returns distinct instance IDs that have at least one // enabled cert_expiry rule — used by the sweeper. -func GetAllOrgsWithCertExpiryRules(ctx context.Context, pool *pgxpool.Pool) ([]string, error) { +func GetAllInstancesWithCertExpiryRules(ctx context.Context, pool *pgxpool.Pool) ([]string, error) { const q = ` SELECT DISTINCT instance_id FROM alert_rules @@ -490,7 +490,7 @@ func ListCertificatesExpiringWithin( return result, rows.Err() } -// GetCertificateByID returns a single certificate row by ID (org-scoped). +// GetCertificateByID returns a single certificate row by ID (instance-scoped). func GetCertificateByID( ctx context.Context, pool *pgxpool.Pool, diff --git a/apps/ingest/internal/db/queries/checks_test.go b/apps/ingest/internal/db/queries/checks_test.go index a4c837ac..51e352cd 100644 --- a/apps/ingest/internal/db/queries/checks_test.go +++ b/apps/ingest/internal/db/queries/checks_test.go @@ -42,7 +42,7 @@ func TestGetChecksForHostScopesByHostAndInstance(t *testing.T) { queryErr := errors.New("stop after query capture") queryer := &fakeCheckQueryer{err: queryErr} - _, err := getChecksForHost(context.Background(), queryer, "host-victim", "org-victim") + _, err := getChecksForHost(context.Background(), queryer, "host-victim", "instance-victim") if !errors.Is(err, queryErr) { t.Fatalf("getChecksForHost error = %v, want sentinel query error", err) } @@ -52,7 +52,7 @@ func TestGetChecksForHostScopesByHostAndInstance(t *testing.T) { if !strings.Contains(queryer.sql, "AND instance_id = $2") { t.Fatalf("query does not filter by instance_id: %s", queryer.sql) } - if len(queryer.args) != 2 || queryer.args[0] != "host-victim" || queryer.args[1] != "org-victim" { + if len(queryer.args) != 2 || queryer.args[0] != "host-victim" || queryer.args[1] != "instance-victim" { t.Fatalf("args = %#v, want host and instance", queryer.args) } } @@ -67,7 +67,7 @@ func TestInsertCheckResultRejectsCrossInstanceCheckID(t *testing.T) { execer, "check-attacker", "host-victim", - "org-victim", + "instance-victim", "pass", "ok", 123, diff --git a/apps/ingest/internal/db/queries/networks.sql.go b/apps/ingest/internal/db/queries/networks.sql.go index e3c4c370..7b327090 100644 --- a/apps/ingest/internal/db/queries/networks.sql.go +++ b/apps/ingest/internal/db/queries/networks.sql.go @@ -21,8 +21,8 @@ type HostNetworkMembershipRow struct { AutoAssigned bool } -// GetNetworksForOrg returns all active (non-deleted) networks for an instance. -func GetNetworksForOrg(ctx context.Context, pool *pgxpool.Pool, instanceID string) ([]NetworkRow, error) { +// GetNetworksForInstance returns all active (non-deleted) networks for an instance. +func GetNetworksForInstance(ctx context.Context, pool *pgxpool.Pool, instanceID string) ([]NetworkRow, error) { const q = ` SELECT id, cidr FROM networks @@ -125,8 +125,8 @@ func SyncHostNetworks(ctx context.Context, pool *pgxpool.Pool, instanceID, hostI } } - // Fetch all active networks for the org. - networks, err := GetNetworksForOrg(ctx, pool, instanceID) + // Fetch all active networks for the instance. + networks, err := GetNetworksForInstance(ctx, pool, instanceID) if err != nil { return err } diff --git a/apps/ingest/internal/db/queries/software.sql.go b/apps/ingest/internal/db/queries/software.sql.go index 980b700e..6b509a5d 100644 --- a/apps/ingest/internal/db/queries/software.sql.go +++ b/apps/ingest/internal/db/queries/software.sql.go @@ -8,16 +8,16 @@ import ( "github.com/jackc/pgx/v5/pgxpool" ) -// HostOrgForTaskRunHost returns the host_id and instance_id for a given +// HostInstanceForTaskRunHost returns the host_id and instance_id for a given // task_run_hosts row, also checking that the row belongs to the given agent. -type HostOrgForTask struct { +type HostInstanceForTask struct { HostID string InstanceID string } -// GetHostOrgForTaskRunHost looks up the host and org for a task_run_hosts row, +// GetHostInstanceForTaskRunHost looks up the host and instance for a task_run_hosts row, // validating that the host's agent matches the caller. -func GetHostOrgForTaskRunHost(ctx context.Context, pool *pgxpool.Pool, taskRunHostID, agentID string) (*HostOrgForTask, error) { +func GetHostInstanceForTaskRunHost(ctx context.Context, pool *pgxpool.Pool, taskRunHostID, agentID string) (*HostInstanceForTask, error) { const q = ` SELECT h.id, h.instance_id FROM task_run_hosts trh @@ -28,7 +28,7 @@ func GetHostOrgForTaskRunHost(ctx context.Context, pool *pgxpool.Pool, taskRunHo AND h.deleted_at IS NULL LIMIT 1 ` - var r HostOrgForTask + var r HostInstanceForTask err := pool.QueryRow(ctx, q, taskRunHostID, agentID).Scan(&r.HostID, &r.InstanceID) return &r, err } @@ -248,14 +248,14 @@ type SoftwareSweeperHost struct { InstanceID string } -// GetHostsDueForSoftwareScan returns hosts belonging to instances that have +// GetHostsDueForSoftwareScan returns hosts belonging to instance_settings that have // software inventory enabled and whose last scan is older than the configured // interval (or who have never been scanned). func GetHostsDueForSoftwareScan(ctx context.Context, pool *pgxpool.Pool) ([]SoftwareSweeperHost, error) { const q = ` SELECT h.id, h.instance_id FROM hosts h - JOIN instances o ON o.id = h.instance_id + JOIN instance_settings o ON o.id = h.instance_id WHERE h.deleted_at IS NULL AND o.deleted_at IS NULL AND h.status = 'online' diff --git a/apps/ingest/internal/db/queries/tags.sql.go b/apps/ingest/internal/db/queries/tags.sql.go index 0c19bf48..d9b05af4 100644 --- a/apps/ingest/internal/db/queries/tags.sql.go +++ b/apps/ingest/internal/db/queries/tags.sql.go @@ -42,10 +42,10 @@ func lower(s string) string { return string(b) } -// GetOrgDefaultTags reads instances.metadata.defaultTags. Returns nil when +// GetInstanceDefaultTags reads instance_settings.metadata.defaultTags. Returns nil when // no defaults are set. -func GetOrgDefaultTags(ctx context.Context, pool *pgxpool.Pool, instanceID string) ([]TagPair, error) { - const q = `SELECT COALESCE(metadata, '{}'::jsonb) FROM instances WHERE id = $1` +func GetInstanceDefaultTags(ctx context.Context, pool *pgxpool.Pool, instanceID string) ([]TagPair, error) { + const q = `SELECT COALESCE(metadata, '{}'::jsonb) FROM instance_settings WHERE id = $1` var raw []byte if err := pool.QueryRow(ctx, q, instanceID).Scan(&raw); err != nil { if errors.Is(err, pgx.ErrNoRows) { diff --git a/apps/ingest/internal/db/queries/terminal_sessions.sql.go b/apps/ingest/internal/db/queries/terminal_sessions.sql.go index 0acb6791..1451feb5 100644 --- a/apps/ingest/internal/db/queries/terminal_sessions.sql.go +++ b/apps/ingest/internal/db/queries/terminal_sessions.sql.go @@ -41,7 +41,7 @@ func ValidateAndActivateTerminalSession(ctx context.Context, pool *pgxpool.Pool, SET status = 'active', started_at = NOW(), updated_at = NOW() - FROM instances o, hosts h + FROM instance_settings o, hosts h WHERE ts.session_id = $1 AND ts.websocket_token_hash = $2 AND ts.status = 'pending' diff --git a/apps/ingest/internal/db/queries/terminal_sessions_test.go b/apps/ingest/internal/db/queries/terminal_sessions_test.go index 53591744..2bd593c9 100644 --- a/apps/ingest/internal/db/queries/terminal_sessions_test.go +++ b/apps/ingest/internal/db/queries/terminal_sessions_test.go @@ -86,7 +86,7 @@ func TestVerifySSHHostKeyMetadataBlocksWhenPendingChangeExists(t *testing.T) { func TestTerminalAuthThrottleKeysIncludeUserHostUsernameAndSource(t *testing.T) { info := TerminalSessionInfo{ - InstanceID: "org-1", + InstanceID: "instance-1", UserID: "user-1", HostID: "host-1", Username: "Admin", @@ -116,7 +116,7 @@ func TestTerminalAuthThrottleKeysIncludeUserHostUsernameAndSource(t *testing.T) func TestTerminalAuthThrottleKeyNormalisesUsernameAndSource(t *testing.T) { base := TerminalSessionInfo{ - InstanceID: "org-1", + InstanceID: "instance-1", UserID: "user-1", HostID: "host-1", Username: "Admin", diff --git a/apps/ingest/internal/handlers/alerts.go b/apps/ingest/internal/handlers/alerts.go index 74a6cc60..3f712c81 100644 --- a/apps/ingest/internal/handlers/alerts.go +++ b/apps/ingest/internal/handlers/alerts.go @@ -629,7 +629,7 @@ type certExpiryConfig struct { } // evaluateCertExpiryForCert is called immediately after a cert is upserted. -// It loads all cert_expiry rules for the org and evaluates them against the +// It loads all cert_expiry rules for the instance and evaluates them against the // freshly-observed cert. func evaluateCertExpiryForCert( ctx context.Context, @@ -637,7 +637,7 @@ func evaluateCertExpiryForCert( instanceID, certID, commonName, issuer, host string, port int, notAfter time.Time, status string, ) { - rules, err := queries.GetCertExpiryRulesForOrg(ctx, pool, instanceID) + rules, err := queries.GetCertExpiryRulesForInstance(ctx, pool, instanceID) if err != nil { slog.Warn("evaluateCertExpiry: fetching rules", "instance_id", instanceID, "err", err) return @@ -763,7 +763,7 @@ func evaluateCertExpiryRule( } } -// RunCertExpirySweeper periodically evaluates all cert_expiry rules for all orgs. +// RunCertExpirySweeper periodically evaluates all cert_expiry rules for all instances. // This catches certs that drift into the warning window without a new scan. func RunCertExpirySweeper(ctx context.Context, pool *pgxpool.Pool, interval time.Duration) { if interval <= 0 { @@ -785,14 +785,14 @@ func RunCertExpirySweeper(ctx context.Context, pool *pgxpool.Pool, interval time } func runCertExpirySweep(ctx context.Context, pool *pgxpool.Pool) { - instanceIDs, err := queries.GetAllOrgsWithCertExpiryRules(ctx, pool) + instanceIDs, err := queries.GetAllInstancesWithCertExpiryRules(ctx, pool) if err != nil { - slog.Warn("cert sweeper: fetching orgs", "err", err) + slog.Warn("cert sweeper: fetching instances", "err", err) return } for _, instanceID := range instanceIDs { - rules, err := queries.GetCertExpiryRulesForOrg(ctx, pool, instanceID) + rules, err := queries.GetCertExpiryRulesForInstance(ctx, pool, instanceID) if err != nil { slog.Warn("cert sweeper: fetching rules", "instance_id", instanceID, "err", err) continue diff --git a/apps/ingest/internal/handlers/inventory.go b/apps/ingest/internal/handlers/inventory.go index f2f517b9..9582b32a 100644 --- a/apps/ingest/internal/handlers/inventory.go +++ b/apps/ingest/internal/handlers/inventory.go @@ -73,7 +73,7 @@ func (h *InventoryHandler) SubmitSoftwareInventory(stream agentv1.IngestService_ } // ── Validate scan_id belongs to this agent ──────────────────────────────── - hostOrg, err := queries.GetHostOrgForTaskRunHost(ctx, h.pool, scanID, agentID) + hostInstance, err := queries.GetHostInstanceForTaskRunHost(ctx, h.pool, scanID, agentID) if err != nil { if err == pgx.ErrNoRows { return status.Errorf(codes.NotFound, "scan_id %s not found for agent %s", scanID, agentID) @@ -82,11 +82,11 @@ func (h *InventoryHandler) SubmitSoftwareInventory(stream agentv1.IngestService_ return status.Error(codes.Internal, "internal error") } - slog.Info("inventory: scan started", "scan_id", scanID, "agent_id", agentID, "host_id", hostOrg.HostID, "source", source) + slog.Info("inventory: scan started", "scan_id", scanID, "agent_id", agentID, "host_id", hostInstance.HostID, "source", source) // ── Create software_scans row ───────────────────────────────────────────── startedAt := time.Now() - softwareScanID, err := queries.InsertSoftwareScan(ctx, h.pool, hostOrg.InstanceID, hostOrg.HostID, scanID, source, startedAt) + softwareScanID, err := queries.InsertSoftwareScan(ctx, h.pool, hostInstance.InstanceID, hostInstance.HostID, scanID, source, startedAt) if err != nil { slog.Error("inventory: creating software scan", "err", err) return status.Error(codes.Internal, "could not create scan record") @@ -143,7 +143,7 @@ func (h *InventoryHandler) SubmitSoftwareInventory(stream agentv1.IngestService_ added, err := queries.UpsertSoftwarePackagesBatch( ctx, h.pool, - hostOrg.InstanceID, hostOrg.HostID, source, + hostInstance.InstanceID, hostInstance.HostID, source, names, versions, archs, publishers, distroIDs, distroVersionIDs, distroCodenames, distroIDLikes, sourceNames, sourceVersions, packageEpochs, packageReleases, @@ -167,7 +167,7 @@ func (h *InventoryHandler) SubmitSoftwareInventory(stream agentv1.IngestService_ } if first.IsLast { - return h.finalise(ctx, softwareScanID, scanID, hostOrg.HostID, source, startedAt, totalReceived, totalAdded, stream) + return h.finalise(ctx, softwareScanID, scanID, hostInstance.HostID, source, startedAt, totalReceived, totalAdded, stream) } // ── Receive remaining chunks ────────────────────────────────────────────── @@ -194,11 +194,11 @@ func (h *InventoryHandler) SubmitSoftwareInventory(stream agentv1.IngestService_ } if chunk.IsLast { - return h.finalise(ctx, softwareScanID, scanID, hostOrg.HostID, source, startedAt, totalReceived, totalAdded, stream) + return h.finalise(ctx, softwareScanID, scanID, hostInstance.HostID, source, startedAt, totalReceived, totalAdded, stream) } } - return h.finalise(ctx, softwareScanID, scanID, hostOrg.HostID, source, startedAt, totalReceived, totalAdded, stream) + return h.finalise(ctx, softwareScanID, scanID, hostInstance.HostID, source, startedAt, totalReceived, totalAdded, stream) } // finalise marks removed packages, completes the scan row, and sends the ack. diff --git a/apps/ingest/internal/handlers/notify.go b/apps/ingest/internal/handlers/notify.go index f01e6787..43f9d01d 100644 --- a/apps/ingest/internal/handlers/notify.go +++ b/apps/ingest/internal/handlers/notify.go @@ -500,7 +500,7 @@ func dispatchTelegram(ctx context.Context, channels []queries.TelegramChannelRow // dispatchInApp creates an in-app notification row for each eligible user in the org. // alertInstanceID may be empty (cert alerts use a different instance query). func dispatchInApp(ctx context.Context, pool *pgxpool.Pool, instanceID, alertInstanceID, resourceType, resourceID string, event AlertEvent) { - settings, err := queries.GetOrgNotificationSettings(ctx, pool, instanceID) + settings, err := queries.GetInstanceNotificationSettings(ctx, pool, instanceID) if err != nil { slog.Warn("dispatchInApp: fetching org notification settings", "instance_id", instanceID, "err", err) // Proceed with defaults (already set by the query function on error) diff --git a/apps/ingest/internal/handlers/register.go b/apps/ingest/internal/handlers/register.go index 3c5d4420..25a2fc42 100644 --- a/apps/ingest/internal/handlers/register.go +++ b/apps/ingest/internal/handlers/register.go @@ -270,7 +270,7 @@ func (h *RegisterHandler) Register(ctx context.Context, req *agentv1.RegisterReq // must be written here. Saved tag_rules are evaluated later in TS by // a cron/backfill job since they may depend on non-registration state. if hostID != "" { - defaults, err := queries.GetOrgDefaultTags(ctx, h.pool, instanceID) + defaults, err := queries.GetInstanceDefaultTags(ctx, h.pool, instanceID) if err != nil { slog.Warn("loading instance default tags", "err", err) } diff --git a/apps/ingest/internal/handlers/register_test.go b/apps/ingest/internal/handlers/register_test.go index 360b2df8..8909ef9f 100644 --- a/apps/ingest/internal/handlers/register_test.go +++ b/apps/ingest/internal/handlers/register_test.go @@ -85,11 +85,11 @@ func TestExistingAgentBelongsToTokenInstanceRejectsInstanceMismatch(t *testing.T existing := &queries.AgentRow{ ID: "agent_existing", - InstanceID: "org_original", + InstanceID: "instance_original", Status: "active", } - if existingAgentBelongsToTokenInstance(existing, "org_attacker") { + if existingAgentBelongsToTokenInstance(existing, "instance_attacker") { t.Fatal("existing agent matched a different enrolment token instance") } } diff --git a/apps/web/CHANGELOG.md b/apps/web/CHANGELOG.md index 3a21d316..8e1c4a42 100644 --- a/apps/web/CHANGELOG.md +++ b/apps/web/CHANGELOG.md @@ -159,7 +159,7 @@ ### Features -* **password-manager:** add tenant audit view ([d990582](https://github.com/carrtech-dev/ct-ops/commit/d9905825c5c9ed959e917514460093c1eff6cb57)) +* **password-manager:** add instance audit view ([d990582](https://github.com/carrtech-dev/ct-ops/commit/d9905825c5c9ed959e917514460093c1eff6cb57)) ### Bug Fixes @@ -276,8 +276,8 @@ ### Features -* **web:** remove organisation scope from web baseline ([0b41340](https://github.com/carrtech-dev/ct-ops/commit/0b41340e8ee70b6b552a5b21a0f1f361975deda6)) -* **web:** remove organisation scope from web baseline ([c1a8e54](https://github.com/carrtech-dev/ct-ops/commit/c1a8e54f1634dc0310c2493ce0975c1a7cfe7931)) +* **web:** remove instance scope from web baseline ([0b41340](https://github.com/carrtech-dev/ct-ops/commit/0b41340e8ee70b6b552a5b21a0f1f361975deda6)) +* **web:** remove instance scope from web baseline ([c1a8e54](https://github.com/carrtech-dev/ct-ops/commit/c1a8e54f1634dc0310c2493ce0975c1a7cfe7931)) ## [0.130.0](https://github.com/carrtech-dev/ct-ops/compare/web/v0.129.3...web/v0.130.0) (2026-05-10) @@ -343,7 +343,7 @@ ### Features -* **web:** remove organisation onboarding from auth setup ([#1218](https://github.com/carrtech-dev/ct-ops/issues/1218)) ([863f40e](https://github.com/carrtech-dev/ct-ops/commit/863f40ec8bc3552b77a86cf2ba0e33b0ea5198d7)) +* **web:** remove instance onboarding from auth setup ([#1218](https://github.com/carrtech-dev/ct-ops/issues/1218)) ([863f40e](https://github.com/carrtech-dev/ct-ops/commit/863f40ec8bc3552b77a86cf2ba0e33b0ea5198d7)) ## [0.124.5](https://github.com/carrtech-dev/ct-ops/compare/web/v0.124.4...web/v0.124.5) (2026-05-09) @@ -682,7 +682,7 @@ ### Bug Fixes -* **authz:** require write access for tenant mutations ([1f2d660](https://github.com/carrtech-dev/ct-ops/commit/1f2d6606344d340a54ffd3ea98dbc8aa1d5cd1af)) +* **authz:** require write access for instance mutations ([1f2d660](https://github.com/carrtech-dev/ct-ops/commit/1f2d6606344d340a54ffd3ea98dbc8aa1d5cd1af)) ## [0.106.0](https://github.com/carrtech-dev/ct-ops/compare/web/v0.105.1...web/v0.106.0) (2026-05-05) @@ -696,7 +696,7 @@ ### Bug Fixes -* **security:** scope host checks to tenant ([#1059](https://github.com/carrtech-dev/ct-ops/issues/1059)) ([c137203](https://github.com/carrtech-dev/ct-ops/commit/c137203ebec8acfc36156f50cf3587896ac3b845)) +* **security:** scope host checks to instance ([#1059](https://github.com/carrtech-dev/ct-ops/issues/1059)) ([c137203](https://github.com/carrtech-dev/ct-ops/commit/c137203ebec8acfc36156f50cf3587896ac3b845)) ## [0.105.0](https://github.com/carrtech-dev/ct-ops/compare/web/v0.104.0...web/v0.105.0) (2026-05-05) @@ -745,7 +745,7 @@ ### Bug Fixes -* **db:** scope auth tables with tenant RLS ([#1027](https://github.com/carrtech-dev/ct-ops/issues/1027)) ([28bd8e9](https://github.com/carrtech-dev/ct-ops/commit/28bd8e937246c6822a619996c74e33cb5062a9f3)) +* **db:** scope auth tables with instance RLS ([#1027](https://github.com/carrtech-dev/ct-ops/issues/1027)) ([28bd8e9](https://github.com/carrtech-dev/ct-ops/commit/28bd8e937246c6822a619996c74e33cb5062a9f3)) ## [0.103.15](https://github.com/carrtech-dev/ct-ops/compare/web/v0.103.14...web/v0.103.15) (2026-05-05) @@ -759,7 +759,7 @@ ### Bug Fixes -* **db:** fail closed for tenant RLS scope ([0612b94](https://github.com/carrtech-dev/ct-ops/commit/0612b94489c4bf4517509faf6d76a18d11e13c19)), closes [#980](https://github.com/carrtech-dev/ct-ops/issues/980) +* **db:** fail closed for instance RLS scope ([0612b94](https://github.com/carrtech-dev/ct-ops/commit/0612b94489c4bf4517509faf6d76a18d11e13c19)), closes [#980](https://github.com/carrtech-dev/ct-ops/issues/980) ## [0.103.13](https://github.com/carrtech-dev/ct-ops/compare/web/v0.103.12...web/v0.103.13) (2026-05-05) @@ -787,7 +787,7 @@ ### Bug Fixes -* **auth:** scope LDAP login to organisation slug ([f5e331e](https://github.com/carrtech-dev/ct-ops/commit/f5e331ed73e272a38ee8fe4a4ee203b396d67e9e)) +* **auth:** scope LDAP login to instance slug ([f5e331e](https://github.com/carrtech-dev/ct-ops/commit/f5e331ed73e272a38ee8fe4a4ee203b396d67e9e)) ## [0.103.9](https://github.com/carrtech-dev/ct-ops/compare/web/v0.103.8...web/v0.103.9) (2026-05-05) @@ -1229,7 +1229,7 @@ ### Bug Fixes -* **web:** add org-scoped RLS helper ([#681](https://github.com/carrtech-dev/ct-ops/issues/681)) ([08e4b6e](https://github.com/carrtech-dev/ct-ops/commit/08e4b6edb59a2f4a45d3cd70f28b46f9ebaeb0d5)) +* **web:** add instance-scoped RLS helper ([#681](https://github.com/carrtech-dev/ct-ops/issues/681)) ([08e4b6e](https://github.com/carrtech-dev/ct-ops/commit/08e4b6edb59a2f4a45d3cd70f28b46f9ebaeb0d5)) ## [0.74.2](https://github.com/carrtech-dev/ct-ops/compare/web/v0.74.1...web/v0.74.2) (2026-04-28) @@ -1497,7 +1497,7 @@ ### Bug Fixes -* **auth:** scope LDAP login links to organisation ([96d28df](https://github.com/carrtech-dev/ct-ops/commit/96d28df206a7548ef5b1e07e6237585bf8f15cb8)), closes [#273](https://github.com/carrtech-dev/ct-ops/issues/273) +* **auth:** scope LDAP login links to instance ([96d28df](https://github.com/carrtech-dev/ct-ops/commit/96d28df206a7548ef5b1e07e6237585bf8f15cb8)), closes [#273](https://github.com/carrtech-dev/ct-ops/issues/273) ## [0.68.0](https://github.com/carrtech-dev/ct-ops/compare/web/v0.67.2...web/v0.68.0) (2026-04-26) diff --git a/apps/web/app/(dashboard)/alerts/alerts-client.tsx b/apps/web/app/(dashboard)/alerts/alerts-client.tsx index b5dd92be..a4b661f9 100644 --- a/apps/web/app/(dashboard)/alerts/alerts-client.tsx +++ b/apps/web/app/(dashboard)/alerts/alerts-client.tsx @@ -939,13 +939,13 @@ function AddSilenceDialog({
- + - {orgUsers.members + {instanceUsers.members .filter((u) => !(currentTerminalSettings.terminalAllowedUsers ?? []).includes(u.id)) .filter((u) => u.role !== 'agent') .map((u) => ( @@ -620,7 +620,7 @@ export function SettingsTab({ hostId, isAdmin }: SettingsTabProps) { {/* Current allowlist */}
{(currentTerminalSettings.terminalAllowedUsers ?? []).map((userId) => { - const user = orgUsers?.members.find((u) => u.id === userId) + const user = instanceUsers?.members.find((u) => u.id === userId) return ( diff --git a/apps/web/app/(dashboard)/hosts/groups/[id]/group-detail-client.tsx b/apps/web/app/(dashboard)/hosts/groups/[id]/group-detail-client.tsx index 986e1b9d..de9c5206 100644 --- a/apps/web/app/(dashboard)/hosts/groups/[id]/group-detail-client.tsx +++ b/apps/web/app/(dashboard)/hosts/groups/[id]/group-detail-client.tsx @@ -180,7 +180,7 @@ export function GroupDetailClient({ }: Props) { const queryClient = useQueryClient() const router = useRouter() - const canRunTasks = userRole === 'org_admin' || userRole === 'super_admin' + const canRunTasks = userRole === 'instance_admin' || userRole === 'super_admin' const canUseAnsible = canRunTasks && ansibleAutomationEnabled const [addOpen, setAddOpen] = useState(false) const [search, setSearch] = useState('') diff --git a/apps/web/app/(dashboard)/hosts/hosts-client.tsx b/apps/web/app/(dashboard)/hosts/hosts-client.tsx index 10297508..0f7938d2 100644 --- a/apps/web/app/(dashboard)/hosts/hosts-client.tsx +++ b/apps/web/app/(dashboard)/hosts/hosts-client.tsx @@ -203,7 +203,7 @@ export function HostsClient({ initialPendingAgents, }: HostsClientProps) { const queryClient = useQueryClient() - const isAdmin = currentUserRole === 'super_admin' || currentUserRole === 'org_admin' + const isAdmin = currentUserRole === 'super_admin' || currentUserRole === 'instance_admin' // ─── Filter / sort / page state ──────────────────────────────────────────── const [searchInput, setSearchInput] = useState('') diff --git a/apps/web/app/(dashboard)/password-manager/password-manager-client.tsx b/apps/web/app/(dashboard)/password-manager/password-manager-client.tsx index 2e73db5c..be8c086d 100644 --- a/apps/web/app/(dashboard)/password-manager/password-manager-client.tsx +++ b/apps/web/app/(dashboard)/password-manager/password-manager-client.tsx @@ -3992,7 +3992,7 @@ function PasswordManagerWorkspace({
Audit - Tenant-wide Password Manager activity with public-safe details only. + Instance-wide Password Manager activity with public-safe details only.
diff --git a/apps/web/app/(dashboard)/profile/profile-client.tsx b/apps/web/app/(dashboard)/profile/profile-client.tsx index 10a719f0..ffdf05bd 100644 --- a/apps/web/app/(dashboard)/profile/profile-client.tsx +++ b/apps/web/app/(dashboard)/profile/profile-client.tsx @@ -94,8 +94,8 @@ export function ProfileClient({ user, instanceId }: ProfileClientProps) { }, [twoFactorUri]) const requireTwoFactorSetup = searchParams.get('setup') === 'two-factor' && !twoFactorEnabled - const { data: orgNotifSettings } = useQuery({ - queryKey: ['org-notification-settings', instanceId], + const { data: instanceNotifSettings } = useQuery({ + queryKey: ['instance-notification-settings', instanceId], queryFn: () => getInstanceNotificationSettings(instanceId), enabled: !!instanceId, }) @@ -568,7 +568,7 @@ export function ProfileClient({ user, instanceId }: ProfileClientProps) { {/* Notifications */} - {orgNotifSettings?.inAppEnabled && ( + {instanceNotifSettings?.inAppEnabled && ( @@ -576,7 +576,7 @@ export function ProfileClient({ user, instanceId }: ProfileClientProps) { Notifications - {orgNotifSettings.allowUserOptOut + {instanceNotifSettings.allowUserOptOut ? 'Control whether you receive in-app notifications for alert events' : 'Your instance requires in-app notifications for your role'} @@ -591,7 +591,7 @@ export function ProfileClient({ user, instanceId }: ProfileClientProps) {
{ setNotificationsEnabled(checked) diff --git a/apps/web/app/(dashboard)/reports/software/page.tsx b/apps/web/app/(dashboard)/reports/software/page.tsx index 6b5b4abf..2382c523 100644 --- a/apps/web/app/(dashboard)/reports/software/page.tsx +++ b/apps/web/app/(dashboard)/reports/software/page.tsx @@ -14,7 +14,7 @@ export default async function SoftwareReportPage() { return ( - + ) } diff --git a/apps/web/app/(dashboard)/reports/software/software-report-client.tsx b/apps/web/app/(dashboard)/reports/software/software-report-client.tsx index f6eb1e74..faae9335 100644 --- a/apps/web/app/(dashboard)/reports/software/software-report-client.tsx +++ b/apps/web/app/(dashboard)/reports/software/software-report-client.tsx @@ -81,7 +81,7 @@ import { compareVersions } from '@/lib/version-compare' import type { HostGroup } from '@/lib/db/schema' interface Props { - orgName: string + instanceName: string hostGroups: Pick[] } @@ -107,7 +107,9 @@ const OS_OPTIONS = [ ] -export function SoftwareReportClient({ orgName, hostGroups }: Props) { +export function SoftwareReportClient(props: Props) { + void props + const queryClient = useQueryClient() const [activeView, setActiveView] = useState('search') diff --git a/apps/web/app/(dashboard)/settings/agents/defaults/page.tsx b/apps/web/app/(dashboard)/settings/agents/defaults/page.tsx index a3f288da..2e036b29 100644 --- a/apps/web/app/(dashboard)/settings/agents/defaults/page.tsx +++ b/apps/web/app/(dashboard)/settings/agents/defaults/page.tsx @@ -12,12 +12,12 @@ export const metadata: Metadata = { export default async function AgentDefaultsPage() { const session = await getRequiredSession() - const isAdmin = hasRole(session.user, ['org_admin', 'super_admin']) + const isAdmin = hasRole(session.user, ['instance_admin', 'super_admin']) if (!isAdmin) redirect('/dashboard') - const org = await getCurrentInstanceSettingsRecord() + const instance = await getCurrentInstanceSettingsRecord() - if (!org) return null + if (!instance) return null return (
@@ -30,7 +30,7 @@ export default async function AgentDefaultsPage() { ]} /> @@ -30,7 +30,7 @@ export default async function SoftwareInventorySettingsPage() { ]} /> = { ['org' + 'Id']: scopeId } + const scopeRef: Record = { ['instance' + 'Id']: scopeId } const [overview, settings] = await Promise.all([ buildCtCveConnectorSetupOverview(scopeRef as unknown as Parameters[0]), getCtCveConnectorSettingsForAdmin(scopeId), diff --git a/apps/web/app/(dashboard)/settings/integrations/page.tsx b/apps/web/app/(dashboard)/settings/integrations/page.tsx index 3a673644..f0c846b0 100644 --- a/apps/web/app/(dashboard)/settings/integrations/page.tsx +++ b/apps/web/app/(dashboard)/settings/integrations/page.tsx @@ -14,7 +14,7 @@ export const metadata: Metadata = { export default async function IntegrationsSettingsPage() { const session = await getRequiredSession() - if (!hasRole(session.user, ['org_admin', 'super_admin'])) { + if (!hasRole(session.user, ['instance_admin', 'super_admin'])) { redirect('/settings') } diff --git a/apps/web/app/(dashboard)/settings/integrations/smtp/page.tsx b/apps/web/app/(dashboard)/settings/integrations/smtp/page.tsx index e3bbf340..30a209bd 100644 --- a/apps/web/app/(dashboard)/settings/integrations/smtp/page.tsx +++ b/apps/web/app/(dashboard)/settings/integrations/smtp/page.tsx @@ -13,12 +13,12 @@ export const metadata: Metadata = { export default async function SmtpRelaySettingsPage() { const session = await getRequiredSession() - const isAdmin = hasRole(session.user, ['org_admin', 'super_admin']) + const isAdmin = hasRole(session.user, ['instance_admin', 'super_admin']) if (!isAdmin) redirect('/dashboard') - const org = await getCurrentInstanceSettingsRecord() + const instance = await getCurrentInstanceSettingsRecord() - if (!org) { + if (!instance) { return (
@@ -71,7 +71,7 @@ export default async function LicenceSettingsPage() { ]} />
diff --git a/apps/web/app/(dashboard)/settings/monitoring/notifications/page.tsx b/apps/web/app/(dashboard)/settings/monitoring/notifications/page.tsx index 3f9c59ee..ca104d61 100644 --- a/apps/web/app/(dashboard)/settings/monitoring/notifications/page.tsx +++ b/apps/web/app/(dashboard)/settings/monitoring/notifications/page.tsx @@ -12,12 +12,12 @@ export const metadata: Metadata = { export default async function NotificationPolicyPage() { const session = await getRequiredSession() - const isAdmin = hasRole(session.user, ['org_admin', 'super_admin']) + const isAdmin = hasRole(session.user, ['instance_admin', 'super_admin']) if (!isAdmin) redirect('/dashboard') - const org = await getCurrentInstanceSettingsRecord() + const instance = await getCurrentInstanceSettingsRecord() - if (!org) return null + if (!instance) return null return (
@@ -29,7 +29,7 @@ export default async function NotificationPolicyPage() { ]} /> @@ -29,7 +29,7 @@ export default async function MetricRetentionSettingsPage() { ]} /> @@ -51,7 +51,7 @@ export default async function SettingsPage() { ]} /> @@ -36,7 +36,7 @@ export default async function TerminalAccessSettingsPage() { ]} /> +type InstanceNameValues = z.infer const licenceSchema = z.object({ key: z.string().min(1, 'Licence key is required'), @@ -67,7 +67,7 @@ export type SettingsSection = | 'software' interface SettingsClientProps { - org: Instance + instance: Instance isAdmin: boolean sections?: SettingsSection[] title?: string @@ -175,7 +175,7 @@ const EMPTY_SMTP_RELAY_SETTINGS: InstanceSmtpRelaySettingsInput = { } export function SettingsClient({ - org, + instance, isAdmin, sections, title = 'Settings', @@ -186,7 +186,7 @@ export function SettingsClient({ }: SettingsClientProps) { const router = useRouter() const queryClient = useQueryClient() - const tier = effectiveLicence?.tier ?? (org.licenceTier as LicenceTier) + const tier = effectiveLicence?.tier ?? (instance.licenceTier as LicenceTier) const visibleSections = new Set( sections ?? [ 'instance', @@ -200,7 +200,7 @@ export function SettingsClient({ 'licence', ], ) - const [orgSaveSuccess, setOrgSaveSuccess] = useState(false) + const [instanceSaveSuccess, setInstanceSaveSuccess] = useState(false) const [licenceResult, setLicenceResult] = useState<{ success?: boolean tier?: string @@ -209,10 +209,10 @@ export function SettingsClient({ previousMaxUsers?: number error?: string } | null>(null) - const [retentionDays, setRetentionDays] = useState(String(org.metricRetentionDays ?? 30)) + const [retentionDays, setRetentionDays] = useState(String(instance.metricRetentionDays ?? 30)) const [retentionSaveSuccess, setRetentionSaveSuccess] = useState(false) const [retentionError, setRetentionError] = useState(null) - const [dockerRetentionDays, setDockerRetentionDays] = useState(String(org.dockerMetricRetentionDays ?? 30)) + const [dockerRetentionDays, setDockerRetentionDays] = useState(String(instance.dockerMetricRetentionDays ?? 30)) const [dockerRetentionSaveSuccess, setDockerRetentionSaveSuccess] = useState(false) const [dockerRetentionError, setDockerRetentionError] = useState(null) const [selectedFreeSeatUserIds, setSelectedFreeSeatUserIds] = useState( @@ -220,9 +220,9 @@ export function SettingsClient({ ) const [freeSeatResult, setFreeSeatResult] = useState<{ success?: boolean; error?: string } | null>(null) - const orgForm = useForm({ - resolver: zodResolver(orgNameSchema), - defaultValues: { name: org.name }, + const instanceForm = useForm({ + resolver: zodResolver(instanceNameSchema), + defaultValues: { name: instance.name }, }) const licenceForm = useForm({ @@ -230,20 +230,20 @@ export function SettingsClient({ defaultValues: { key: '' }, }) - const orgMutation = useMutation({ - mutationFn: (values: OrgNameValues) => updateOrgName(org.id, values.name), + const instanceMutation = useMutation({ + mutationFn: (values: InstanceNameValues) => updateInstanceName(instance.id, values.name), onSuccess: (result) => { if ('error' in result) { - orgForm.setError('name', { message: result.error }) + instanceForm.setError('name', { message: result.error }) return } - setOrgSaveSuccess(true) - setTimeout(() => setOrgSaveSuccess(false), 3000) + setInstanceSaveSuccess(true) + setTimeout(() => setInstanceSaveSuccess(false), 3000) }, }) const licenceMutation = useMutation({ - mutationFn: (values: LicenceValues) => saveLicenceKey(org.id, values.key), + mutationFn: (values: LicenceValues) => saveLicenceKey(instance.id, values.key), onSuccess: (result) => { if ('error' in result) { setLicenceResult({ error: result.error }) @@ -262,7 +262,7 @@ export function SettingsClient({ }) const freeSeatMutation = useMutation({ - mutationFn: (userIds: string[]) => updateFreeSeatUsers(org.id, userIds), + mutationFn: (userIds: string[]) => updateFreeSeatUsers(instance.id, userIds), onSuccess: (result) => { if ('error' in result) { setFreeSeatResult({ error: result.error }) @@ -280,7 +280,7 @@ export function SettingsClient({ const [activationCopied, setActivationCopied] = useState(false) const activationMutation = useMutation({ - mutationFn: () => generateActivationToken(org.id), + mutationFn: () => generateActivationToken(instance.id), onSuccess: (result) => { if ('error' in result) { setActivationError(result.error) @@ -315,7 +315,7 @@ export function SettingsClient({ } const retentionMutation = useMutation({ - mutationFn: (days: number) => updateMetricRetention(org.id, days), + mutationFn: (days: number) => updateMetricRetention(instance.id, days), onSuccess: (result) => { if ('error' in result) { setRetentionError(result.error) @@ -328,7 +328,7 @@ export function SettingsClient({ }) const dockerRetentionMutation = useMutation({ - mutationFn: (days: number) => updateDockerMetricRetention(org.id, days), + mutationFn: (days: number) => updateDockerMetricRetention(instance.id, days), onSuccess: (result) => { if ('error' in result) { setDockerRetentionError(result.error) @@ -343,20 +343,20 @@ export function SettingsClient({ // Default collection settings const [collectionSaveSuccess, setCollectionSaveSuccess] = useState(false) const { data: collectionDefaults } = useQuery({ - queryKey: ['org-collection-defaults', org.id], - queryFn: () => getOrgDefaultCollectionSettings(org.id), + queryKey: ['instance-collection-defaults', instance.id], + queryFn: () => getInstanceDefaultCollectionSettings(instance.id), }) const [localCollectionSettings, setLocalCollectionSettings] = useState(null) const currentCollectionSettings = localCollectionSettings ?? collectionDefaults ?? { ...DEFAULT_COLLECTION_SETTINGS } const collectionDirty = localCollectionSettings !== null const collectionMutation = useMutation({ - mutationFn: (settings: HostCollectionSettings) => updateOrgDefaultCollectionSettings(org.id, settings), + mutationFn: (settings: HostCollectionSettings) => updateInstanceDefaultCollectionSettings(instance.id, settings), onSuccess: (result) => { if ('error' in result) return setLocalCollectionSettings(null) setCollectionSaveSuccess(true) - queryClient.invalidateQueries({ queryKey: ['org-collection-defaults', org.id] }) + queryClient.invalidateQueries({ queryKey: ['instance-collection-defaults', instance.id] }) setTimeout(() => setCollectionSaveSuccess(false), 3000) }, }) @@ -364,8 +364,8 @@ export function SettingsClient({ // Default tags const [tagsSaveSuccess, setTagsSaveSuccess] = useState(false) const { data: defaultTags } = useQuery({ - queryKey: ['org-default-tags', org.id], - queryFn: () => getOrgDefaultTags(org.id), + queryKey: ['instance-default-tags', instance.id], + queryFn: () => getInstanceDefaultTags(instance.id), }) const [localDefaultTags, setLocalDefaultTags] = useState(null) const currentDefaultTags: EditorTag[] = @@ -373,12 +373,12 @@ export function SettingsClient({ const tagsDirty = localDefaultTags !== null const tagsMutation = useMutation({ - mutationFn: (pairs: TagPair[]) => updateOrgDefaultTags(org.id, pairs), + mutationFn: (pairs: TagPair[]) => updateInstanceDefaultTags(instance.id, pairs), onSuccess: (result) => { if ('error' in result) return setLocalDefaultTags(null) setTagsSaveSuccess(true) - queryClient.invalidateQueries({ queryKey: ['org-default-tags', org.id] }) + queryClient.invalidateQueries({ queryKey: ['instance-default-tags', instance.id] }) setTimeout(() => setTagsSaveSuccess(false), 3000) }, }) @@ -386,20 +386,20 @@ export function SettingsClient({ // Terminal settings const [terminalSaveSuccess, setTerminalSaveSuccess] = useState(false) const { data: terminalDefaults } = useQuery({ - queryKey: ['org-terminal-settings', org.id], - queryFn: () => getOrgTerminalSettings(org.id), + queryKey: ['instance-terminal-settings', instance.id], + queryFn: () => getInstanceTerminalSettings(instance.id), }) - const [localTerminalSettings, setLocalTerminalSettings] = useState(null) + const [localTerminalSettings, setLocalTerminalSettings] = useState(null) const currentTerminalSettings = localTerminalSettings ?? terminalDefaults ?? { terminalEnabled: true, terminalLoggingEnabled: false, terminalDirectAccess: false } const terminalDirty = localTerminalSettings !== null const terminalMutation = useMutation({ - mutationFn: (settings: OrgTerminalSettings) => updateOrgTerminalSettings(org.id, settings), + mutationFn: (settings: InstanceTerminalSettings) => updateInstanceTerminalSettings(instance.id, settings), onSuccess: (result) => { if ('error' in result) return setLocalTerminalSettings(null) setTerminalSaveSuccess(true) - queryClient.invalidateQueries({ queryKey: ['org-terminal-settings', org.id] }) + queryClient.invalidateQueries({ queryKey: ['instance-terminal-settings', instance.id] }) setTimeout(() => setTerminalSaveSuccess(false), 3000) }, }) @@ -407,24 +407,24 @@ export function SettingsClient({ // Notification settings const [notificationSaveSuccess, setNotificationSaveSuccess] = useState(false) const { data: notificationDefaults } = useQuery({ - queryKey: ['org-notification-settings', org.id], - queryFn: () => getInstanceNotificationSettings(org.id), + queryKey: ['instance-notification-settings', instance.id], + queryFn: () => getInstanceNotificationSettings(instance.id), }) const [localNotificationSettings, setLocalNotificationSettings] = useState(null) const currentNotificationSettings = localNotificationSettings ?? notificationDefaults ?? { inAppEnabled: true, - inAppRoles: ['super_admin', 'org_admin', 'engineer'], + inAppRoles: ['super_admin', 'instance_admin', 'engineer'], allowUserOptOut: true, } const notificationDirty = localNotificationSettings !== null const notificationMutation = useMutation({ - mutationFn: (settings: InstanceNotificationSettingsFull) => updateInstanceNotificationSettings(org.id, settings), + mutationFn: (settings: InstanceNotificationSettingsFull) => updateInstanceNotificationSettings(instance.id, settings), onSuccess: (result) => { if ('error' in result) return setLocalNotificationSettings(null) setNotificationSaveSuccess(true) - queryClient.invalidateQueries({ queryKey: ['org-notification-settings', org.id] }) + queryClient.invalidateQueries({ queryKey: ['instance-notification-settings', instance.id] }) setTimeout(() => setNotificationSaveSuccess(false), 3000) }, }) @@ -440,8 +440,8 @@ export function SettingsClient({ log?: SmtpRelayTestLogEntry[] } | null>(null) const { data: smtpRelayDefaults } = useQuery({ - queryKey: ['org-smtp-relay-settings', org.id], - queryFn: () => getOrgSmtpRelaySettings(org.id), + queryKey: ['instance-smtp-relay-settings', instance.id], + queryFn: () => getInstanceSmtpRelaySettings(instance.id), }) const [localSmtpRelaySettings, setLocalSmtpRelaySettings] = useState(null) const currentSmtpRelaySettings = localSmtpRelaySettings ?? { @@ -452,7 +452,7 @@ export function SettingsClient({ const smtpDirty = localSmtpRelaySettings !== null const smtpMutation = useMutation({ - mutationFn: (settings: InstanceSmtpRelaySettingsInput) => updateOrgSmtpRelaySettings(org.id, settings), + mutationFn: (settings: InstanceSmtpRelaySettingsInput) => updateInstanceSmtpRelaySettings(instance.id, settings), onSuccess: (result) => { if ('error' in result) { setSmtpError(result.error) @@ -462,13 +462,13 @@ export function SettingsClient({ setLocalSmtpRelaySettings(null) setSmtpError(null) setSmtpSaveSuccess(true) - queryClient.invalidateQueries({ queryKey: ['org-smtp-relay-settings', org.id] }) + queryClient.invalidateQueries({ queryKey: ['instance-smtp-relay-settings', instance.id] }) setTimeout(() => setSmtpSaveSuccess(false), 3000) }, }) const smtpTestMutation = useMutation({ - mutationFn: (recipient: string) => sendTestSmtpRelaySettings(org.id, recipient), + mutationFn: (recipient: string) => sendTestSmtpRelaySettings(instance.id, recipient), onSuccess: (result, recipient) => { if ('error' in result) { setSmtpTestResult({ error: result.error, log: result.log }) @@ -493,20 +493,20 @@ export function SettingsClient({ // Software inventory settings const [swInvSaveSuccess, setSwInvSaveSuccess] = useState(false) const { data: swInvDefaults } = useQuery({ - queryKey: ['org-software-inventory-settings', org.id], - queryFn: () => getSoftwareInventorySettings(org.id), + queryKey: ['instance-software-inventory-settings', instance.id], + queryFn: () => getSoftwareInventorySettings(instance.id), }) const [localSwInvSettings, setLocalSwInvSettings] = useState(null) const currentSwInvSettings = localSwInvSettings ?? swInvDefaults ?? { enabled: false, intervalHours: 24 } const swInvDirty = localSwInvSettings !== null const swInvMutation = useMutation({ - mutationFn: (settings: SoftwareInventorySettings) => updateSoftwareInventorySettings(org.id, settings), + mutationFn: (settings: SoftwareInventorySettings) => updateSoftwareInventorySettings(instance.id, settings), onSuccess: (result) => { if ('error' in result) return setLocalSwInvSettings(null) setSwInvSaveSuccess(true) - queryClient.invalidateQueries({ queryKey: ['org-software-inventory-settings', org.id] }) + queryClient.invalidateQueries({ queryKey: ['instance-software-inventory-settings', instance.id] }) setTimeout(() => setSwInvSaveSuccess(false), 3000) }, }) @@ -544,19 +544,19 @@ export function SettingsClient({ {isAdmin ? ( orgMutation.mutate(v))} + onSubmit={instanceForm.handleSubmit((v) => instanceMutation.mutate(v))} className="space-y-4" >
- + - {orgForm.formState.errors.name && ( + {instanceForm.formState.errors.name && (

- {orgForm.formState.errors.name.message} + {instanceForm.formState.errors.name.message}

)}
@@ -564,15 +564,15 @@ export function SettingsClient({ - {orgSaveSuccess && ( + {instanceSaveSuccess && ( Saved @@ -581,7 +581,7 @@ export function SettingsClient({
) : ( -

{org.name}

+

{instance.name}

)} @@ -627,7 +627,7 @@ export function SettingsClient({
diff --git a/apps/web/app/(dashboard)/settings/system/page.tsx b/apps/web/app/(dashboard)/settings/system/page.tsx index 7998adb5..435c2b09 100644 --- a/apps/web/app/(dashboard)/settings/system/page.tsx +++ b/apps/web/app/(dashboard)/settings/system/page.tsx @@ -10,7 +10,7 @@ export const metadata: Metadata = { export default async function SystemHealthPage() { const session = await getRequiredSession() - const isAdmin = hasRole(session.user, ['org_admin', 'super_admin']) + const isAdmin = hasRole(session.user, ['instance_admin', 'super_admin']) if (!isAdmin) redirect('/dashboard') return diff --git a/apps/web/app/(dashboard)/tasks/schedules-client.tsx b/apps/web/app/(dashboard)/tasks/schedules-client.tsx index 9092b74b..c9c8557f 100644 --- a/apps/web/app/(dashboard)/tasks/schedules-client.tsx +++ b/apps/web/app/(dashboard)/tasks/schedules-client.tsx @@ -60,7 +60,7 @@ export function SchedulesClient({ const [deleteTarget, setDeleteTarget] = useState(null) const [errorMsg, setErrorMsg] = useState(null) - const canEdit = userRole === 'org_admin' || userRole === 'super_admin' + const canEdit = userRole === 'instance_admin' || userRole === 'super_admin' const { data: schedules = initialSchedules } = useQuery({ queryKey: ['task-schedules'], diff --git a/apps/web/app/(dashboard)/team/page.tsx b/apps/web/app/(dashboard)/team/page.tsx index 6374d5b7..0fd41188 100644 --- a/apps/web/app/(dashboard)/team/page.tsx +++ b/apps/web/app/(dashboard)/team/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next' import { getRequiredSession } from '@/lib/auth/session' -import { getOrgUsers } from '@/lib/actions/users' +import { getInstanceUsers } from '@/lib/actions/users' import { TeamClient } from './team-client' export const metadata: Metadata = { @@ -9,7 +9,7 @@ export const metadata: Metadata = { export default async function TeamPage() { const session = await getRequiredSession() - const { members, pendingInvites } = await getOrgUsers() + const { members, pendingInvites } = await getInstanceUsers() return ( role === 'super_admin' || role === 'org_admin' +const canManage = (role: string) => role === 'super_admin' || role === 'instance_admin' function getDisplayedRoles(entity: Pick): string[] { const roles = normalizeAssignedRoles(entity.roles, entity.role) @@ -79,8 +79,8 @@ export function TeamClient({ const [copiedLink, setCopiedLink] = useState(false) const { data } = useQuery({ - queryKey: ['org-users'], - queryFn: () => getOrgUsers(), + queryKey: ['instance-users'], + queryFn: () => getInstanceUsers(), initialData: { members: initialMembers, pendingInvites: initialPendingInvites }, refetchOnMount: 'always', refetchOnWindowFocus: 'always', @@ -90,7 +90,7 @@ export function TeamClient({ const members = data?.members ?? [] const pendingInvites = data?.pendingInvites ?? [] - const invalidate = () => queryClient.invalidateQueries({ queryKey: ['org-users'] }) + const invalidate = () => queryClient.invalidateQueries({ queryKey: ['instance-users'] }) const { handleSubmit, diff --git a/apps/web/app/api/agent/bundle/route.ts b/apps/web/app/api/agent/bundle/route.ts index fc4f6960..8b302cac 100644 --- a/apps/web/app/api/agent/bundle/route.ts +++ b/apps/web/app/api/agent/bundle/route.ts @@ -63,7 +63,7 @@ const requestSchema = z.object({ * and embeds a single-use, short-lived enrolment token — this is recorded * in the `agent_enrolment_tokens` table (the audit trail). * - * Gated on org_admin / super_admin. Scoped by instanceId. + * Gated on instance_admin / super_admin. Scoped by instanceId. */ export async function POST(request: NextRequest) { try { diff --git a/apps/web/app/api/auth/ldap/route.ts b/apps/web/app/api/auth/ldap/route.ts index fe065061..70f8e193 100644 --- a/apps/web/app/api/auth/ldap/route.ts +++ b/apps/web/app/api/auth/ldap/route.ts @@ -153,7 +153,7 @@ export async function POST(request: NextRequest) { userId = createId() const email = ldapUser.email || `${ldapUser.username}@ldap.local` - // Find the org from the LDAP config + // Find the instance from the LDAP config. await db.insert(users).values({ id: userId, name: ldapUser.displayName ?? ldapUser.username, diff --git a/apps/web/app/api/integrations/ct-cve/v1/connection-health/route.ts b/apps/web/app/api/integrations/ct-cve/v1/connection-health/route.ts index e294030f..f02053bc 100644 --- a/apps/web/app/api/integrations/ct-cve/v1/connection-health/route.ts +++ b/apps/web/app/api/integrations/ct-cve/v1/connection-health/route.ts @@ -5,7 +5,7 @@ import { verifyCtCveServiceRequest, type CtCveServiceAuthError, } from '@/lib/integrations/ct-cve/service-token' -import { getCtCveServiceTokensForOrg } from '@/lib/integrations/ct-cve/connector-settings' +import { getCtCveServiceTokensForInstance } from '@/lib/integrations/ct-cve/connector-settings' import { recordCtCveConnectionHealth } from '@/lib/integrations/ct-cve/connection-status' export const dynamic = 'force-dynamic' @@ -44,7 +44,7 @@ export async function GET(request: NextRequest) { ) } - const tokens = await getCtCveServiceTokensForOrg(instanceId) + const tokens = await getCtCveServiceTokensForInstance(instanceId) if (tokens.length === 0) { return NextResponse.json( { diff --git a/apps/web/app/api/integrations/ct-cve/v1/finding-batches/route.ts b/apps/web/app/api/integrations/ct-cve/v1/finding-batches/route.ts index 7fe6d667..0a1d1927 100644 --- a/apps/web/app/api/integrations/ct-cve/v1/finding-batches/route.ts +++ b/apps/web/app/api/integrations/ct-cve/v1/finding-batches/route.ts @@ -5,7 +5,7 @@ import { verifyCtCveServiceRequest, type CtCveServiceAuthError, } from '@/lib/integrations/ct-cve/service-token' -import { getCtCveServiceTokensForOrg } from '@/lib/integrations/ct-cve/connector-settings' +import { getCtCveServiceTokensForInstance } from '@/lib/integrations/ct-cve/connector-settings' import { CtCveFindingBatchValidationError, ingestCtCveFindingBatch, @@ -69,7 +69,7 @@ export async function POST(request: NextRequest) { }, 400) } - const tokens = await getCtCveServiceTokensForOrg(instanceId) + const tokens = await getCtCveServiceTokensForInstance(instanceId) if (tokens.length === 0) { return errorResponse({ code: 'ct_cve_not_configured', diff --git a/apps/web/app/api/reports/software/export/route.ts b/apps/web/app/api/reports/software/export/route.ts index 06a99e02..4de0293b 100644 --- a/apps/web/app/api/reports/software/export/route.ts +++ b/apps/web/app/api/reports/software/export/route.ts @@ -210,10 +210,10 @@ export async function GET(req: NextRequest) { } // ── PDF ────────────────────────────────────────────────────────────────────── - const orgName = 'CT-Ops' + const instanceName = 'CT-Ops' const pdfElement = createElement(SoftwareReportPDF, { - orgName, + instanceName, packageName, versionFilter: vm && vm !== 'any' ? { mode: vm, exact: ve, prefix: vp, low: vl, high: vh } : undefined, osFamily, diff --git a/apps/web/app/api/system/health/route.ts b/apps/web/app/api/system/health/route.ts index 784950d0..429a3b99 100644 --- a/apps/web/app/api/system/health/route.ts +++ b/apps/web/app/api/system/health/route.ts @@ -58,7 +58,7 @@ export async function GET() { }) } - const [agentRows, org, agentVersionRows, ingestLatestRows, ingestHistoryRows, agentErrorRows] = await Promise.all([ + const [agentRows, instance, agentVersionRows, ingestLatestRows, ingestHistoryRows, agentErrorRows] = await Promise.all([ db .select({ status: agents.status, count: count() }) .from(agents) @@ -235,9 +235,9 @@ export async function GET() { return Response.json({ version: pkg.version, - licenceTier: org?.licenceTier ?? 'community', - metricRetentionDays: org?.metricRetentionDays ?? 30, - dockerMetricRetentionDays: org?.dockerMetricRetentionDays ?? 30, + licenceTier: instance?.licenceTier ?? 'community', + metricRetentionDays: instance?.metricRetentionDays ?? 30, + dockerMetricRetentionDays: instance?.dockerMetricRetentionDays ?? 30, database: { connected: true }, agents: { online: agentOnline, diff --git a/apps/web/components/notes/note-card.tsx b/apps/web/components/notes/note-card.tsx index 9d53f1e6..a80161b6 100644 --- a/apps/web/components/notes/note-card.tsx +++ b/apps/web/components/notes/note-card.tsx @@ -101,7 +101,7 @@ export function NoteCard({ const [actionError, setActionError] = useState(null) const isAuthor = note.authorId === currentUserId - const isAdmin = userRole === 'super_admin' || userRole === 'org_admin' + const isAdmin = userRole === 'super_admin' || userRole === 'instance_admin' const canEdit = !note.deletedAt && userRole !== 'read_only' && (isAuthor || isAdmin) const canDelete = canEdit const canPin = note.directTargetId != null && canEdit diff --git a/apps/web/components/notes/notes-tab.tsx b/apps/web/components/notes/notes-tab.tsx index 80106014..6563808c 100644 --- a/apps/web/components/notes/notes-tab.tsx +++ b/apps/web/components/notes/notes-tab.tsx @@ -198,7 +198,7 @@ export function NotesTab({ hostId, currentUserId, userRole }: Props) { {visibleNotes.map((note) => { const isAuthor = note.authorId === currentUserId - const isAdmin = userRole === 'super_admin' || userRole === 'org_admin' + const isAdmin = userRole === 'super_admin' || userRole === 'instance_admin' const canManage = !note.deletedAt && userRole !== 'read_only' && (isAuthor || isAdmin) diff --git a/apps/web/components/shared/tag-editor.tsx b/apps/web/components/shared/tag-editor.tsx index fcb1d81e..8614ec26 100644 --- a/apps/web/components/shared/tag-editor.tsx +++ b/apps/web/components/shared/tag-editor.tsx @@ -56,7 +56,7 @@ export function TagEditor({ const debKey = useDebounced(keyInput) const debValue = useDebounced(valueInput) - // Key suggestions: prefix match on any key in this org's tag catalogue. + // Key suggestions: prefix match on any key in this instance's tag catalogue. // Distinct keys (via de-dupe on the client) so typing "env" doesn't fan out // into one entry per value. const keyQuery = useQuery({ diff --git a/apps/web/lib/actions/action-auth.test.mjs b/apps/web/lib/actions/action-auth.test.mjs index b1c6183f..d971c5f4 100644 --- a/apps/web/lib/actions/action-auth.test.mjs +++ b/apps/web/lib/actions/action-auth.test.mjs @@ -5,39 +5,39 @@ import { assertInstanceAccess, assertInstanceAdminAccess, assertInstanceWriteAcc test('assertInstanceAccess rejects inactive users', () => { assert.throws( - () => assertInstanceAccess({ instanceId: 'org-1', isActive: false, deletedAt: null }, 'org-1'), + () => assertInstanceAccess({ instanceId: 'instance-1', isActive: false, deletedAt: null }, 'instance-1'), /forbidden: inactive user/, ) }) test('assertInstanceAccess rejects deleted users', () => { assert.throws( - () => assertInstanceAccess({ instanceId: 'org-1', isActive: true, deletedAt: new Date() }, 'org-1'), + () => assertInstanceAccess({ instanceId: 'instance-1', isActive: true, deletedAt: new Date() }, 'instance-1'), /forbidden: inactive user/, ) }) -test('assertInstanceAccess rejects cross-org access', () => { +test('assertInstanceAccess rejects cross-instance access', () => { assert.throws( - () => assertInstanceAccess({ instanceId: 'org-1', isActive: true, deletedAt: null }, 'org-2'), + () => assertInstanceAccess({ instanceId: 'instance-1', isActive: true, deletedAt: null }, 'instance-2'), /forbidden: instance mismatch/, ) }) test('assertInstanceAccess allows active users in their own instance', () => { assert.doesNotThrow( - () => assertInstanceAccess({ instanceId: 'org-1', isActive: true, deletedAt: null }, 'org-1'), + () => assertInstanceAccess({ instanceId: 'instance-1', isActive: true, deletedAt: null }, 'instance-1'), ) }) test('assertInstanceWriteAccess rejects read-only users in their own instance', () => { assert.throws( () => assertInstanceWriteAccess({ - instanceId: 'org-1', + instanceId: 'instance-1', isActive: true, deletedAt: null, role: 'read_only', - }, 'org-1'), + }, 'instance-1'), /forbidden: write role required/, ) }) @@ -45,33 +45,33 @@ test('assertInstanceWriteAccess rejects read-only users in their own instance', test('assertInstanceWriteAccess allows engineers in their own instance', () => { assert.doesNotThrow( () => assertInstanceWriteAccess({ - instanceId: 'org-1', + instanceId: 'instance-1', isActive: true, deletedAt: null, role: 'engineer', - }, 'org-1'), + }, 'instance-1'), ) }) test('assertInstanceAdminAccess rejects non-admin roles in the same instance', () => { assert.throws( () => assertInstanceAdminAccess({ - instanceId: 'org-1', + instanceId: 'instance-1', isActive: true, deletedAt: null, role: 'engineer', - }, 'org-1'), + }, 'instance-1'), /forbidden: admin role required/, ) }) -test('assertInstanceAdminAccess allows org admins in their own instance', () => { +test('assertInstanceAdminAccess allows instance admins in their own instance', () => { assert.doesNotThrow( () => assertInstanceAdminAccess({ - instanceId: 'org-1', + instanceId: 'instance-1', isActive: true, deletedAt: null, - role: 'org_admin', - }, 'org-1'), + role: 'instance_admin', + }, 'instance-1'), ) }) diff --git a/apps/web/lib/actions/agents-core.ts b/apps/web/lib/actions/agents-core.ts index 6de2d465..0557df02 100644 --- a/apps/web/lib/actions/agents-core.ts +++ b/apps/web/lib/actions/agents-core.ts @@ -43,9 +43,9 @@ import type { Agent, AgentEnrolmentToken, Host, HostDockerStatus, HostMetric } f import { HOST_HIGH_USAGE_THRESHOLD, HOST_STALE_MINUTES } from '@/lib/db/schema/hosts' import { applyGlobalDefaultsToHost } from '@/lib/actions/alerts' import { escapeLikePattern } from '@/lib/utils' -import { getOrgDefaultCollectionSettings } from '@/lib/actions/host-settings' +import { getInstanceDefaultCollectionSettings } from '@/lib/actions/host-settings' import { triggerAgentUninstall, getTaskRun } from '@/lib/actions/task-runs-core' -import { assignTagsToResource, getOrgDefaultTags, mergeTagLayers } from '@/lib/actions/tags' +import { assignTagsToResource, getInstanceDefaultTags, mergeTagLayers } from '@/lib/actions/tags' import { runMatchingTagRules } from '@/lib/actions/tag-rules' import type { HostMetadata, TagPair } from '@/lib/db/schema' import { parseHostMetadata } from '@/lib/db/schema/hosts' @@ -129,7 +129,7 @@ export async function approveAgent( if (agent.status !== 'pending') return { error: 'Agent is not in pending state' } // Guard against duplicate hosts: a pending agent that shares a hostname or - // any IP with a currently-online host in the same org must not be approved, + // any IP with a currently-online host in the same instance must not be approved, // because activating it would leave two live rows for the same physical // machine. The ingest register handler enforces the same rule at first // contact, but a collision can emerge later (e.g. another host with the @@ -192,15 +192,15 @@ export async function approveAgent( if (host) { await applyGlobalDefaultsToHost(instanceId, host.id) - // Apply org default collection settings + drain any pendingTags the + // Apply instance default collection settings + drain any pendingTags the // ingest handler stashed at register time. pendingTags already represent - // the (token → CLI) merge on the ingest side; here we layer org defaults + // the (token → CLI) merge on the ingest side; here we layer instance defaults // underneath (weakest), then run any saved tag_rules last. - const defaults = await getOrgDefaultCollectionSettings(instanceId) + const defaults = await getInstanceDefaultCollectionSettings(instanceId) const currentMetadata = parseHostMetadata(host.metadata) const pendingTags: TagPair[] = currentMetadata.pendingTags ?? [] - const orgDefaultTags = await getOrgDefaultTags(instanceId) - const finalTags = await mergeTagLayers(orgDefaultTags, pendingTags) + const instanceDefaultTags = await getInstanceDefaultTags(instanceId) + const finalTags = await mergeTagLayers(instanceDefaultTags, pendingTags) const nextMetadata: HostMetadata = { ...currentMetadata, @@ -234,7 +234,7 @@ export async function approveAgent( } } -// findOnlineHostCollision returns the first online host in the org (excluding +// findOnlineHostCollision returns the first online host in the instance (excluding // the one linked to excludeAgentId) whose hostname matches or whose // ip_addresses jsonb array overlaps any of the provided ips. Used by // approveAgent to block activation when doing so would produce a duplicate. @@ -589,7 +589,7 @@ export async function createEnrolmentToken( } // autoApprove bypasses the registration approval queue — restrict to super_admin to limit - // blast radius if an org_admin account is compromised (M-29). + // blast radius if an instance_admin account is compromised (M-29). if (parsed.data.autoApprove) { if (!hasRole(session.user, 'super_admin')) { return { error: 'Only super_admin users may create auto-approve enrolment tokens.' } diff --git a/apps/web/lib/actions/alerts-notification-redaction.test.mjs b/apps/web/lib/actions/alerts-notification-redaction.test.mjs index 8aa09106..ae83810e 100644 --- a/apps/web/lib/actions/alerts-notification-redaction.test.mjs +++ b/apps/web/lib/actions/alerts-notification-redaction.test.mjs @@ -26,7 +26,7 @@ test('getNotificationChannels redacts Slack webhook URLs from client payloads', assert.doesNotMatch(slackBranch, /config:\s*\{\s*webhookUrl:\s*cfg\.webhookUrl\s*\}/) }) -test('notification channel management actions require org admin access', () => { +test('notification channel management actions require instance admin access', () => { for (const action of [ 'createNotificationChannel', 'deleteNotificationChannel', @@ -38,7 +38,7 @@ test('notification channel management actions require org admin access', () => { assert.match( segment, /(?:const session = )?await requireInstanceAdminAccess\(instanceId\)/, - `${action} must require org admin access before managing notification channels`, + `${action} must require instance admin access before managing notification channels`, ) } }) diff --git a/apps/web/lib/actions/alerts.ts b/apps/web/lib/actions/alerts.ts index 97fdde4e..010deaac 100644 --- a/apps/web/lib/actions/alerts.ts +++ b/apps/web/lib/actions/alerts.ts @@ -863,11 +863,11 @@ export async function sendTestNotification( } } else if (existing.type === 'smtp') { const channelCfg = normaliseSmtpConfig(existing.config) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const relay = parseInstanceMetadata(org?.metadata).notificationSettings?.smtpRelay + const relay = parseInstanceMetadata(instance?.metadata).notificationSettings?.smtpRelay if (!relay?.enabled) return { error: 'Central SMTP relay is not enabled' } await assertPublicHost(relay.host) let password = '' @@ -1011,7 +1011,7 @@ export async function getActiveSilencesForHost( isNull(alertSilences.deletedAt), lte(alertSilences.startsAt, now), gte(alertSilences.endsAt, now), - // match host-specific silences for this host, or org-wide silences (hostId IS NULL) + // match host-specific silences for this host, or instance-wide silences (hostId IS NULL) // We use a raw SQL OR here via the Drizzle `or` helper imported above sql`(${alertSilences.hostId} = ${hostId} OR ${alertSilences.hostId} IS NULL)`, ), diff --git a/apps/web/lib/actions/calendar-source.test.mjs b/apps/web/lib/actions/calendar-source.test.mjs index 8d4b85f6..68f8f9e6 100644 --- a/apps/web/lib/actions/calendar-source.test.mjs +++ b/apps/web/lib/actions/calendar-source.test.mjs @@ -14,7 +14,7 @@ function getActionSegment(action) { return source.slice(start, next === -1 ? undefined : next) } -test('calendar mutations require write access but list requires org access', () => { +test('calendar mutations require write access but list requires instance access', () => { assert.match(getActionSegment('listCalendarEvents'), /await requireInstanceAccess\(instanceId\)/) for (const action of [ diff --git a/apps/web/lib/actions/calendar.ts b/apps/web/lib/actions/calendar.ts index a33a4905..3d99ace6 100644 --- a/apps/web/lib/actions/calendar.ts +++ b/apps/web/lib/actions/calendar.ts @@ -222,7 +222,7 @@ function uniqueValues(values: readonly string[]): string[] { return Array.from(new Set(values.filter((value) => value.trim().length > 0))) } -async function ensureHostsBelongToOrg(instanceId: string, hostIds: readonly string[]): Promise<{ ok: true; hostIds: string[] } | { error: string }> { +async function ensureHostsBelongToInstance(instanceId: string, hostIds: readonly string[]): Promise<{ ok: true; hostIds: string[] } | { error: string }> { const uniqueHostIds = uniqueValues(hostIds) if (uniqueHostIds.length === 0) return { ok: true, hostIds: [] } @@ -236,7 +236,7 @@ async function ensureHostsBelongToOrg(instanceId: string, hostIds: readonly stri return { ok: true, hostIds: uniqueHostIds } } -async function ensureUsersBelongToOrg( +async function ensureUsersBelongToInstance( instanceId: string, participants: readonly z.infer[], ): Promise<{ ok: true; participants: Array<{ userId: string; role: CalendarParticipantRole }> } | { error: string }> { @@ -663,9 +663,9 @@ export async function createCalendarEvent( const parsed = parseCalendarInput(input) if ('error' in parsed) return parsed - const hostCheck = await ensureHostsBelongToOrg(instanceId, parsed.data.hostIds) + const hostCheck = await ensureHostsBelongToInstance(instanceId, parsed.data.hostIds) if ('error' in hostCheck) return hostCheck - const participantCheck = await ensureUsersBelongToOrg(instanceId, parsed.data.participants) + const participantCheck = await ensureUsersBelongToInstance(instanceId, parsed.data.participants) if ('error' in participantCheck) return participantCheck try { @@ -742,9 +742,9 @@ export async function updateCalendarEvent( const parsed = parseCalendarInput(input) if ('error' in parsed) return parsed - const hostCheck = await ensureHostsBelongToOrg(instanceId, parsed.data.hostIds) + const hostCheck = await ensureHostsBelongToInstance(instanceId, parsed.data.hostIds) if ('error' in hostCheck) return hostCheck - const participantCheck = await ensureUsersBelongToOrg(instanceId, parsed.data.participants) + const participantCheck = await ensureUsersBelongToInstance(instanceId, parsed.data.participants) if ('error' in participantCheck) return participantCheck const existing = await db.query.calendarEvents.findFirst({ diff --git a/apps/web/lib/actions/dashboard-standalone.test.mjs b/apps/web/lib/actions/dashboard-standalone.test.mjs index d41502fc..b725eee1 100644 --- a/apps/web/lib/actions/dashboard-standalone.test.mjs +++ b/apps/web/lib/actions/dashboard-standalone.test.mjs @@ -133,7 +133,7 @@ const administrationPages = [ 'app/(dashboard)/settings/system/page.tsx', ] -test('dashboard shell falls back to community licence without org-backed scope', () => { +test('dashboard shell falls back to community licence without instance-backed scope', () => { assert.match( dashboardLayoutSource, /getInstanceEffectiveLicence\(session\.user\.instanceId\)/, @@ -158,8 +158,8 @@ test('dashboard overview uses the instance display name for page context', () => test('licence activation panel shows instance purchase context', () => { assert.match(settingsClientSource, /data-testid="licence-instance-name"/) assert.match(settingsClientSource, /data-testid="licence-instance-id"/) - assert.match(settingsClientSource, /\{org\.name\}/) - assert.match(settingsClientSource, /\{org\.id\}/) + assert.match(settingsClientSource, /\{instance\.name\}/) + assert.match(settingsClientSource, /\{instance\.id\}/) }) test('sidebar-linked pages do not force an instance-backed action scope', () => { @@ -178,11 +178,11 @@ test('sidebar-linked pages do not force an instance-backed action scope', () => } }) -test('standalone sidebar APIs use regular session auth and empty no-org fallbacks', () => { +test('standalone sidebar APIs use regular session auth and empty no-instance fallbacks', () => { for (const relativePath of standaloneApiRoutes) { const source = readFileSync(path.join(repoRoot, relativePath), 'utf8') - assert.match(source, /getApiSession\(/, `${relativePath} should allow no-org sessions`) - assert.doesNotMatch(source, /getApiOrg(Admin)?Session\(/) + assert.match(source, /getApiSession\(/, `${relativePath} should allow no-instance sessions`) + assert.doesNotMatch(source, /getApiInstance(Admin)?Session\(/) } }) @@ -253,10 +253,10 @@ test('role assignment can claim pending users that signed up outside the instanc test('administration pages use normalized role checks', () => { for (const relativePath of administrationPages) { const source = readFileSync(path.join(repoRoot, relativePath), 'utf8') - assert.match(source, /hasRole\(session\.user, \['org_admin', 'super_admin'\]\)/) + assert.match(source, /hasRole\(session\.user, \['instance_admin', 'super_admin'\]\)/) assert.doesNotMatch( source, - /ADMIN_ROLES\.includes\(session\.user\.role\)|session\.user\.role === 'super_admin'|\['org_admin', 'super_admin'\]\.includes\(session\.user\.role\)/, + /ADMIN_ROLES\.includes\(session\.user\.role\)|session\.user\.role === 'super_admin'|\['instance_admin', 'super_admin'\]\.includes\(session\.user\.role\)/, `${relativePath} should not depend on the primary legacy role string`, ) } diff --git a/apps/web/lib/actions/domain-accounts-core.ts b/apps/web/lib/actions/domain-accounts-core.ts index 630efa95..78c9a784 100644 --- a/apps/web/lib/actions/domain-accounts-core.ts +++ b/apps/web/lib/actions/domain-accounts-core.ts @@ -164,7 +164,7 @@ export async function createDomainAccount( return { success: true, id: row.id } } catch (err) { const message = err instanceof Error ? err.message : '' - if (message.includes('domain_accounts_org_username_idx')) { + if (message.includes('domain_accounts_instance_username_idx')) { return { error: 'An account with this username already exists' } } logError('Failed to create service account:', err) diff --git a/apps/web/lib/actions/host-docker-retention.test.mjs b/apps/web/lib/actions/host-docker-retention.test.mjs index 58a7cfd5..796f94a3 100644 --- a/apps/web/lib/actions/host-docker-retention.test.mjs +++ b/apps/web/lib/actions/host-docker-retention.test.mjs @@ -18,7 +18,7 @@ test('host Docker retention reads inherited and effective retention values', () const segment = getActionSegment('getHostDockerRetentionSettings') assert.match(segment, /parseHostMetadata\(host\?\.metadata\)/) - assert.match(segment, /org\?\.dockerMetricRetentionDays \?\? 30/) + assert.match(segment, /instance\?\.dockerMetricRetentionDays \?\? 30/) assert.match(segment, /retentionDaysOverride \?\? globalRetentionDays/) }) diff --git a/apps/web/lib/actions/host-settings.ts b/apps/web/lib/actions/host-settings.ts index 6dc3a415..ec56f4d3 100644 --- a/apps/web/lib/actions/host-settings.ts +++ b/apps/web/lib/actions/host-settings.ts @@ -42,8 +42,8 @@ export async function getHostCollectionSettings( return metadata.collectionSettings } - // Fall back to org defaults - return getOrgDefaultCollectionSettings(currentScope) + // Fall back to instance defaults + return getInstanceDefaultCollectionSettings(currentScope) } export async function updateHostCollectionSettings( @@ -89,7 +89,7 @@ export async function getHostDockerRetentionSettings( args.length === 2 ? args : [resolveCurrentActionScope(session), args[0]] await requireInstanceAccess(currentScope) - const [host, org] = await Promise.all([ + const [host, instance] = await Promise.all([ db.query.hosts.findFirst({ where: and(eq(hosts.id, hostId), eq(hosts.instanceId, currentScope), isNull(hosts.deletedAt)), columns: { metadata: true }, @@ -101,7 +101,7 @@ export async function getHostDockerRetentionSettings( ]) const metadata = parseHostMetadata(host?.metadata) - const globalRetentionDays = org?.dockerMetricRetentionDays ?? 30 + const globalRetentionDays = instance?.dockerMetricRetentionDays ?? 30 const retentionDaysOverride = metadata.dockerSettings?.retentionDaysOverride ?? null return { @@ -152,16 +152,16 @@ export async function updateHostDockerRetentionOverride( } } -export async function getOrgDefaultCollectionSettings( +export async function getInstanceDefaultCollectionSettings( instanceId: string, ): Promise { await requireInstanceAccess(instanceId) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const meta = parseInstanceMetadata(org?.metadata) + const meta = parseInstanceMetadata(instance?.metadata) if (meta?.defaultCollectionSettings) { return meta.defaultCollectionSettings } @@ -169,19 +169,19 @@ export async function getOrgDefaultCollectionSettings( return { ...DEFAULT_COLLECTION_SETTINGS } } -export async function updateOrgDefaultCollectionSettings( +export async function updateInstanceDefaultCollectionSettings( instanceId: string, settings: HostCollectionSettings, ): Promise<{ success: true } | { error: string }> { await requireInstanceAdminAccess(instanceId) try { - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { id: true, metadata: true }, }) - if (!org) return { error: 'Instance not found' } + if (!instance) return { error: 'Instance not found' } - const currentMetadata = parseInstanceMetadata(org.metadata) + const currentMetadata = parseInstanceMetadata(instance.metadata) const updatedMetadata = { ...currentMetadata, defaultCollectionSettings: settings, @@ -194,7 +194,7 @@ export async function updateOrgDefaultCollectionSettings( return { success: true } } catch (err) { - logError('Failed to update org default collection settings:', err) + logError('Failed to update instance default collection settings:', err) return { error: 'An unexpected error occurred' } } } diff --git a/apps/web/lib/actions/licence-guard.ts b/apps/web/lib/actions/licence-guard.ts index 7d99a14e..79674a97 100644 --- a/apps/web/lib/actions/licence-guard.ts +++ b/apps/web/lib/actions/licence-guard.ts @@ -32,7 +32,7 @@ export type EffectiveLicence = { // Cached per-request so multiple requireFeature() calls in one server action // don't re-validate the JWT repeatedly. const loadEffectiveLicence = cache(async (instanceId: string): Promise => { - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ columns: { licenceTier: true, licenceKey: true, @@ -41,16 +41,16 @@ const loadEffectiveLicence = cache(async (instanceId: string): Promise { +test('instance state mutations require write-capable instance access', () => { for (const [fileName, action] of writeGuardedActions) { const segment = getActionSegment(fileName, action) @@ -77,14 +77,14 @@ test('instance state mutations require write-capable org access', () => { } }) -test('instance-wide defaults and rules require org admin access', () => { +test('instance-wide defaults and rules require instance admin access', () => { for (const [fileName, action] of adminGuardedActions) { const segment = getActionSegment(fileName, action) assert.match( segment, /(?:const session = )?await requireInstanceAdminAccess\((?:instanceId|currentScope)\)/, - `${fileName} ${action} must require org admin access before mutating instance-wide settings`, + `${fileName} ${action} must require instance admin access before mutating instance-wide settings`, ) } }) diff --git a/apps/web/lib/actions/notes-resolver.ts b/apps/web/lib/actions/notes-resolver.ts index fc8daf8c..fc40126f 100644 --- a/apps/web/lib/actions/notes-resolver.ts +++ b/apps/web/lib/actions/notes-resolver.ts @@ -31,7 +31,7 @@ export type ResolvedNote = Omit & { // Resolves every visible, non-deleted note that applies to a host via any of // the three targeting modes. Runs as a single CTE query so N-per-host stays -// under a few ms even with tens of thousands of notes per org. Materialising +// under a few ms even with tens of thousands of notes per instance. Materialising // the result into a cache table is deferred — the CTE is small enough at v1 // scale and avoids a new invalidation surface. export async function resolveNotesForHost( diff --git a/apps/web/lib/actions/notification-settings.ts b/apps/web/lib/actions/notification-settings.ts index ea77e87f..62fbacb8 100644 --- a/apps/web/lib/actions/notification-settings.ts +++ b/apps/web/lib/actions/notification-settings.ts @@ -42,7 +42,7 @@ export interface InstanceNotificationSettingsFull { allowUserOptOut: boolean } -const updateOrgSmtpRelaySettingsSchema = z.object({ +const updateInstanceSmtpRelaySettingsSchema = z.object({ enabled: z.boolean(), host: z.string().min(1, 'Host is required'), port: z @@ -58,7 +58,7 @@ const updateOrgSmtpRelaySettingsSchema = z.object({ fromName: z.string().optional(), }) -export type InstanceSmtpRelaySettingsInput = z.infer +export type InstanceSmtpRelaySettingsInput = z.infer export interface SmtpRelayTestLogEntry { level: 'info' | 'success' | 'error' @@ -69,11 +69,11 @@ export async function getInstanceNotificationSettings( instanceId: string, ): Promise { await requireInstanceAccess(instanceId) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const meta = parseInstanceMetadata(org?.metadata) + const meta = parseInstanceMetadata(instance?.metadata) const ns = meta.notificationSettings ?? {} return { inAppEnabled: ns.inAppEnabled !== false, @@ -99,13 +99,13 @@ export async function updateInstanceNotificationSettings( const { inAppEnabled, inAppRoles, allowUserOptOut } = parsed.data - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - if (!org) return { error: 'Instance not found' } + if (!instance) return { error: 'Instance not found' } - const currentMetadata = parseInstanceMetadata(org.metadata) + const currentMetadata = parseInstanceMetadata(instance.metadata) const updatedMetadata = { ...currentMetadata, notificationSettings: { @@ -126,15 +126,15 @@ export async function updateInstanceNotificationSettings( } } -export async function getOrgSmtpRelaySettings( +export async function getInstanceSmtpRelaySettings( instanceId: string, ): Promise { await requireInstanceAccess(instanceId) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const meta = parseInstanceMetadata(org?.metadata) + const meta = parseInstanceMetadata(instance?.metadata) return sanitiseSmtpRelayForClient(meta.notificationSettings?.smtpRelay) } @@ -148,23 +148,23 @@ async function getAdminInstanceMetadata(instanceId: string): Promise< return { error: 'You do not have permission to update SMTP settings' } } - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - if (!org) return { error: 'Instance not found' } + if (!instance) return { error: 'Instance not found' } - return { metadata: parseInstanceMetadata(org.metadata) } + return { metadata: parseInstanceMetadata(instance.metadata) } } -export async function updateOrgSmtpRelaySettings( +export async function updateInstanceSmtpRelaySettings( instanceId: string, input: unknown, ): Promise<{ success: true } | { error: string }> { const access = await getAdminInstanceMetadata(instanceId) if ('error' in access) return { error: access.error ?? 'Unable to load SMTP settings' } - const parsed = updateOrgSmtpRelaySettingsSchema.safeParse(input) + const parsed = updateInstanceSmtpRelaySettingsSchema.safeParse(input) if (!parsed.success) { return { error: parsed.error.issues[0]?.message ?? 'Invalid input' } } diff --git a/apps/web/lib/actions/profile.ts b/apps/web/lib/actions/profile.ts index c8c978d5..f5f2e594 100644 --- a/apps/web/lib/actions/profile.ts +++ b/apps/web/lib/actions/profile.ts @@ -108,12 +108,12 @@ export async function updateNotificationPreference(enabled: boolean): Promise<{ return { error: 'Instance context is required' } } - // Check whether the org allows users to opt out - const org = await db.query.instanceSettings.findFirst({ + // Check whether the instance allows users to opt out. + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const meta = parseInstanceMetadata(org?.metadata) + const meta = parseInstanceMetadata(instance?.metadata) const allowOptOut = meta.notificationSettings?.allowUserOptOut !== false if (!allowOptOut && !enabled) { diff --git a/apps/web/lib/actions/seat-enforcement.ts b/apps/web/lib/actions/seat-enforcement.ts index 638df6ac..e9066721 100644 --- a/apps/web/lib/actions/seat-enforcement.ts +++ b/apps/web/lib/actions/seat-enforcement.ts @@ -18,7 +18,7 @@ export class SeatLimitError extends Error { } } -export async function getOrgSeatUsage(instanceId: string, options: SeatUsageOptions = {}): Promise { +export async function getInstanceSeatUsage(instanceId: string, options: SeatUsageOptions = {}): Promise { const licence = await getTrustedEffectiveLicence(instanceId) const now = new Date() @@ -56,7 +56,7 @@ export async function assertCanReserveUserSeat( instanceId: string, options: SeatUsageOptions = {}, ): Promise { - const usage = await getOrgSeatUsage(instanceId, options) + const usage = await getInstanceSeatUsage(instanceId, options) if (!canReserveSeats(usage, 1)) { throw new SeatLimitError(usage) } diff --git a/apps/web/lib/actions/security-auth.test.mjs b/apps/web/lib/actions/security-auth.test.mjs index 681e7aa8..555ca2c4 100644 --- a/apps/web/lib/actions/security-auth.test.mjs +++ b/apps/web/lib/actions/security-auth.test.mjs @@ -10,11 +10,11 @@ const here = path.dirname(fileURLToPath(import.meta.url)) const securitySource = readFileSync(path.join(here, 'security.ts'), 'utf8') const baseUser = { - role: 'org_admin', - roles: ['org_admin'], + role: 'instance_admin', + roles: ['instance_admin'], } -test('Agent CA management rejects org admins', () => { +test('Agent CA management rejects instance admins', () => { assert.throws( () => assertAgentCAManagementAccess(baseUser), /forbidden: super_admin role required/, diff --git a/apps/web/lib/actions/security.ts b/apps/web/lib/actions/security.ts index f7e2c09e..49971a57 100644 --- a/apps/web/lib/actions/security.ts +++ b/apps/web/lib/actions/security.ts @@ -36,12 +36,12 @@ export async function getSecurityOverview(): Promise { @@ -39,7 +39,7 @@ export async function updateOrgName( return { error: 'You do not have permission to perform this action' } } - const parsed = updateOrgNameSchema.safeParse({ name }) + const parsed = updateInstanceNameSchema.safeParse({ name }) if (!parsed.success) { return { error: parsed.error.issues[0]?.message ?? 'Invalid name' } } @@ -52,7 +52,7 @@ export async function updateOrgName( return { success: true } } catch (err) { - logError('Failed to update org name:', err) + logError('Failed to update instance name:', err) return { error: 'An unexpected error occurred' } } } @@ -220,17 +220,17 @@ export async function generateActivationToken( } try { - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { id: true, name: true }, }) - if (!org) { + if (!instance) { return { error: 'Instance not found' } } const token = encodeActivationToken({ - installOrgId: org.id, - installOrgName: org.name, + installInstanceId: instance.id, + installInstanceName: instance.name, nonce: createId(), }) return { success: true, token } @@ -266,7 +266,7 @@ export async function updateFreeSeatUsers( } try { - const [org, activeUsers] = await Promise.all([ + const [instance, activeUsers] = await Promise.all([ db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, @@ -284,7 +284,7 @@ export async function updateFreeSeatUsers( }), ]) - if (!org) { + if (!instance) { return { error: 'Instance not found' } } @@ -295,7 +295,7 @@ export async function updateFreeSeatUsers( } const metadata = { - ...parseInstanceMetadata(org.metadata), + ...parseInstanceMetadata(instance.metadata), freeSeatUserIds: uniqueUserIds, } diff --git a/apps/web/lib/actions/software-inventory-core.ts b/apps/web/lib/actions/software-inventory-core.ts index 02e28886..0b917534 100644 --- a/apps/web/lib/actions/software-inventory-core.ts +++ b/apps/web/lib/actions/software-inventory-core.ts @@ -52,11 +52,11 @@ export async function getSoftwareInventorySettings( instanceId: string, ): Promise { await requireInstanceAccess(instanceId) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: and(eq(instanceSettings.id, instanceId), isNull(instanceSettings.deletedAt)), columns: { metadata: true }, }) - const metadata = parseInstanceMetadata(org?.metadata) + const metadata = parseInstanceMetadata(instance?.metadata) return metadata.softwareInventorySettings ?? { enabled: false, intervalHours: 24, @@ -79,11 +79,11 @@ export async function updateSoftwareInventorySettings( } try { - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const currentMetadata = parseInstanceMetadata(org?.metadata) + const currentMetadata = parseInstanceMetadata(instance?.metadata) await db .update(instanceSettings) .set({ diff --git a/apps/web/lib/actions/tags-core.ts b/apps/web/lib/actions/tags-core.ts index 20e44286..0a1ea5e1 100644 --- a/apps/web/lib/actions/tags-core.ts +++ b/apps/web/lib/actions/tags-core.ts @@ -33,7 +33,7 @@ function dedupeByKey(pairs: TagPair[]): TagPair[] { } // Merges layers of {key,value} pairs with last-wins on conflicting keys. The -// caller passes layers from weakest to strongest (e.g. org defaults, token +// caller passes layers from weakest to strongest (e.g. instance defaults, token // tags, CLI tags) so the final set reflects the operator's most-specific // intent. export async function mergeTagLayers(...layers: TagPair[][]): Promise { @@ -285,17 +285,17 @@ export async function listResourceTags( return rows } -export async function getOrgDefaultTags(instanceId: string): Promise { +export async function getInstanceDefaultTags(instanceId: string): Promise { await requireInstanceAccess(instanceId) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const meta = parseInstanceMetadata(org?.metadata) + const meta = parseInstanceMetadata(instance?.metadata) return meta?.defaultTags ?? [] } -export async function updateOrgDefaultTags( +export async function updateInstanceDefaultTags( instanceId: string, pairs: TagPair[], ): Promise<{ success: true } | { error: string }> { @@ -305,13 +305,13 @@ export async function updateOrgDefaultTags( if (!parsed.success) return { error: parsed.error.issues[0]?.message ?? 'Invalid tags' } const deduped = dedupeByKey(parsed.data) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { id: true, metadata: true }, }) - if (!org) return { error: 'Instance not found' } + if (!instance) return { error: 'Instance not found' } - const currentMetadata = parseInstanceMetadata(org.metadata) + const currentMetadata = parseInstanceMetadata(instance.metadata) const updatedMetadata: InstanceMetadata = { ...currentMetadata, defaultTags: deduped, @@ -323,7 +323,7 @@ export async function updateOrgDefaultTags( return { success: true } } catch (err) { - logError('Failed to update org default tags:', err) + logError('Failed to update instance default tags:', err) return { error: 'An unexpected error occurred' } } } diff --git a/apps/web/lib/actions/tags.ts b/apps/web/lib/actions/tags.ts index f7286e4e..8c48f27c 100644 --- a/apps/web/lib/actions/tags.ts +++ b/apps/web/lib/actions/tags.ts @@ -4,13 +4,13 @@ import { getRequiredSession } from '@/lib/auth/session' import { resolveCurrentActionScope } from './action-scope' import { assignTagsToResource as assignTagsToResourceCore, - getOrgDefaultTags as getOrgDefaultTagsCore, + getInstanceDefaultTags as getInstanceDefaultTagsCore, listResourceTags as listResourceTagsCore, mergeTagLayers as mergeTagLayersCore, removeTagFromResource as removeTagFromResourceCore, replaceResourceTags as replaceResourceTagsCore, searchTags as searchTagsCore, - updateOrgDefaultTags as updateOrgDefaultTagsCore, + updateInstanceDefaultTags as updateInstanceDefaultTagsCore, type TagAssignment, } from './tags-core' @@ -62,13 +62,13 @@ export async function listResourceTags( return listResourceTagsCore(currentScope, resourceType, resourceId) } -export async function getOrgDefaultTags(scopeId: string): Promise { - return getOrgDefaultTagsCore(scopeId) +export async function getInstanceDefaultTags(scopeId: string): Promise { + return getInstanceDefaultTagsCore(scopeId) } -export async function updateOrgDefaultTags( +export async function updateInstanceDefaultTags( scopeId: string, pairs: import('@/lib/db/schema').TagPair[], ): Promise<{ success: true } | { error: string }> { - return updateOrgDefaultTagsCore(scopeId, pairs) + return updateInstanceDefaultTagsCore(scopeId, pairs) } diff --git a/apps/web/lib/actions/task-runs-authz.test.mjs b/apps/web/lib/actions/task-runs-authz.test.mjs index e5dd752e..da83d22c 100644 --- a/apps/web/lib/actions/task-runs-authz.test.mjs +++ b/apps/web/lib/actions/task-runs-authz.test.mjs @@ -27,7 +27,7 @@ const privilegedTaskRunActions = [ 'triggerGroupPatchRun', ] -test('privileged host task actions require org admin access and derive actors from session', () => { +test('privileged host task actions require instance admin access and derive actors from session', () => { for (const action of privilegedTaskRunActions) { const segment = getActionSegment(taskRunsSource, action) @@ -39,7 +39,7 @@ test('privileged host task actions require org admin access and derive actors fr assert.match( segment, /const session = await requireInstanceAdminAccess\(currentScope\)/, - `${action} must capture an org-admin-authorized session`, + `${action} must capture an instance-admin-authorized session`, ) assert.match( segment, @@ -49,14 +49,14 @@ test('privileged host task actions require org admin access and derive actors fr } }) -test('privileged task schedule mutations require org admin access', () => { +test('privileged task schedule mutations require instance admin access', () => { for (const action of ['createSchedule', 'updateSchedule', 'runScheduleNow']) { const segment = getActionSegment(taskSchedulesSource, action) assert.match( segment, /(?:const session = )?await requireInstanceAdminAccess\(currentScope\)/, - `${action} must require org admin access before mutating privileged schedules`, + `${action} must require instance admin access before mutating privileged schedules`, ) } }) diff --git a/apps/web/lib/actions/terminal-core.ts b/apps/web/lib/actions/terminal-core.ts index 06f458f4..7b71c0fa 100644 --- a/apps/web/lib/actions/terminal-core.ts +++ b/apps/web/lib/actions/terminal-core.ts @@ -59,13 +59,13 @@ export async function checkTerminalAccess( return { allowed: false, reason: 'Terminal access requires at minimum engineer role' } } - // 2. Org-level terminal enabled - const org = await db.query.instanceSettings.findFirst({ + // 2. Instance-level terminal enabled + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const orgMeta = parseInstanceMetadata(org?.metadata) - if (orgMeta.terminalEnabled === false) { + const instanceMeta = parseInstanceMetadata(instance?.metadata) + if (instanceMeta.terminalEnabled === false) { return { allowed: false, reason: 'Terminal access is disabled for this instance' } } @@ -181,23 +181,23 @@ export async function createTerminalSession( } } -// --- Org-level terminal settings --- +// --- Instance-level terminal settings --- -export interface OrgTerminalSettings { +export interface InstanceTerminalSettings { terminalEnabled: boolean terminalLoggingEnabled: boolean terminalDirectAccess: boolean } -export async function getOrgTerminalSettings( +export async function getInstanceTerminalSettings( instanceId: string, -): Promise { +): Promise { await requireInstanceAccess(instanceId) - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }) - const meta = parseInstanceMetadata(org?.metadata) + const meta = parseInstanceMetadata(instance?.metadata) return { terminalEnabled: meta.terminalEnabled !== false, terminalLoggingEnabled: meta.terminalLoggingEnabled === true, @@ -205,9 +205,9 @@ export async function getOrgTerminalSettings( } } -export async function updateOrgTerminalSettings( +export async function updateInstanceTerminalSettings( instanceId: string, - settings: OrgTerminalSettings, + settings: InstanceTerminalSettings, ): Promise<{ success: true } | { error: string }> { let session try { @@ -217,13 +217,13 @@ export async function updateOrgTerminalSettings( } try { - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { id: true, metadata: true }, }) - if (!org) return { error: 'Instance not found' } + if (!instance) return { error: 'Instance not found' } - const currentMetadata = parseInstanceMetadata(org.metadata) + const currentMetadata = parseInstanceMetadata(instance.metadata) const updatedMetadata = { ...currentMetadata, terminalEnabled: settings.terminalEnabled, @@ -255,7 +255,7 @@ export async function updateOrgTerminalSettings( return { success: true } } catch (err) { - logError('Failed to update org terminal settings:', err) + logError('Failed to update instance terminal settings:', err) return { error: 'An unexpected error occurred' } } } diff --git a/apps/web/lib/actions/terminal.ts b/apps/web/lib/actions/terminal.ts index d1086db0..9c3af57a 100644 --- a/apps/web/lib/actions/terminal.ts +++ b/apps/web/lib/actions/terminal.ts @@ -5,15 +5,15 @@ import { resolveCurrentActionScope } from './action-scope' import { checkTerminalAccess as checkTerminalAccessCore, createTerminalSession as createTerminalSessionCore, - getOrgTerminalSettings as getOrgTerminalSettingsCore, + getInstanceTerminalSettings as getInstanceTerminalSettingsCore, getHostTerminalSettings as getHostTerminalSettingsCore, type HostTerminalSettings, - type OrgTerminalSettings, + type InstanceTerminalSettings, type TerminalAccessDenied, type TerminalAccessResult, trustPendingSshHostKeys as trustPendingSshHostKeysCore, updateHostTerminalSettings as updateHostTerminalSettingsCore, - updateOrgTerminalSettings as updateOrgTerminalSettingsCore, + updateInstanceTerminalSettings as updateInstanceTerminalSettingsCore, } from './terminal-core' export async function checkTerminalAccess( @@ -46,17 +46,17 @@ export async function createTerminalSession( return createTerminalSessionCore(currentScope, hostId, username) } -export async function getOrgTerminalSettings( +export async function getInstanceTerminalSettings( scopeId: string, -): Promise { - return getOrgTerminalSettingsCore(scopeId) +): Promise { + return getInstanceTerminalSettingsCore(scopeId) } -export async function updateOrgTerminalSettings( +export async function updateInstanceTerminalSettings( scopeId: string, - settings: OrgTerminalSettings, + settings: InstanceTerminalSettings, ): Promise<{ success: true } | { error: string }> { - return updateOrgTerminalSettingsCore(scopeId, settings) + return updateInstanceTerminalSettingsCore(scopeId, settings) } export async function getHostTerminalSettings( diff --git a/apps/web/lib/actions/users.ts b/apps/web/lib/actions/users.ts index ef9be4aa..2b8543cf 100644 --- a/apps/web/lib/actions/users.ts +++ b/apps/web/lib/actions/users.ts @@ -50,7 +50,7 @@ async function claimDirectSignupUsers(instanceId: string): Promise { `) } -export async function getOrgUsers(): Promise<{ members: User[]; pendingInvites: Invitation[] }> { +export async function getInstanceUsers(): Promise<{ members: User[]; pendingInvites: Invitation[] }> { const session = await getRequiredSession() const currentScope = resolveOptionalActionScope(session) if (!currentScope) return { members: [session.user], pendingInvites: [] } @@ -234,11 +234,11 @@ export async function updateUserRole( } if (hasSuperAdminRole(targetUser.role, targetUser.roles) && !nextRoles.includes('super_admin')) { - const orgUsers = await db.query.users.findMany({ + const instanceUsers = await db.query.users.findMany({ where: and(eq(users.instanceId, instanceId), isNull(users.deletedAt)), columns: { id: true, role: true, roles: true }, }) - const superAdmins = orgUsers.filter((user) => hasSuperAdminRole(user.role, user.roles)) + const superAdmins = instanceUsers.filter((user) => hasSuperAdminRole(user.role, user.roles)) if (superAdmins.length === 1 && superAdmins[0]?.id === targetUserId) { return { error: 'Cannot demote the last super admin' } } diff --git a/apps/web/lib/agent/install-script.test.mjs b/apps/web/lib/agent/install-script.test.mjs index 4ab22f12..2cb2aa0f 100644 --- a/apps/web/lib/agent/install-script.test.mjs +++ b/apps/web/lib/agent/install-script.test.mjs @@ -20,7 +20,7 @@ test('buildAgentInstallCommand downloads the script without embedding secrets in assert.equal(command, 'curl -fsSLk "https://ct-ops.example.com/api/agent/install?skip_verify=true" | sh') assert.equal(command.includes('token='), false) - assert.equal(command.includes('CT_OPS_ORG_TOKEN='), false) + assert.equal(command.includes('CT_OPS_ENROLMENT_TOKEN='), false) assert.equal(command.includes('CT_OPS_ENROLMENT_TOKEN='), false) }) @@ -33,7 +33,7 @@ test('buildAgentInstallCommand can pass a newly-created token outside the URL', ) assert.equal(command.includes('token='), false) assert.match(command, /CT_OPS_ENROLMENT_TOKEN='ctops_test_token'/) - assert.equal(command.includes('CT_OPS_ORG_TOKEN='), false) + assert.equal(command.includes(['CT_OPS', 'ORG_TOKEN='].join('_')), false) }) test('buildAgentInstallCommand shell-quotes tokens', () => { @@ -48,8 +48,7 @@ test('buildAgentInstallScript consumes CT_OPS_ENROLMENT_TOKEN from the runtime e assert.match(script, /if \[ -n "\$\{CT_OPS_ENROLMENT_TOKEN:-\}" \]; then/) assert.match(script, /sudo env CT_OPS_ENROLMENT_TOKEN="\$CT_OPS_ENROLMENT_TOKEN" \.\/ct-ops-agent --install --address 'ct-ops\.example\.com:9443'/) - assert.match(script, /elif \[ -n "\$\{CT_OPS_ORG_TOKEN:-\}" \]; then/) - assert.match(script, /sudo env CT_OPS_ENROLMENT_TOKEN="\$CT_OPS_ORG_TOKEN" \.\/ct-ops-agent --install --address 'ct-ops\.example\.com:9443'/) + assert.equal(script.includes('elif [ -n'), false) assert.equal(script.includes('token='), false) }) diff --git a/apps/web/lib/agent/install-script.ts b/apps/web/lib/agent/install-script.ts index e4c183cb..c446ff08 100644 --- a/apps/web/lib/agent/install-script.ts +++ b/apps/web/lib/agent/install-script.ts @@ -131,9 +131,6 @@ echo "Binary downloaded." if [ -n "\${CT_OPS_ENROLMENT_TOKEN:-}" ]; then sudo env CT_OPS_ENROLMENT_TOKEN="\$CT_OPS_ENROLMENT_TOKEN" ./ct-ops-agent --install --address ${shellSingleQuote(safeIngestAddress)}${tlsFlag} exit 0 -elif [ -n "\${CT_OPS_ORG_TOKEN:-}" ]; then - sudo env CT_OPS_ENROLMENT_TOKEN="\$CT_OPS_ORG_TOKEN" ./ct-ops-agent --install --address ${shellSingleQuote(safeIngestAddress)}${tlsFlag} - exit 0 fi echo "" diff --git a/apps/web/lib/audit/events.test.mjs b/apps/web/lib/audit/events.test.mjs index 329e7be0..626269c0 100644 --- a/apps/web/lib/audit/events.test.mjs +++ b/apps/web/lib/audit/events.test.mjs @@ -25,7 +25,7 @@ test('serialiseAuditMetadata normalises dates, bigint values, and nested records test('buildAuditEventValues drops empty metadata and preserves nullable targets', () => { const values = buildAuditEventValues({ - instanceId: 'org-1', + instanceId: 'instance-1', actorUserId: 'user-1', action: 'licence.updated', targetType: 'instance', diff --git a/apps/web/lib/auth/email.test.mjs b/apps/web/lib/auth/email.test.mjs index 1df5521c..8b30162d 100644 --- a/apps/web/lib/auth/email.test.mjs +++ b/apps/web/lib/auth/email.test.mjs @@ -4,7 +4,7 @@ import assert from 'node:assert/strict' import { encrypt } from '../crypto/encrypt.ts' import { getAuthEmailConfigFromEnv, - getAuthEmailConfigFromOrgSettings, + getAuthEmailConfigFromInstanceSettings, } from './email.ts' test('getAuthEmailConfigFromEnv returns null without required SMTP env vars', () => { @@ -34,12 +34,12 @@ test('getAuthEmailConfigFromEnv maps AUTH_EMAIL_* vars into SMTP config', () => ) }) -test('getAuthEmailConfigFromOrgSettings uses the enabled central relay and decrypts its password', () => { +test('getAuthEmailConfigFromInstanceSettings uses the enabled central relay and decrypts its password', () => { process.env.BETTER_AUTH_SECRET = '01234567890123456789012345678901' const encryptedPassword = encrypt('relay-secret') assert.deepEqual( - getAuthEmailConfigFromOrgSettings({ + getAuthEmailConfigFromInstanceSettings({ smtpRelay: { enabled: true, host: 'relay.example.com', @@ -63,10 +63,10 @@ test('getAuthEmailConfigFromOrgSettings uses the enabled central relay and decry ) }) -test('getAuthEmailConfigFromOrgSettings ignores disabled or missing relays', () => { - assert.equal(getAuthEmailConfigFromOrgSettings(undefined), null) +test('getAuthEmailConfigFromInstanceSettings ignores disabled or missing relays', () => { + assert.equal(getAuthEmailConfigFromInstanceSettings(undefined), null) assert.equal( - getAuthEmailConfigFromOrgSettings({ + getAuthEmailConfigFromInstanceSettings({ smtpRelay: { enabled: false, host: 'relay.example.com', diff --git a/apps/web/lib/auth/email.ts b/apps/web/lib/auth/email.ts index 16301d62..05640992 100644 --- a/apps/web/lib/auth/email.ts +++ b/apps/web/lib/auth/email.ts @@ -37,7 +37,7 @@ export function getAuthEmailConfigFromEnv( } } -export function getAuthEmailConfigFromOrgSettings( +export function getAuthEmailConfigFromInstanceSettings( notificationSettings: InstanceNotificationSettings | undefined, ): SmtpSendConfig | null { const relay = notificationSettings?.smtpRelay diff --git a/apps/web/lib/auth/guards.test.mjs b/apps/web/lib/auth/guards.test.mjs index b422ca60..08406a38 100644 --- a/apps/web/lib/auth/guards.test.mjs +++ b/apps/web/lib/auth/guards.test.mjs @@ -11,9 +11,9 @@ import { } from './guards.ts' const activeAdmin = { - instanceId: 'org-1', - role: 'org_admin', - roles: ['org_admin'], + instanceId: 'instance-1', + role: 'instance_admin', + roles: ['instance_admin'], isActive: true, deletedAt: null, } @@ -30,38 +30,38 @@ test('requireActiveUser rejects inactive or deleted users', () => { }) test('requireSameInstance accepts either instance ids or instance-scoped resources', () => { - assert.doesNotThrow(() => requireSameInstance(activeAdmin, 'org-1')) - assert.doesNotThrow(() => requireSameInstance(activeAdmin, { instanceId: 'org-1' })) - assert.equal(isSameInstance(activeAdmin, { instanceId: 'org-2' }), false) + assert.doesNotThrow(() => requireSameInstance(activeAdmin, 'instance-1')) + assert.doesNotThrow(() => requireSameInstance(activeAdmin, { instanceId: 'instance-1' })) + assert.equal(isSameInstance(activeAdmin, { instanceId: 'instance-2' }), false) }) test('requireSameInstance rejects cross-instance access', () => { assert.throws( - () => requireSameInstance(activeAdmin, 'org-2'), + () => requireSameInstance(activeAdmin, 'instance-2'), /forbidden: instance mismatch/, ) }) test('requireInstanceAdmin rejects non-admin roles', () => { assert.throws( - () => requireInstanceAdmin({ ...activeAdmin, role: 'engineer', roles: ['engineer'] }, 'org-1'), + () => requireInstanceAdmin({ ...activeAdmin, role: 'engineer', roles: ['engineer'] }, 'instance-1'), /forbidden: admin role required/, ) }) test('requireInstanceWriteAccess rejects read-only users', () => { assert.throws( - () => requireInstanceWriteAccess({ ...activeAdmin, role: 'read_only', roles: ['read_only'] }, 'org-1'), + () => requireInstanceWriteAccess({ ...activeAdmin, role: 'read_only', roles: ['read_only'] }, 'instance-1'), /forbidden: write role required/, ) }) test('hasRole handles single and multiple role checks', () => { - assert.equal(hasRole(activeAdmin, 'org_admin'), true) + assert.equal(hasRole(activeAdmin, 'instance_admin'), true) assert.equal(hasRole(activeAdmin, ['super_admin', 'engineer']), false) assert.equal(hasRole({ ...activeAdmin, role: 'engineer', roles: ['engineer', 'read_only'] }, 'read_only'), true) assert.equal( - hasRole({ ...activeAdmin, role: 'engineer', roles: ['engineer', 'read_only'] }, ['super_admin', 'org_admin']), + hasRole({ ...activeAdmin, role: 'engineer', roles: ['engineer', 'read_only'] }, ['super_admin', 'instance_admin']), false, ) }) diff --git a/apps/web/lib/auth/index.ts b/apps/web/lib/auth/index.ts index 9ff7d924..d9a6314b 100644 --- a/apps/web/lib/auth/index.ts +++ b/apps/web/lib/auth/index.ts @@ -7,7 +7,7 @@ import { authDb, db } from '@/lib/db' import * as schema from '@/lib/db/schema' import { parseInstanceMetadata } from '@/lib/db/schema/instance-settings' import { - getAuthEmailConfigFromOrgSettings, + getAuthEmailConfigFromInstanceSettings, sendPasswordResetEmail, sendVerificationEmail, } from './email' @@ -44,7 +44,7 @@ async function getAuthEmailConfigForUser(userId: string) { if (!instance) return null const metadata = parseInstanceMetadata(instance.metadata) - return getAuthEmailConfigFromOrgSettings(metadata.notificationSettings) + return getAuthEmailConfigFromInstanceSettings(metadata.notificationSettings) } export const auth = betterAuth({ diff --git a/apps/web/lib/auth/roles.ts b/apps/web/lib/auth/roles.ts index 5d79f087..d73e761b 100644 --- a/apps/web/lib/auth/roles.ts +++ b/apps/web/lib/auth/roles.ts @@ -1,14 +1,14 @@ -export const ASSIGNED_ROLES = ['super_admin', 'org_admin', 'engineer', 'read_only'] as const -export const INVITABLE_ROLES = ['org_admin', 'engineer', 'read_only'] as const +export const ASSIGNED_ROLES = ['super_admin', 'instance_admin', 'engineer', 'read_only'] as const +export const INVITABLE_ROLES = ['instance_admin', 'engineer', 'read_only'] as const export type AssignedRole = (typeof ASSIGNED_ROLES)[number] export type LegacyRole = AssignedRole | 'pending' -export type AdminRole = 'org_admin' | 'super_admin' -export type MembershipRole = 'org_admin' | 'super_admin' | 'engineer' +export type AdminRole = 'instance_admin' | 'super_admin' +export type MembershipRole = 'instance_admin' | 'super_admin' | 'engineer' -export const ADMIN_ROLES: readonly string[] = ['org_admin', 'super_admin'] -export const MEMBERSHIP_ROLES: readonly string[] = ['org_admin', 'super_admin', 'engineer'] -export const DEFAULT_NOTIFICATION_ROLES: readonly string[] = ['super_admin', 'org_admin', 'engineer'] +export const ADMIN_ROLES: readonly string[] = ['instance_admin', 'super_admin'] +export const MEMBERSHIP_ROLES: readonly string[] = ['instance_admin', 'super_admin', 'engineer'] +export const DEFAULT_NOTIFICATION_ROLES: readonly string[] = ['super_admin', 'instance_admin', 'engineer'] export function isAssignedRole(value: string): value is AssignedRole { return ASSIGNED_ROLES.includes(value as AssignedRole) diff --git a/apps/web/lib/auth/tooling.test.mjs b/apps/web/lib/auth/tooling.test.mjs index 50073f79..111374e0 100644 --- a/apps/web/lib/auth/tooling.test.mjs +++ b/apps/web/lib/auth/tooling.test.mjs @@ -4,14 +4,14 @@ import assert from 'node:assert/strict' import { canAccessTooling, requireToolingAccess } from './tooling.ts' const baseUser = { - instanceId: 'org-1', + instanceId: 'instance-1', isActive: true, deletedAt: null, } test('canAccessTooling allows engineer and admin roles', () => { assert.equal(canAccessTooling({ ...baseUser, role: 'engineer', roles: ['engineer'] }), true) - assert.equal(canAccessTooling({ ...baseUser, role: 'org_admin', roles: ['org_admin'] }), true) + assert.equal(canAccessTooling({ ...baseUser, role: 'instance_admin', roles: ['instance_admin'] }), true) assert.equal(canAccessTooling({ ...baseUser, role: 'super_admin', roles: ['super_admin'] }), true) assert.equal(canAccessTooling({ ...baseUser, role: 'engineer', roles: ['read_only', 'engineer'] }), true) }) diff --git a/apps/web/lib/auth/two-factor-policy.test.mjs b/apps/web/lib/auth/two-factor-policy.test.mjs index 27b8b81b..3687b186 100644 --- a/apps/web/lib/auth/two-factor-policy.test.mjs +++ b/apps/web/lib/auth/two-factor-policy.test.mjs @@ -15,11 +15,11 @@ test('isTwoFactorRequired reads the instance security metadata flag', () => { }) test('two-factor policy only redirects unprotected users away from non-setup paths', () => { - const requiringOrg = { securitySettings: { requireTwoFactor: true } } + const requiringInstance = { securitySettings: { requireTwoFactor: true } } assert.equal( getTwoFactorPolicyRedirect({ - metadata: requiringOrg, + metadata: requiringInstance, userTwoFactorEnabled: false, pathname: '/hosts', }), @@ -28,7 +28,7 @@ test('two-factor policy only redirects unprotected users away from non-setup pat assert.equal( getTwoFactorPolicyRedirect({ - metadata: requiringOrg, + metadata: requiringInstance, userTwoFactorEnabled: false, pathname: '/profile', }), @@ -37,7 +37,7 @@ test('two-factor policy only redirects unprotected users away from non-setup pat assert.equal( getTwoFactorPolicyRedirect({ - metadata: requiringOrg, + metadata: requiringInstance, userTwoFactorEnabled: true, pathname: '/hosts', }), diff --git a/apps/web/lib/certificates/fetch.test.mjs b/apps/web/lib/certificates/fetch.test.mjs index 8e6e97a1..2f524db2 100644 --- a/apps/web/lib/certificates/fetch.test.mjs +++ b/apps/web/lib/certificates/fetch.test.mjs @@ -21,7 +21,7 @@ prompt = no [req_dn] CN = example.com -O = Example Org +O = Example Instance OU = Platform C = GB ST = London @@ -67,7 +67,7 @@ test('parseCertificateBuffer uses the X509 path and preserves key certificate me const parsed = parseCertificateBuffer(pem) assert.equal(parsed.commonName, 'example.com') - assert.equal(parsed.organization, 'Example Org') + assert.equal(parsed.organization, 'Example Instance') assert.equal(parsed.organizationalUnit, 'Platform') assert.equal(parsed.country, 'GB') assert.equal(parsed.state, 'London') diff --git a/apps/web/lib/db/migrations/0000_salty_edwin_jarvis.sql b/apps/web/lib/db/migrations/0000_salty_edwin_jarvis.sql index 9054619d..d6a552ce 100644 --- a/apps/web/lib/db/migrations/0000_salty_edwin_jarvis.sql +++ b/apps/web/lib/db/migrations/0000_salty_edwin_jarvis.sql @@ -1188,53 +1188,53 @@ ALTER TABLE "calendar_event_participants" ADD CONSTRAINT "calendar_event_partici ALTER TABLE "calendar_event_participants" ADD CONSTRAINT "calendar_event_participants_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "calendar_events" ADD CONSTRAINT "calendar_events_instance_id_instance_settings_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."instance_settings"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "calendar_events" ADD CONSTRAINT "calendar_events_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "users_org_active_deleted_name_email_idx" ON "user" USING btree ("instance_id","is_active","deleted_at","name","email");--> statement-breakpoint -CREATE UNIQUE INDEX "tags_org_key_value_ci_uidx" ON "tags" USING btree ("instance_id",lower("key"),lower("value"));--> statement-breakpoint -CREATE INDEX "tags_org_key_idx" ON "tags" USING btree ("instance_id","key");--> statement-breakpoint +CREATE INDEX "users_instance_active_deleted_name_email_idx" ON "user" USING btree ("instance_id","is_active","deleted_at","name","email");--> statement-breakpoint +CREATE UNIQUE INDEX "tags_instance_key_value_ci_uidx" ON "tags" USING btree ("instance_id",lower("key"),lower("value"));--> statement-breakpoint +CREATE INDEX "tags_instance_key_idx" ON "tags" USING btree ("instance_id","key");--> statement-breakpoint CREATE INDEX "resource_tags_resource_idx" ON "resource_tags" USING btree ("resource_id","resource_type");--> statement-breakpoint CREATE INDEX "resource_tags_tag_idx" ON "resource_tags" USING btree ("tag_id");--> statement-breakpoint CREATE UNIQUE INDEX "resource_tags_unique_uidx" ON "resource_tags" USING btree ("resource_id","resource_type","tag_id");--> statement-breakpoint -CREATE INDEX "host_metrics_org_host_time_idx" ON "host_metrics" USING btree ("instance_id","host_id","recorded_at");--> statement-breakpoint +CREATE INDEX "host_metrics_instance_host_time_idx" ON "host_metrics" USING btree ("instance_id","host_id","recorded_at");--> statement-breakpoint CREATE INDEX "check_results_check_idx" ON "check_results" USING btree ("check_id","ran_at");--> statement-breakpoint CREATE INDEX "check_results_instance_idx" ON "check_results" USING btree ("instance_id","ran_at");--> statement-breakpoint -CREATE INDEX "checks_org_host_idx" ON "checks" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "checks_instance_host_idx" ON "checks" USING btree ("instance_id","host_id");--> statement-breakpoint CREATE INDEX "agent_queries_host_status_idx" ON "agent_queries" USING btree ("host_id","status");--> statement-breakpoint CREATE INDEX "agent_queries_instance_idx" ON "agent_queries" USING btree ("instance_id","requested_at");--> statement-breakpoint -CREATE INDEX "alert_instances_org_status_idx" ON "alert_instances" USING btree ("instance_id","status");--> statement-breakpoint +CREATE INDEX "alert_instances_instance_status_idx" ON "alert_instances" USING btree ("instance_id","status");--> statement-breakpoint CREATE INDEX "alert_instances_rule_host_status_idx" ON "alert_instances" USING btree ("rule_id","host_id","status");--> statement-breakpoint -CREATE INDEX "alert_rules_org_host_idx" ON "alert_rules" USING btree ("instance_id","host_id");--> statement-breakpoint -CREATE INDEX "alert_rules_org_enabled_idx" ON "alert_rules" USING btree ("instance_id","enabled");--> statement-breakpoint -CREATE INDEX "alert_rules_org_global_idx" ON "alert_rules" USING btree ("instance_id","is_global_default");--> statement-breakpoint -CREATE INDEX "alert_silences_org_host_idx" ON "alert_silences" USING btree ("instance_id","host_id");--> statement-breakpoint -CREATE INDEX "alert_silences_org_active_idx" ON "alert_silences" USING btree ("instance_id","starts_at","ends_at");--> statement-breakpoint -CREATE INDEX "notification_channels_org_enabled_idx" ON "notification_channels" USING btree ("instance_id","enabled");--> statement-breakpoint +CREATE INDEX "alert_rules_instance_host_idx" ON "alert_rules" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "alert_rules_instance_enabled_idx" ON "alert_rules" USING btree ("instance_id","enabled");--> statement-breakpoint +CREATE INDEX "alert_rules_instance_global_idx" ON "alert_rules" USING btree ("instance_id","is_global_default");--> statement-breakpoint +CREATE INDEX "alert_silences_instance_host_idx" ON "alert_silences" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "alert_silences_instance_active_idx" ON "alert_silences" USING btree ("instance_id","starts_at","ends_at");--> statement-breakpoint +CREATE INDEX "notification_channels_instance_enabled_idx" ON "notification_channels" USING btree ("instance_id","enabled");--> statement-breakpoint CREATE INDEX "notifications_user_read_idx" ON "notifications" USING btree ("user_id","read");--> statement-breakpoint -CREATE INDEX "notifications_org_user_idx" ON "notifications" USING btree ("instance_id","user_id");--> statement-breakpoint +CREATE INDEX "notifications_instance_user_idx" ON "notifications" USING btree ("instance_id","user_id");--> statement-breakpoint CREATE INDEX "notifications_user_created_idx" ON "notifications" USING btree ("user_id","created_at");--> statement-breakpoint CREATE INDEX "notifications_deleted_at_idx" ON "notifications" USING btree ("deleted_at") WHERE "notifications"."deleted_at" IS NOT NULL;--> statement-breakpoint CREATE INDEX "cert_events_cert_time_idx" ON "certificate_events" USING btree ("certificate_id","occurred_at");--> statement-breakpoint -CREATE INDEX "cert_events_org_time_idx" ON "certificate_events" USING btree ("instance_id","occurred_at");--> statement-breakpoint +CREATE INDEX "cert_events_instance_time_idx" ON "certificate_events" USING btree ("instance_id","occurred_at");--> statement-breakpoint CREATE UNIQUE INDEX "certificates_identity_idx" ON "certificates" USING btree ("instance_id","host","port","server_name","fingerprint_sha256");--> statement-breakpoint -CREATE INDEX "certificates_org_expiry_idx" ON "certificates" USING btree ("instance_id","not_after");--> statement-breakpoint -CREATE INDEX "certificates_org_status_idx" ON "certificates" USING btree ("instance_id","status");--> statement-breakpoint -CREATE INDEX "certificates_org_host_idx" ON "certificates" USING btree ("instance_id","discovered_by_host_id");--> statement-breakpoint +CREATE INDEX "certificates_instance_expiry_idx" ON "certificates" USING btree ("instance_id","not_after");--> statement-breakpoint +CREATE INDEX "certificates_instance_status_idx" ON "certificates" USING btree ("instance_id","status");--> statement-breakpoint +CREATE INDEX "certificates_instance_host_idx" ON "certificates" USING btree ("instance_id","discovered_by_host_id");--> statement-breakpoint CREATE INDEX "certificates_refresh_due_idx" ON "certificates" USING btree ("tracked_url","last_refreshed_at");--> statement-breakpoint -CREATE INDEX "identity_events_org_time_idx" ON "identity_events" USING btree ("instance_id","occurred_at");--> statement-breakpoint +CREATE INDEX "identity_events_instance_time_idx" ON "identity_events" USING btree ("instance_id","occurred_at");--> statement-breakpoint CREATE INDEX "identity_events_account_time_idx" ON "identity_events" USING btree ("service_account_id","occurred_at");--> statement-breakpoint CREATE INDEX "identity_events_key_time_idx" ON "identity_events" USING btree ("ssh_key_id","occurred_at");--> statement-breakpoint CREATE INDEX "identity_events_host_time_idx" ON "identity_events" USING btree ("host_id","occurred_at");--> statement-breakpoint CREATE UNIQUE INDEX "service_accounts_identity_idx" ON "service_accounts" USING btree ("instance_id","host_id","username");--> statement-breakpoint -CREATE INDEX "service_accounts_org_type_idx" ON "service_accounts" USING btree ("instance_id","account_type");--> statement-breakpoint -CREATE INDEX "service_accounts_org_status_idx" ON "service_accounts" USING btree ("instance_id","status");--> statement-breakpoint -CREATE INDEX "service_accounts_org_host_idx" ON "service_accounts" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "service_accounts_instance_type_idx" ON "service_accounts" USING btree ("instance_id","account_type");--> statement-breakpoint +CREATE INDEX "service_accounts_instance_status_idx" ON "service_accounts" USING btree ("instance_id","status");--> statement-breakpoint +CREATE INDEX "service_accounts_instance_host_idx" ON "service_accounts" USING btree ("instance_id","host_id");--> statement-breakpoint CREATE UNIQUE INDEX "ssh_keys_identity_idx" ON "ssh_keys" USING btree ("instance_id","host_id","fingerprint_sha256","file_path");--> statement-breakpoint -CREATE INDEX "ssh_keys_org_fingerprint_idx" ON "ssh_keys" USING btree ("instance_id","fingerprint_sha256");--> statement-breakpoint -CREATE INDEX "ssh_keys_org_type_idx" ON "ssh_keys" USING btree ("instance_id","key_type");--> statement-breakpoint -CREATE INDEX "ssh_keys_org_status_idx" ON "ssh_keys" USING btree ("instance_id","status");--> statement-breakpoint -CREATE INDEX "ssh_keys_org_host_idx" ON "ssh_keys" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "ssh_keys_instance_fingerprint_idx" ON "ssh_keys" USING btree ("instance_id","fingerprint_sha256");--> statement-breakpoint +CREATE INDEX "ssh_keys_instance_type_idx" ON "ssh_keys" USING btree ("instance_id","key_type");--> statement-breakpoint +CREATE INDEX "ssh_keys_instance_status_idx" ON "ssh_keys" USING btree ("instance_id","status");--> statement-breakpoint +CREATE INDEX "ssh_keys_instance_host_idx" ON "ssh_keys" USING btree ("instance_id","host_id");--> statement-breakpoint CREATE INDEX "ssh_keys_account_idx" ON "ssh_keys" USING btree ("service_account_id");--> statement-breakpoint -CREATE UNIQUE INDEX "domain_accounts_org_username_idx" ON "domain_accounts" USING btree ("instance_id","username");--> statement-breakpoint -CREATE INDEX "domain_accounts_org_status_idx" ON "domain_accounts" USING btree ("instance_id","status");--> statement-breakpoint +CREATE UNIQUE INDEX "domain_accounts_instance_username_idx" ON "domain_accounts" USING btree ("instance_id","username");--> statement-breakpoint +CREATE INDEX "domain_accounts_instance_status_idx" ON "domain_accounts" USING btree ("instance_id","status");--> statement-breakpoint CREATE INDEX "task_run_hosts_run_idx" ON "task_run_hosts" USING btree ("task_run_id");--> statement-breakpoint CREATE INDEX "task_run_hosts_host_status_idx" ON "task_run_hosts" USING btree ("host_id","status");--> statement-breakpoint CREATE INDEX "task_runs_instance_idx" ON "task_runs" USING btree ("instance_id","created_at");--> statement-breakpoint @@ -1242,24 +1242,24 @@ CREATE INDEX "task_runs_target_idx" ON "task_runs" USING btree ("target_type","t CREATE INDEX "task_runs_scheduled_from_idx" ON "task_runs" USING btree ("scheduled_from_id");--> statement-breakpoint CREATE INDEX "task_schedules_instance_idx" ON "task_schedules" USING btree ("instance_id","created_at");--> statement-breakpoint CREATE INDEX "task_schedules_due_idx" ON "task_schedules" USING btree ("enabled","next_run_at");--> statement-breakpoint -CREATE INDEX "terminal_sessions_org_host_idx" ON "terminal_sessions" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "terminal_sessions_instance_host_idx" ON "terminal_sessions" USING btree ("instance_id","host_id");--> statement-breakpoint CREATE INDEX "terminal_sessions_session_id_idx" ON "terminal_sessions" USING btree ("session_id");--> statement-breakpoint CREATE INDEX "saved_sw_reports_user_idx" ON "saved_software_reports" USING btree ("user_id");--> statement-breakpoint CREATE UNIQUE INDEX "sw_pkg_uniq" ON "software_packages" USING btree ("instance_id","host_id","name","version","architecture");--> statement-breakpoint -CREATE INDEX "sw_pkg_org_name_idx" ON "software_packages" USING btree ("instance_id","name");--> statement-breakpoint +CREATE INDEX "sw_pkg_instance_name_idx" ON "software_packages" USING btree ("instance_id","name");--> statement-breakpoint CREATE INDEX "sw_pkg_source_name_idx" ON "software_packages" USING btree ("source","distro_id","distro_codename","source_name");--> statement-breakpoint CREATE INDEX "sw_pkg_host_idx" ON "software_packages" USING btree ("host_id");--> statement-breakpoint CREATE INDEX "sw_pkg_first_seen_idx" ON "software_packages" USING btree ("instance_id","first_seen_at");--> statement-breakpoint CREATE INDEX "sw_scan_host_idx" ON "software_scans" USING btree ("host_id","created_at");--> statement-breakpoint CREATE INDEX "sw_scan_instance_idx" ON "software_scans" USING btree ("instance_id","created_at");--> statement-breakpoint CREATE UNIQUE INDEX "host_package_updates_current_uniq" ON "host_package_updates" USING btree ("instance_id","host_id","name","current_version","available_version","architecture","package_manager");--> statement-breakpoint -CREATE INDEX "host_package_updates_org_status_idx" ON "host_package_updates" USING btree ("instance_id","status");--> statement-breakpoint +CREATE INDEX "host_package_updates_instance_status_idx" ON "host_package_updates" USING btree ("instance_id","status");--> statement-breakpoint CREATE INDEX "host_package_updates_host_status_idx" ON "host_package_updates" USING btree ("host_id","status");--> statement-breakpoint CREATE UNIQUE INDEX "host_patch_statuses_check_uniq" ON "host_patch_statuses" USING btree ("check_id");--> statement-breakpoint -CREATE INDEX "host_patch_statuses_org_status_idx" ON "host_patch_statuses" USING btree ("instance_id","status");--> statement-breakpoint +CREATE INDEX "host_patch_statuses_instance_status_idx" ON "host_patch_statuses" USING btree ("instance_id","status");--> statement-breakpoint CREATE INDEX "host_patch_statuses_host_checked_idx" ON "host_patch_statuses" USING btree ("host_id","checked_at");--> statement-breakpoint CREATE UNIQUE INDEX "host_vuln_findings_uniq" ON "host_vulnerability_findings" USING btree ("instance_id","host_id","software_package_id","cve_id");--> statement-breakpoint -CREATE INDEX "host_vuln_findings_org_status_idx" ON "host_vulnerability_findings" USING btree ("instance_id","status","severity");--> statement-breakpoint +CREATE INDEX "host_vuln_findings_instance_status_idx" ON "host_vulnerability_findings" USING btree ("instance_id","status","severity");--> statement-breakpoint CREATE INDEX "host_vuln_findings_host_status_idx" ON "host_vulnerability_findings" USING btree ("host_id","status");--> statement-breakpoint CREATE INDEX "host_vuln_findings_cve_idx" ON "host_vulnerability_findings" USING btree ("cve_id");--> statement-breakpoint CREATE INDEX "host_vuln_findings_confidence_idx" ON "host_vulnerability_findings" USING btree ("instance_id","status","confidence");--> statement-breakpoint @@ -1272,16 +1272,16 @@ CREATE INDEX "note_revisions_note_created_idx" ON "note_revisions" USING btree ( CREATE INDEX "note_targets_type_id_idx" ON "note_targets" USING btree ("target_type","target_id");--> statement-breakpoint CREATE INDEX "note_targets_note_idx" ON "note_targets" USING btree ("note_id");--> statement-breakpoint CREATE UNIQUE INDEX "note_targets_direct_unique_uidx" ON "note_targets" USING btree ("note_id","target_type","target_id") WHERE "note_targets"."target_id" IS NOT NULL;--> statement-breakpoint -CREATE INDEX "notes_org_active_updated_idx" ON "notes" USING btree ("instance_id","deleted_at","updated_at");--> statement-breakpoint +CREATE INDEX "notes_instance_active_updated_idx" ON "notes" USING btree ("instance_id","deleted_at","updated_at");--> statement-breakpoint CREATE INDEX "notes_author_idx" ON "notes" USING btree ("author_id");--> statement-breakpoint CREATE INDEX "notes_search_vector_idx" ON "notes" USING gin ("search_vector");--> statement-breakpoint CREATE INDEX "cert_authorities_purpose_idx" ON "certificate_authorities" USING btree ("purpose","deleted_at");--> statement-breakpoint CREATE INDEX "revoked_certs_instance_idx" ON "revoked_certificates" USING btree ("instance_id");--> statement-breakpoint CREATE INDEX "pending_cert_signings_requested_at_idx" ON "pending_cert_signings" USING btree ("requested_at");--> statement-breakpoint -CREATE INDEX "audit_events_org_time_idx" ON "audit_events" USING btree ("instance_id","created_at");--> statement-breakpoint +CREATE INDEX "audit_events_instance_time_idx" ON "audit_events" USING btree ("instance_id","created_at");--> statement-breakpoint CREATE INDEX "audit_events_actor_time_idx" ON "audit_events" USING btree ("actor_user_id","created_at");--> statement-breakpoint CREATE INDEX "audit_events_target_time_idx" ON "audit_events" USING btree ("target_type","target_id","created_at");--> statement-breakpoint -CREATE UNIQUE INDEX "build_doc_asset_storage_settings_org_uidx" ON "build_doc_asset_storage_settings" USING btree ("instance_id");--> statement-breakpoint +CREATE UNIQUE INDEX "build_doc_asset_storage_settings_instance_uidx" ON "build_doc_asset_storage_settings" USING btree ("instance_id");--> statement-breakpoint CREATE INDEX "build_doc_assets_doc_idx" ON "build_doc_assets" USING btree ("build_doc_id","deleted_at");--> statement-breakpoint CREATE INDEX "build_doc_assets_section_idx" ON "build_doc_assets" USING btree ("section_id");--> statement-breakpoint CREATE UNIQUE INDEX "build_doc_assets_storage_key_uidx" ON "build_doc_assets" USING btree ("provider","storage_key");--> statement-breakpoint @@ -1289,13 +1289,13 @@ CREATE INDEX "build_doc_revisions_doc_created_idx" ON "build_doc_revisions" USIN CREATE INDEX "build_doc_sections_doc_position_idx" ON "build_doc_sections" USING btree ("build_doc_id","position");--> statement-breakpoint CREATE INDEX "build_doc_sections_instance_idx" ON "build_doc_sections" USING btree ("instance_id","deleted_at");--> statement-breakpoint CREATE INDEX "build_doc_sections_search_vector_idx" ON "build_doc_sections" USING gin ("search_vector");--> statement-breakpoint -CREATE INDEX "build_doc_snippets_org_updated_idx" ON "build_doc_snippets" USING btree ("instance_id","deleted_at","updated_at");--> statement-breakpoint +CREATE INDEX "build_doc_snippets_instance_updated_idx" ON "build_doc_snippets" USING btree ("instance_id","deleted_at","updated_at");--> statement-breakpoint CREATE INDEX "build_doc_snippets_search_vector_idx" ON "build_doc_snippets" USING gin ("search_vector");--> statement-breakpoint CREATE UNIQUE INDEX "build_doc_template_versions_template_version_uidx" ON "build_doc_template_versions" USING btree ("template_id","version");--> statement-breakpoint CREATE INDEX "build_doc_template_versions_instance_idx" ON "build_doc_template_versions" USING btree ("instance_id","template_id");--> statement-breakpoint -CREATE INDEX "build_doc_templates_org_active_idx" ON "build_doc_templates" USING btree ("instance_id","deleted_at","is_active");--> statement-breakpoint +CREATE INDEX "build_doc_templates_instance_active_idx" ON "build_doc_templates" USING btree ("instance_id","deleted_at","is_active");--> statement-breakpoint CREATE UNIQUE INDEX "build_doc_templates_default_uidx" ON "build_doc_templates" USING btree ("instance_id","is_default") WHERE "build_doc_templates"."is_default" = TRUE AND "build_doc_templates"."deleted_at" IS NULL;--> statement-breakpoint -CREATE INDEX "build_docs_org_updated_idx" ON "build_docs" USING btree ("instance_id","deleted_at","updated_at");--> statement-breakpoint +CREATE INDEX "build_docs_instance_updated_idx" ON "build_docs" USING btree ("instance_id","deleted_at","updated_at");--> statement-breakpoint CREATE INDEX "build_docs_template_idx" ON "build_docs" USING btree ("template_version_id");--> statement-breakpoint CREATE INDEX "build_docs_status_idx" ON "build_docs" USING btree ("instance_id","status");--> statement-breakpoint CREATE INDEX "build_docs_search_vector_idx" ON "build_docs" USING gin ("search_vector");--> statement-breakpoint @@ -1303,9 +1303,9 @@ CREATE INDEX "ingest_server_snapshots_server_time_idx" ON "ingest_server_snapsho CREATE INDEX "ingest_server_snapshots_observed_idx" ON "ingest_server_snapshots" USING btree ("observed_at");--> statement-breakpoint CREATE INDEX "ct_cve_service_nonces_expires_at_idx" ON "ct_cve_service_nonces" USING btree ("expires_at");--> statement-breakpoint CREATE INDEX "ct_cve_connector_settings_enabled_idx" ON "ct_cve_connector_settings" USING btree ("enabled");--> statement-breakpoint -CREATE INDEX "calendar_event_hosts_org_host_idx" ON "calendar_event_hosts" USING btree ("instance_id","host_id");--> statement-breakpoint -CREATE INDEX "calendar_event_participants_org_user_idx" ON "calendar_event_participants" USING btree ("instance_id","user_id");--> statement-breakpoint -CREATE INDEX "calendar_events_org_range_idx" ON "calendar_events" USING btree ("instance_id","starts_at","ends_at");--> statement-breakpoint -CREATE INDEX "calendar_events_org_series_idx" ON "calendar_events" USING btree ("instance_id","series_id","recurrence_instance_start_at");--> statement-breakpoint -CREATE UNIQUE INDEX "calendar_events_org_client_request_idx" ON "calendar_events" USING btree ("instance_id","client_request_id");--> statement-breakpoint -CREATE UNIQUE INDEX "calendar_events_org_series_occurrence_idx" ON "calendar_events" USING btree ("instance_id","series_id","recurrence_instance_start_at") WHERE "calendar_events"."series_id" IS NOT NULL AND "calendar_events"."recurrence_instance_start_at" IS NOT NULL; \ No newline at end of file +CREATE INDEX "calendar_event_hosts_instance_host_idx" ON "calendar_event_hosts" USING btree ("instance_id","host_id");--> statement-breakpoint +CREATE INDEX "calendar_event_participants_instance_user_idx" ON "calendar_event_participants" USING btree ("instance_id","user_id");--> statement-breakpoint +CREATE INDEX "calendar_events_instance_range_idx" ON "calendar_events" USING btree ("instance_id","starts_at","ends_at");--> statement-breakpoint +CREATE INDEX "calendar_events_instance_series_idx" ON "calendar_events" USING btree ("instance_id","series_id","recurrence_instance_start_at");--> statement-breakpoint +CREATE UNIQUE INDEX "calendar_events_instance_client_request_idx" ON "calendar_events" USING btree ("instance_id","client_request_id");--> statement-breakpoint +CREATE UNIQUE INDEX "calendar_events_instance_series_occurrence_idx" ON "calendar_events" USING btree ("instance_id","series_id","recurrence_instance_start_at") WHERE "calendar_events"."series_id" IS NOT NULL AND "calendar_events"."recurrence_instance_start_at" IS NOT NULL; \ No newline at end of file diff --git a/apps/web/lib/db/migrations/0002_host_docker_status.sql b/apps/web/lib/db/migrations/0002_host_docker_status.sql index 9a6a2b46..2f682205 100644 --- a/apps/web/lib/db/migrations/0002_host_docker_status.sql +++ b/apps/web/lib/db/migrations/0002_host_docker_status.sql @@ -14,4 +14,4 @@ CREATE TABLE "host_docker_status" ( ALTER TABLE "host_docker_status" ADD CONSTRAINT "host_docker_status_instance_id_instance_settings_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."instance_settings"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "host_docker_status" ADD CONSTRAINT "host_docker_status_host_id_hosts_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."hosts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint CREATE UNIQUE INDEX "host_docker_status_host_uidx" ON "host_docker_status" USING btree ("host_id");--> statement-breakpoint -CREATE INDEX "host_docker_status_org_status_checked_idx" ON "host_docker_status" USING btree ("instance_id","status","checked_at"); +CREATE INDEX "host_docker_status_instance_status_checked_idx" ON "host_docker_status" USING btree ("instance_id","status","checked_at"); diff --git a/apps/web/lib/db/migrations/0003_docker_containers.sql b/apps/web/lib/db/migrations/0003_docker_containers.sql index 1bd916d6..21292eda 100644 --- a/apps/web/lib/db/migrations/0003_docker_containers.sql +++ b/apps/web/lib/db/migrations/0003_docker_containers.sql @@ -25,5 +25,5 @@ CREATE TABLE "docker_containers" ( ALTER TABLE "docker_containers" ADD CONSTRAINT "docker_containers_instance_id_instance_settings_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."instance_settings"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "docker_containers" ADD CONSTRAINT "docker_containers_host_id_hosts_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."hosts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint CREATE UNIQUE INDEX "docker_containers_host_container_uidx" ON "docker_containers" USING btree ("host_id","docker_container_id");--> statement-breakpoint -CREATE INDEX "docker_containers_org_host_present_seen_idx" ON "docker_containers" USING btree ("instance_id","host_id","is_present","last_seen_at");--> statement-breakpoint -CREATE INDEX "docker_containers_org_image_idx" ON "docker_containers" USING btree ("instance_id","image"); +CREATE INDEX "docker_containers_instance_host_present_seen_idx" ON "docker_containers" USING btree ("instance_id","host_id","is_present","last_seen_at");--> statement-breakpoint +CREATE INDEX "docker_containers_instance_image_idx" ON "docker_containers" USING btree ("instance_id","image"); diff --git a/apps/web/lib/db/migrations/0004_docker_container_metrics.sql b/apps/web/lib/db/migrations/0004_docker_container_metrics.sql index 9ee91c2e..72e6e057 100644 --- a/apps/web/lib/db/migrations/0004_docker_container_metrics.sql +++ b/apps/web/lib/db/migrations/0004_docker_container_metrics.sql @@ -35,7 +35,7 @@ ALTER TABLE "docker_container_metrics" ADD CONSTRAINT "docker_container_metrics_ ALTER TABLE "docker_container_metrics" ADD CONSTRAINT "docker_container_metrics_docker_container_row_id_docker_containers_id_fk" FOREIGN KEY ("docker_container_row_id") REFERENCES "public"."docker_containers"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "docker_telemetry_batches" ADD CONSTRAINT "docker_telemetry_batches_instance_id_instance_settings_id_fk" FOREIGN KEY ("instance_id") REFERENCES "public"."instance_settings"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "docker_telemetry_batches" ADD CONSTRAINT "docker_telemetry_batches_host_id_hosts_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."hosts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "docker_container_metrics_org_host_time_idx" ON "docker_container_metrics" USING btree ("instance_id","host_id","recorded_at");--> statement-breakpoint -CREATE INDEX "docker_container_metrics_org_container_time_idx" ON "docker_container_metrics" USING btree ("instance_id","docker_container_row_id","recorded_at");--> statement-breakpoint +CREATE INDEX "docker_container_metrics_instance_host_time_idx" ON "docker_container_metrics" USING btree ("instance_id","host_id","recorded_at");--> statement-breakpoint +CREATE INDEX "docker_container_metrics_instance_container_time_idx" ON "docker_container_metrics" USING btree ("instance_id","docker_container_row_id","recorded_at");--> statement-breakpoint CREATE UNIQUE INDEX "docker_telemetry_batches_host_batch_uidx" ON "docker_telemetry_batches" USING btree ("host_id","batch_id");--> statement-breakpoint CREATE INDEX "docker_telemetry_batches_received_idx" ON "docker_telemetry_batches" USING btree ("received_at"); diff --git a/apps/web/lib/db/migrations/0006_docker_container_lifecycle_events.sql b/apps/web/lib/db/migrations/0006_docker_container_lifecycle_events.sql index ea0b6cd0..ebbbff2d 100644 --- a/apps/web/lib/db/migrations/0006_docker_container_lifecycle_events.sql +++ b/apps/web/lib/db/migrations/0006_docker_container_lifecycle_events.sql @@ -18,5 +18,5 @@ ALTER TABLE "docker_container_lifecycle_events" ADD CONSTRAINT "docker_container ALTER TABLE "docker_container_lifecycle_events" ADD CONSTRAINT "docker_container_lifecycle_events_host_id_hosts_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."hosts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "docker_container_lifecycle_events" ADD CONSTRAINT "docker_container_lifecycle_events_docker_container_row_id_docker_containers_id_fk" FOREIGN KEY ("docker_container_row_id") REFERENCES "public"."docker_containers"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint CREATE UNIQUE INDEX "docker_container_lifecycle_events_host_event_uidx" ON "docker_container_lifecycle_events" USING btree ("host_id","docker_container_id","event_type","occurred_at");--> statement-breakpoint -CREATE INDEX "docker_container_lifecycle_events_org_host_time_idx" ON "docker_container_lifecycle_events" USING btree ("instance_id","host_id","occurred_at");--> statement-breakpoint -CREATE INDEX "docker_container_lifecycle_events_org_container_time_idx" ON "docker_container_lifecycle_events" USING btree ("instance_id","docker_container_row_id","occurred_at"); +CREATE INDEX "docker_container_lifecycle_events_instance_host_time_idx" ON "docker_container_lifecycle_events" USING btree ("instance_id","host_id","occurred_at");--> statement-breakpoint +CREATE INDEX "docker_container_lifecycle_events_instance_container_time_idx" ON "docker_container_lifecycle_events" USING btree ("instance_id","docker_container_row_id","occurred_at"); diff --git a/apps/web/lib/db/migrations/0007_instance_role_residue.sql b/apps/web/lib/db/migrations/0007_instance_role_residue.sql new file mode 100644 index 00000000..8abaade7 --- /dev/null +++ b/apps/web/lib/db/migrations/0007_instance_role_residue.sql @@ -0,0 +1,137 @@ +UPDATE "user" +SET role = 'instance_admin' +WHERE role = 'org_admin'; +--> statement-breakpoint +UPDATE "user" +SET roles = COALESCE( + ( + SELECT jsonb_agg( + to_jsonb(CASE value WHEN 'org_admin' THEN 'instance_admin' ELSE value END) + ) + FROM jsonb_array_elements_text(roles) AS role_values(value) + ), + '[]'::jsonb +) +WHERE roles ? 'org_admin'; +--> statement-breakpoint +UPDATE "invitations" +SET role = 'instance_admin' +WHERE role = 'org_admin'; +--> statement-breakpoint +UPDATE "invitations" +SET roles = COALESCE( + ( + SELECT jsonb_agg( + to_jsonb(CASE value WHEN 'org_admin' THEN 'instance_admin' ELSE value END) + ) + FROM jsonb_array_elements_text(roles) AS role_values(value) + ), + '[]'::jsonb +) +WHERE roles ? 'org_admin'; +--> statement-breakpoint +UPDATE "instance_settings" +SET metadata = replace(metadata::text, '"org_admin"', '"instance_admin"')::jsonb +WHERE metadata::text LIKE '%"org_admin"%'; +--> statement-breakpoint +ALTER INDEX IF EXISTS "alert_instances_org_status_idx" RENAME TO "alert_instances_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "alert_rules_org_enabled_idx" RENAME TO "alert_rules_instance_enabled_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "alert_rules_org_global_idx" RENAME TO "alert_rules_instance_global_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "alert_rules_org_host_idx" RENAME TO "alert_rules_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "alert_silences_org_active_idx" RENAME TO "alert_silences_instance_active_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "alert_silences_org_host_idx" RENAME TO "alert_silences_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "audit_events_org_time_idx" RENAME TO "audit_events_instance_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "build_doc_asset_storage_settings_org_uidx" RENAME TO "build_doc_asset_storage_settings_instance_uidx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "build_doc_snippets_org_updated_idx" RENAME TO "build_doc_snippets_instance_updated_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "build_doc_templates_org_active_idx" RENAME TO "build_doc_templates_instance_active_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "build_docs_org_updated_idx" RENAME TO "build_docs_instance_updated_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "calendar_event_hosts_org_host_idx" RENAME TO "calendar_event_hosts_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "calendar_event_participants_org_user_idx" RENAME TO "calendar_event_participants_instance_user_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "calendar_events_org_client_request_idx" RENAME TO "calendar_events_instance_client_request_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "calendar_events_org_range_idx" RENAME TO "calendar_events_instance_range_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "calendar_events_org_series_idx" RENAME TO "calendar_events_instance_series_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "calendar_events_org_series_occurrence_idx" RENAME TO "calendar_events_instance_series_occurrence_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "cert_events_org_time_idx" RENAME TO "cert_events_instance_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "certificates_org_expiry_idx" RENAME TO "certificates_instance_expiry_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "certificates_org_host_idx" RENAME TO "certificates_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "certificates_org_status_idx" RENAME TO "certificates_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "checks_org_host_idx" RENAME TO "checks_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "docker_container_lifecycle_events_org_container_time_idx" RENAME TO "docker_container_lifecycle_events_instance_container_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "docker_container_lifecycle_events_org_host_time_idx" RENAME TO "docker_container_lifecycle_events_instance_host_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "docker_container_metrics_org_container_time_idx" RENAME TO "docker_container_metrics_instance_container_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "docker_container_metrics_org_host_time_idx" RENAME TO "docker_container_metrics_instance_host_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "docker_containers_org_host_present_seen_idx" RENAME TO "docker_containers_instance_host_present_seen_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "docker_containers_org_image_idx" RENAME TO "docker_containers_instance_image_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "domain_accounts_org_status_idx" RENAME TO "domain_accounts_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "domain_accounts_org_username_idx" RENAME TO "domain_accounts_instance_username_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "host_docker_status_org_status_checked_idx" RENAME TO "host_docker_status_instance_status_checked_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "host_metrics_org_host_time_idx" RENAME TO "host_metrics_instance_host_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "host_package_updates_org_status_idx" RENAME TO "host_package_updates_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "host_patch_statuses_org_status_idx" RENAME TO "host_patch_statuses_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "host_vuln_findings_org_status_idx" RENAME TO "host_vuln_findings_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "identity_events_org_time_idx" RENAME TO "identity_events_instance_time_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "notes_org_active_updated_idx" RENAME TO "notes_instance_active_updated_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "notification_channels_org_enabled_idx" RENAME TO "notification_channels_instance_enabled_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "notifications_org_user_idx" RENAME TO "notifications_instance_user_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "service_accounts_org_host_idx" RENAME TO "service_accounts_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "service_accounts_org_status_idx" RENAME TO "service_accounts_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "service_accounts_org_type_idx" RENAME TO "service_accounts_instance_type_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "ssh_keys_org_fingerprint_idx" RENAME TO "ssh_keys_instance_fingerprint_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "ssh_keys_org_host_idx" RENAME TO "ssh_keys_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "ssh_keys_org_status_idx" RENAME TO "ssh_keys_instance_status_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "ssh_keys_org_type_idx" RENAME TO "ssh_keys_instance_type_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "sw_pkg_org_name_idx" RENAME TO "sw_pkg_instance_name_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "tags_org_key_idx" RENAME TO "tags_instance_key_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "tags_org_key_value_ci_uidx" RENAME TO "tags_instance_key_value_ci_uidx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "terminal_sessions_org_host_idx" RENAME TO "terminal_sessions_instance_host_idx"; +--> statement-breakpoint +ALTER INDEX IF EXISTS "users_org_active_deleted_name_email_idx" RENAME TO "users_instance_active_deleted_name_email_idx"; diff --git a/apps/web/lib/db/migrations/meta/0000_snapshot.json b/apps/web/lib/db/migrations/meta/0000_snapshot.json index 006d886f..59ee3516 100644 --- a/apps/web/lib/db/migrations/meta/0000_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0000_snapshot.json @@ -476,8 +476,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1357,8 +1357,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1384,8 +1384,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1713,8 +1713,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2016,8 +2016,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2316,8 +2316,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2501,8 +2501,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2522,8 +2522,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2543,8 +2543,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2679,8 +2679,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2700,8 +2700,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2857,8 +2857,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3001,8 +3001,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3189,8 +3189,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3446,8 +3446,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3467,8 +3467,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3488,8 +3488,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3639,8 +3639,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3952,8 +3952,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3973,8 +3973,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3994,8 +3994,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4210,8 +4210,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4231,8 +4231,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4252,8 +4252,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4273,8 +4273,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4432,8 +4432,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4453,8 +4453,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5553,8 +5553,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -5962,8 +5962,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6424,8 +6424,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6630,8 +6630,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6893,8 +6893,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7947,8 +7947,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8412,8 +8412,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8635,8 +8635,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9297,8 +9297,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9639,8 +9639,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9827,8 +9827,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10335,8 +10335,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10449,8 +10449,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10652,8 +10652,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10679,8 +10679,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10706,8 +10706,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10727,8 +10727,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0001_snapshot.json b/apps/web/lib/db/migrations/meta/0001_snapshot.json index 58664c86..6b5a018d 100644 --- a/apps/web/lib/db/migrations/meta/0001_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0001_snapshot.json @@ -476,8 +476,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1357,8 +1357,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1384,8 +1384,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1713,8 +1713,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2016,8 +2016,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2316,8 +2316,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2501,8 +2501,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2522,8 +2522,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2543,8 +2543,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2679,8 +2679,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2700,8 +2700,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2857,8 +2857,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3001,8 +3001,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3189,8 +3189,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3446,8 +3446,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3467,8 +3467,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3488,8 +3488,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3639,8 +3639,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3952,8 +3952,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3973,8 +3973,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3994,8 +3994,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4210,8 +4210,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4231,8 +4231,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4252,8 +4252,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4273,8 +4273,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4432,8 +4432,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4453,8 +4453,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5671,8 +5671,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -6080,8 +6080,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6542,8 +6542,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6748,8 +6748,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7011,8 +7011,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -8065,8 +8065,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8530,8 +8530,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8753,8 +8753,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9415,8 +9415,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9757,8 +9757,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9945,8 +9945,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10453,8 +10453,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10567,8 +10567,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10770,8 +10770,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10797,8 +10797,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10824,8 +10824,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10845,8 +10845,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0002_snapshot.json b/apps/web/lib/db/migrations/meta/0002_snapshot.json index 94fffaee..35ec7759 100644 --- a/apps/web/lib/db/migrations/meta/0002_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0002_snapshot.json @@ -476,8 +476,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1357,8 +1357,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1384,8 +1384,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1713,8 +1713,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2016,8 +2016,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2316,8 +2316,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2501,8 +2501,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2522,8 +2522,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2543,8 +2543,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2679,8 +2679,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2700,8 +2700,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2857,8 +2857,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3001,8 +3001,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3189,8 +3189,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3446,8 +3446,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3467,8 +3467,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3488,8 +3488,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3639,8 +3639,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3952,8 +3952,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3973,8 +3973,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3994,8 +3994,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4210,8 +4210,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4231,8 +4231,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4252,8 +4252,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4273,8 +4273,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4432,8 +4432,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4453,8 +4453,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5671,8 +5671,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -6080,8 +6080,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6542,8 +6542,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6748,8 +6748,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7011,8 +7011,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -8065,8 +8065,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8530,8 +8530,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8753,8 +8753,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9415,8 +9415,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9757,8 +9757,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9945,8 +9945,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10453,8 +10453,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10567,8 +10567,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10770,8 +10770,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10797,8 +10797,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10824,8 +10824,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10845,8 +10845,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", @@ -10991,8 +10991,8 @@ "method": "btree", "with": {} }, - "host_docker_status_org_status_checked_idx": { - "name": "host_docker_status_org_status_checked_idx", + "host_docker_status_instance_status_checked_idx": { + "name": "host_docker_status_instance_status_checked_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0003_snapshot.json b/apps/web/lib/db/migrations/meta/0003_snapshot.json index 70b62dcd..da55cd83 100644 --- a/apps/web/lib/db/migrations/meta/0003_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0003_snapshot.json @@ -476,8 +476,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1357,8 +1357,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1384,8 +1384,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1713,8 +1713,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2016,8 +2016,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2316,8 +2316,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2501,8 +2501,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2522,8 +2522,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2543,8 +2543,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2679,8 +2679,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2700,8 +2700,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2857,8 +2857,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3001,8 +3001,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3189,8 +3189,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3446,8 +3446,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3467,8 +3467,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3488,8 +3488,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3639,8 +3639,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3952,8 +3952,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3973,8 +3973,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3994,8 +3994,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4210,8 +4210,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4231,8 +4231,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4252,8 +4252,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4273,8 +4273,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4432,8 +4432,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4453,8 +4453,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5671,8 +5671,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -6080,8 +6080,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6542,8 +6542,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6748,8 +6748,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7011,8 +7011,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -8065,8 +8065,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8530,8 +8530,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8753,8 +8753,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9415,8 +9415,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9757,8 +9757,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9945,8 +9945,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10453,8 +10453,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10567,8 +10567,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10770,8 +10770,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10797,8 +10797,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10824,8 +10824,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10845,8 +10845,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", @@ -10991,8 +10991,8 @@ "method": "btree", "with": {} }, - "host_docker_status_org_status_checked_idx": { - "name": "host_docker_status_org_status_checked_idx", + "host_docker_status_instance_status_checked_idx": { + "name": "host_docker_status_instance_status_checked_idx", "columns": [ { "expression": "instance_id", @@ -11209,8 +11209,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_host_present_seen_idx": { - "name": "docker_containers_org_host_present_seen_idx", + "docker_containers_instance_host_present_seen_idx": { + "name": "docker_containers_instance_host_present_seen_idx", "columns": [ { "expression": "instance_id", @@ -11242,8 +11242,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_image_idx": { - "name": "docker_containers_org_image_idx", + "docker_containers_instance_image_idx": { + "name": "docker_containers_instance_image_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0004_snapshot.json b/apps/web/lib/db/migrations/meta/0004_snapshot.json index a0ad7f08..1283fc0d 100644 --- a/apps/web/lib/db/migrations/meta/0004_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0004_snapshot.json @@ -476,8 +476,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1357,8 +1357,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1384,8 +1384,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1713,8 +1713,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2016,8 +2016,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2316,8 +2316,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2501,8 +2501,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2522,8 +2522,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2543,8 +2543,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2679,8 +2679,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2700,8 +2700,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2857,8 +2857,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3001,8 +3001,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3189,8 +3189,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3446,8 +3446,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3467,8 +3467,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3488,8 +3488,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3639,8 +3639,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3952,8 +3952,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3973,8 +3973,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3994,8 +3994,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4210,8 +4210,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4231,8 +4231,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4252,8 +4252,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4273,8 +4273,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4432,8 +4432,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4453,8 +4453,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5671,8 +5671,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -6080,8 +6080,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6542,8 +6542,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6748,8 +6748,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7011,8 +7011,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -8065,8 +8065,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8530,8 +8530,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8753,8 +8753,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9415,8 +9415,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9757,8 +9757,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9945,8 +9945,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10453,8 +10453,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10567,8 +10567,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10770,8 +10770,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10797,8 +10797,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10824,8 +10824,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10845,8 +10845,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", @@ -10991,8 +10991,8 @@ "method": "btree", "with": {} }, - "host_docker_status_org_status_checked_idx": { - "name": "host_docker_status_org_status_checked_idx", + "host_docker_status_instance_status_checked_idx": { + "name": "host_docker_status_instance_status_checked_idx", "columns": [ { "expression": "instance_id", @@ -11209,8 +11209,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_host_present_seen_idx": { - "name": "docker_containers_org_host_present_seen_idx", + "docker_containers_instance_host_present_seen_idx": { + "name": "docker_containers_instance_host_present_seen_idx", "columns": [ { "expression": "instance_id", @@ -11242,8 +11242,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_image_idx": { - "name": "docker_containers_org_image_idx", + "docker_containers_instance_image_idx": { + "name": "docker_containers_instance_image_idx", "columns": [ { "expression": "instance_id", @@ -11407,8 +11407,8 @@ } }, "indexes": { - "docker_container_metrics_org_host_time_idx": { - "name": "docker_container_metrics_org_host_time_idx", + "docker_container_metrics_instance_host_time_idx": { + "name": "docker_container_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -11434,8 +11434,8 @@ "method": "btree", "with": {} }, - "docker_container_metrics_org_container_time_idx": { - "name": "docker_container_metrics_org_container_time_idx", + "docker_container_metrics_instance_container_time_idx": { + "name": "docker_container_metrics_instance_container_time_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0005_snapshot.json b/apps/web/lib/db/migrations/meta/0005_snapshot.json index 49bfc7c4..970ecae3 100644 --- a/apps/web/lib/db/migrations/meta/0005_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0005_snapshot.json @@ -483,8 +483,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1364,8 +1364,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1391,8 +1391,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1720,8 +1720,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2023,8 +2023,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2323,8 +2323,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2508,8 +2508,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2529,8 +2529,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2550,8 +2550,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2686,8 +2686,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2707,8 +2707,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2864,8 +2864,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3008,8 +3008,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3196,8 +3196,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3453,8 +3453,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3474,8 +3474,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3495,8 +3495,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3646,8 +3646,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3959,8 +3959,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3980,8 +3980,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4001,8 +4001,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4217,8 +4217,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4238,8 +4238,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4259,8 +4259,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4280,8 +4280,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4439,8 +4439,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4460,8 +4460,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5678,8 +5678,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -6087,8 +6087,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6549,8 +6549,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6755,8 +6755,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7018,8 +7018,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -8072,8 +8072,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8537,8 +8537,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8760,8 +8760,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9422,8 +9422,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9764,8 +9764,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9952,8 +9952,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10460,8 +10460,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10574,8 +10574,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10777,8 +10777,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10804,8 +10804,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10831,8 +10831,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10852,8 +10852,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", @@ -10998,8 +10998,8 @@ "method": "btree", "with": {} }, - "host_docker_status_org_status_checked_idx": { - "name": "host_docker_status_org_status_checked_idx", + "host_docker_status_instance_status_checked_idx": { + "name": "host_docker_status_instance_status_checked_idx", "columns": [ { "expression": "instance_id", @@ -11216,8 +11216,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_host_present_seen_idx": { - "name": "docker_containers_org_host_present_seen_idx", + "docker_containers_instance_host_present_seen_idx": { + "name": "docker_containers_instance_host_present_seen_idx", "columns": [ { "expression": "instance_id", @@ -11249,8 +11249,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_image_idx": { - "name": "docker_containers_org_image_idx", + "docker_containers_instance_image_idx": { + "name": "docker_containers_instance_image_idx", "columns": [ { "expression": "instance_id", @@ -11414,8 +11414,8 @@ } }, "indexes": { - "docker_container_metrics_org_host_time_idx": { - "name": "docker_container_metrics_org_host_time_idx", + "docker_container_metrics_instance_host_time_idx": { + "name": "docker_container_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -11441,8 +11441,8 @@ "method": "btree", "with": {} }, - "docker_container_metrics_org_container_time_idx": { - "name": "docker_container_metrics_org_container_time_idx", + "docker_container_metrics_instance_container_time_idx": { + "name": "docker_container_metrics_instance_container_time_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0006_snapshot.json b/apps/web/lib/db/migrations/meta/0006_snapshot.json index 2d9b3150..f99789af 100644 --- a/apps/web/lib/db/migrations/meta/0006_snapshot.json +++ b/apps/web/lib/db/migrations/meta/0006_snapshot.json @@ -483,8 +483,8 @@ } }, "indexes": { - "users_org_active_deleted_name_email_idx": { - "name": "users_org_active_deleted_name_email_idx", + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", "columns": [ { "expression": "instance_id", @@ -1364,8 +1364,8 @@ } }, "indexes": { - "tags_org_key_value_ci_uidx": { - "name": "tags_org_key_value_ci_uidx", + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", "columns": [ { "expression": "instance_id", @@ -1391,8 +1391,8 @@ "method": "btree", "with": {} }, - "tags_org_key_idx": { - "name": "tags_org_key_idx", + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", "columns": [ { "expression": "instance_id", @@ -1720,8 +1720,8 @@ } }, "indexes": { - "host_metrics_org_host_time_idx": { - "name": "host_metrics_org_host_time_idx", + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -2023,8 +2023,8 @@ } }, "indexes": { - "checks_org_host_idx": { - "name": "checks_org_host_idx", + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2323,8 +2323,8 @@ } }, "indexes": { - "alert_instances_org_status_idx": { - "name": "alert_instances_org_status_idx", + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -2508,8 +2508,8 @@ } }, "indexes": { - "alert_rules_org_host_idx": { - "name": "alert_rules_org_host_idx", + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2529,8 +2529,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_enabled_idx": { - "name": "alert_rules_org_enabled_idx", + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -2550,8 +2550,8 @@ "method": "btree", "with": {} }, - "alert_rules_org_global_idx": { - "name": "alert_rules_org_global_idx", + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", "columns": [ { "expression": "instance_id", @@ -2686,8 +2686,8 @@ } }, "indexes": { - "alert_silences_org_host_idx": { - "name": "alert_silences_org_host_idx", + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -2707,8 +2707,8 @@ "method": "btree", "with": {} }, - "alert_silences_org_active_idx": { - "name": "alert_silences_org_active_idx", + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -2864,8 +2864,8 @@ } }, "indexes": { - "notification_channels_org_enabled_idx": { - "name": "notification_channels_org_enabled_idx", + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", "columns": [ { "expression": "instance_id", @@ -3008,8 +3008,8 @@ "method": "btree", "with": {} }, - "notifications_org_user_idx": { - "name": "notifications_org_user_idx", + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -3196,8 +3196,8 @@ "method": "btree", "with": {} }, - "cert_events_org_time_idx": { - "name": "cert_events_org_time_idx", + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3453,8 +3453,8 @@ "method": "btree", "with": {} }, - "certificates_org_expiry_idx": { - "name": "certificates_org_expiry_idx", + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", "columns": [ { "expression": "instance_id", @@ -3474,8 +3474,8 @@ "method": "btree", "with": {} }, - "certificates_org_status_idx": { - "name": "certificates_org_status_idx", + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -3495,8 +3495,8 @@ "method": "btree", "with": {} }, - "certificates_org_host_idx": { - "name": "certificates_org_host_idx", + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -3646,8 +3646,8 @@ } }, "indexes": { - "identity_events_org_time_idx": { - "name": "identity_events_org_time_idx", + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -3959,8 +3959,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_type_idx": { - "name": "service_accounts_org_type_idx", + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -3980,8 +3980,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_status_idx": { - "name": "service_accounts_org_status_idx", + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4001,8 +4001,8 @@ "method": "btree", "with": {} }, - "service_accounts_org_host_idx": { - "name": "service_accounts_org_host_idx", + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4217,8 +4217,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_fingerprint_idx": { - "name": "ssh_keys_org_fingerprint_idx", + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", "columns": [ { "expression": "instance_id", @@ -4238,8 +4238,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_type_idx": { - "name": "ssh_keys_org_type_idx", + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", "columns": [ { "expression": "instance_id", @@ -4259,8 +4259,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_status_idx": { - "name": "ssh_keys_org_status_idx", + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -4280,8 +4280,8 @@ "method": "btree", "with": {} }, - "ssh_keys_org_host_idx": { - "name": "ssh_keys_org_host_idx", + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -4439,8 +4439,8 @@ } }, "indexes": { - "domain_accounts_org_username_idx": { - "name": "domain_accounts_org_username_idx", + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", "columns": [ { "expression": "instance_id", @@ -4460,8 +4460,8 @@ "method": "btree", "with": {} }, - "domain_accounts_org_status_idx": { - "name": "domain_accounts_org_status_idx", + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -5678,8 +5678,8 @@ } }, "indexes": { - "terminal_sessions_org_host_idx": { - "name": "terminal_sessions_org_host_idx", + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -6087,8 +6087,8 @@ "method": "btree", "with": {} }, - "sw_pkg_org_name_idx": { - "name": "sw_pkg_org_name_idx", + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", "columns": [ { "expression": "instance_id", @@ -6549,8 +6549,8 @@ "method": "btree", "with": {} }, - "host_package_updates_org_status_idx": { - "name": "host_package_updates_org_status_idx", + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -6755,8 +6755,8 @@ "method": "btree", "with": {} }, - "host_patch_statuses_org_status_idx": { - "name": "host_patch_statuses_org_status_idx", + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -7018,8 +7018,8 @@ "method": "btree", "with": {} }, - "host_vuln_findings_org_status_idx": { - "name": "host_vuln_findings_org_status_idx", + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", "columns": [ { "expression": "instance_id", @@ -8072,8 +8072,8 @@ } }, "indexes": { - "notes_org_active_updated_idx": { - "name": "notes_org_active_updated_idx", + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", "columns": [ { "expression": "instance_id", @@ -8537,8 +8537,8 @@ } }, "indexes": { - "audit_events_org_time_idx": { - "name": "audit_events_org_time_idx", + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", "columns": [ { "expression": "instance_id", @@ -8760,8 +8760,8 @@ } }, "indexes": { - "build_doc_asset_storage_settings_org_uidx": { - "name": "build_doc_asset_storage_settings_org_uidx", + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", "columns": [ { "expression": "instance_id", @@ -9422,8 +9422,8 @@ } }, "indexes": { - "build_doc_snippets_org_updated_idx": { - "name": "build_doc_snippets_org_updated_idx", + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -9764,8 +9764,8 @@ } }, "indexes": { - "build_doc_templates_org_active_idx": { - "name": "build_doc_templates_org_active_idx", + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", "columns": [ { "expression": "instance_id", @@ -9952,8 +9952,8 @@ } }, "indexes": { - "build_docs_org_updated_idx": { - "name": "build_docs_org_updated_idx", + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", "columns": [ { "expression": "instance_id", @@ -10460,8 +10460,8 @@ } }, "indexes": { - "calendar_event_hosts_org_host_idx": { - "name": "calendar_event_hosts_org_host_idx", + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", "columns": [ { "expression": "instance_id", @@ -10574,8 +10574,8 @@ } }, "indexes": { - "calendar_event_participants_org_user_idx": { - "name": "calendar_event_participants_org_user_idx", + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", "columns": [ { "expression": "instance_id", @@ -10777,8 +10777,8 @@ } }, "indexes": { - "calendar_events_org_range_idx": { - "name": "calendar_events_org_range_idx", + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", "columns": [ { "expression": "instance_id", @@ -10804,8 +10804,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_idx": { - "name": "calendar_events_org_series_idx", + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", "columns": [ { "expression": "instance_id", @@ -10831,8 +10831,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_client_request_idx": { - "name": "calendar_events_org_client_request_idx", + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", "columns": [ { "expression": "instance_id", @@ -10852,8 +10852,8 @@ "method": "btree", "with": {} }, - "calendar_events_org_series_occurrence_idx": { - "name": "calendar_events_org_series_occurrence_idx", + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", "columns": [ { "expression": "instance_id", @@ -10998,8 +10998,8 @@ "method": "btree", "with": {} }, - "host_docker_status_org_status_checked_idx": { - "name": "host_docker_status_org_status_checked_idx", + "host_docker_status_instance_status_checked_idx": { + "name": "host_docker_status_instance_status_checked_idx", "columns": [ { "expression": "instance_id", @@ -11216,8 +11216,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_host_present_seen_idx": { - "name": "docker_containers_org_host_present_seen_idx", + "docker_containers_instance_host_present_seen_idx": { + "name": "docker_containers_instance_host_present_seen_idx", "columns": [ { "expression": "instance_id", @@ -11249,8 +11249,8 @@ "method": "btree", "with": {} }, - "docker_containers_org_image_idx": { - "name": "docker_containers_org_image_idx", + "docker_containers_instance_image_idx": { + "name": "docker_containers_instance_image_idx", "columns": [ { "expression": "instance_id", @@ -11414,8 +11414,8 @@ } }, "indexes": { - "docker_container_metrics_org_host_time_idx": { - "name": "docker_container_metrics_org_host_time_idx", + "docker_container_metrics_instance_host_time_idx": { + "name": "docker_container_metrics_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -11441,8 +11441,8 @@ "method": "btree", "with": {} }, - "docker_container_metrics_org_container_time_idx": { - "name": "docker_container_metrics_org_container_time_idx", + "docker_container_metrics_instance_container_time_idx": { + "name": "docker_container_metrics_instance_container_time_idx", "columns": [ { "expression": "instance_id", @@ -11770,8 +11770,8 @@ "method": "btree", "with": {} }, - "docker_container_lifecycle_events_org_host_time_idx": { - "name": "docker_container_lifecycle_events_org_host_time_idx", + "docker_container_lifecycle_events_instance_host_time_idx": { + "name": "docker_container_lifecycle_events_instance_host_time_idx", "columns": [ { "expression": "instance_id", @@ -11797,8 +11797,8 @@ "method": "btree", "with": {} }, - "docker_container_lifecycle_events_org_container_time_idx": { - "name": "docker_container_lifecycle_events_org_container_time_idx", + "docker_container_lifecycle_events_instance_container_time_idx": { + "name": "docker_container_lifecycle_events_instance_container_time_idx", "columns": [ { "expression": "instance_id", diff --git a/apps/web/lib/db/migrations/meta/0007_snapshot.json b/apps/web/lib/db/migrations/meta/0007_snapshot.json new file mode 100644 index 00000000..f99789af --- /dev/null +++ b/apps/web/lib/db/migrations/meta/0007_snapshot.json @@ -0,0 +1,11887 @@ +{ + "id": "bd4149aa-26df-42e1-bce0-9b0b29eeeac7", + "prevId": "2a2ebbe0-a495-4c00-b982-da36ba33a2f2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "licence_tier": { + "name": "licence_tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'community'" + }, + "licence_key": { + "name": "licence_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "licence_verifier_public_key": { + "name": "licence_verifier_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "licence_verifier_public_key_fingerprint": { + "name": "licence_verifier_public_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metric_retention_days": { + "name": "metric_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "docker_metric_retention_days": { + "name": "docker_metric_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "instance_settings_slug_unique": { + "name": "instance_settings_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.totp_credential": { + "name": "totp_credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "totp_credential_user_id_user_id_fk": { + "name": "totp_credential_user_id_user_id_fk", + "tableFrom": "totp_credential", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'engineer'" + }, + "roles": { + "name": "roles", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "notifications_enabled": { + "name": "notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "users_instance_active_deleted_name_email_idx": { + "name": "users_instance_active_deleted_name_email_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_instance_id_instance_settings_id_fk": { + "name": "user_instance_id_instance_settings_id_fk", + "tableFrom": "user", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitations": { + "name": "invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'engineer'" + }, + "roles": { + "name": "roles", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by_id": { + "name": "invited_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "invitations_instance_id_instance_settings_id_fk": { + "name": "invitations_instance_id_instance_settings_id_fk", + "tableFrom": "invitations", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invitations_invited_by_id_user_id_fk": { + "name": "invitations_invited_by_id_user_id_fk", + "tableFrom": "invitations", + "tableTo": "user", + "columnsFrom": [ + "invited_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitations_token_unique": { + "name": "invitations_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_enrolment_tokens": { + "name": "agent_enrolment_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auto_approve": { + "name": "auto_approve", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_verify": { + "name": "skip_verify", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_enrolment_tokens_instance_id_instance_settings_id_fk": { + "name": "agent_enrolment_tokens_instance_id_instance_settings_id_fk", + "tableFrom": "agent_enrolment_tokens", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_enrolment_tokens_created_by_id_user_id_fk": { + "name": "agent_enrolment_tokens_created_by_id_user_id_fk", + "tableFrom": "agent_enrolment_tokens", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_enrolment_tokens_token_unique": { + "name": "agent_enrolment_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + }, + "agent_enrolment_tokens_token_hash_unique": { + "name": "agent_enrolment_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_status_history": { + "name": "agent_status_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_status_history_agent_id_agents_id_fk": { + "name": "agent_status_history_agent_id_agents_id_fk", + "tableFrom": "agent_status_history", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_status_history_instance_id_instance_settings_id_fk": { + "name": "agent_status_history_instance_id_instance_settings_id_fk", + "tableFrom": "agent_status_history", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arch": { + "name": "arch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "approved_by_id": { + "name": "approved_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "enrolment_token_id": { + "name": "enrolment_token_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_cert_pem": { + "name": "client_cert_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_cert_serial": { + "name": "client_cert_serial", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_cert_issued_at": { + "name": "client_cert_issued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "client_cert_not_after": { + "name": "client_cert_not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agents_instance_id_instance_settings_id_fk": { + "name": "agents_instance_id_instance_settings_id_fk", + "tableFrom": "agents", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_approved_by_id_user_id_fk": { + "name": "agents_approved_by_id_user_id_fk", + "tableFrom": "agents", + "tableTo": "user", + "columnsFrom": [ + "approved_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_enrolment_token_id_agent_enrolment_tokens_id_fk": { + "name": "agents_enrolment_token_id_agent_enrolment_tokens_id_fk", + "tableFrom": "agents", + "tableTo": "agent_enrolment_tokens", + "columnsFrom": [ + "enrolment_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_public_key_unique": { + "name": "agents_public_key_unique", + "nullsNotDistinct": false, + "columns": [ + "public_key" + ] + }, + "agents_client_cert_serial_unique": { + "name": "agents_client_cert_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "client_cert_serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hosts": { + "name": "hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "os_version": { + "name": "os_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arch": { + "name": "arch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_addresses": { + "name": "ip_addresses", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "memory_percent": { + "name": "memory_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "uptime_seconds": { + "name": "uptime_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "hosts_instance_id_instance_settings_id_fk": { + "name": "hosts_instance_id_instance_settings_id_fk", + "tableFrom": "hosts", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hosts_agent_id_agents_id_fk": { + "name": "hosts_agent_id_agents_id_fk", + "tableFrom": "hosts", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_count": { + "name": "usage_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tags_instance_key_value_ci_uidx": { + "name": "tags_instance_key_value_ci_uidx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"key\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "lower(\"value\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tags_instance_key_idx": { + "name": "tags_instance_key_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tags_instance_id_instance_settings_id_fk": { + "name": "tags_instance_id_instance_settings_id_fk", + "tableFrom": "tags", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_tags": { + "name": "resource_tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_tags_resource_idx": { + "name": "resource_tags_resource_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_tags_tag_idx": { + "name": "resource_tags_tag_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_tags_unique_uidx": { + "name": "resource_tags_unique_uidx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_tags_instance_id_instance_settings_id_fk": { + "name": "resource_tags_instance_id_instance_settings_id_fk", + "tableFrom": "resource_tags", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "resource_tags_tag_id_tags_id_fk": { + "name": "resource_tags_tag_id_tags_id_fk", + "tableFrom": "resource_tags", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tag_rules": { + "name": "tag_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "tag_rules_instance_id_instance_settings_id_fk": { + "name": "tag_rules_instance_id_instance_settings_id_fk", + "tableFrom": "tag_rules", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_metrics": { + "name": "host_metrics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "memory_percent": { + "name": "memory_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "disk_percent": { + "name": "disk_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "uptime_seconds": { + "name": "uptime_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "host_metrics_instance_host_time_idx": { + "name": "host_metrics_instance_host_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recorded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_metrics_instance_id_instance_settings_id_fk": { + "name": "host_metrics_instance_id_instance_settings_id_fk", + "tableFrom": "host_metrics", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_metrics_host_id_hosts_id_fk": { + "name": "host_metrics_host_id_hosts_id_fk", + "tableFrom": "host_metrics", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "host_metrics_id_recorded_at_pk": { + "name": "host_metrics_id_recorded_at_pk", + "columns": [ + "id", + "recorded_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.check_results": { + "name": "check_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ran_at": { + "name": "ran_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "check_results_check_idx": { + "name": "check_results_check_idx", + "columns": [ + { + "expression": "check_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ran_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "check_results_instance_idx": { + "name": "check_results_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ran_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "check_results_check_id_checks_id_fk": { + "name": "check_results_check_id_checks_id_fk", + "tableFrom": "check_results", + "tableTo": "checks", + "columnsFrom": [ + "check_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "check_results_host_id_hosts_id_fk": { + "name": "check_results_host_id_hosts_id_fk", + "tableFrom": "check_results", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "check_results_instance_id_instance_settings_id_fk": { + "name": "check_results_instance_id_instance_settings_id_fk", + "tableFrom": "check_results", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.checks": { + "name": "checks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "check_type": { + "name": "check_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "checks_instance_host_idx": { + "name": "checks_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "checks_instance_id_instance_settings_id_fk": { + "name": "checks_instance_id_instance_settings_id_fk", + "tableFrom": "checks", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "checks_host_id_hosts_id_fk": { + "name": "checks_host_id_hosts_id_fk", + "tableFrom": "checks", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_queries": { + "name": "agent_queries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "query_type": { + "name": "query_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_queries_host_status_idx": { + "name": "agent_queries_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_queries_instance_idx": { + "name": "agent_queries_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_queries_instance_id_instance_settings_id_fk": { + "name": "agent_queries_instance_id_instance_settings_id_fk", + "tableFrom": "agent_queries", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_queries_host_id_hosts_id_fk": { + "name": "agent_queries_host_id_hosts_id_fk", + "tableFrom": "agent_queries", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alert_instances": { + "name": "alert_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "rule_id": { + "name": "rule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'firing'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_by": { + "name": "acknowledged_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "alert_instances_instance_status_idx": { + "name": "alert_instances_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alert_instances_rule_host_status_idx": { + "name": "alert_instances_rule_host_status_idx", + "columns": [ + { + "expression": "rule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "alert_instances_rule_id_alert_rules_id_fk": { + "name": "alert_instances_rule_id_alert_rules_id_fk", + "tableFrom": "alert_instances", + "tableTo": "alert_rules", + "columnsFrom": [ + "rule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alert_instances_host_id_hosts_id_fk": { + "name": "alert_instances_host_id_hosts_id_fk", + "tableFrom": "alert_instances", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alert_instances_instance_id_instance_settings_id_fk": { + "name": "alert_instances_instance_id_instance_settings_id_fk", + "tableFrom": "alert_instances", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alert_rules": { + "name": "alert_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition_type": { + "name": "condition_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'warning'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_global_default": { + "name": "is_global_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "alert_rules_instance_host_idx": { + "name": "alert_rules_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alert_rules_instance_enabled_idx": { + "name": "alert_rules_instance_enabled_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alert_rules_instance_global_idx": { + "name": "alert_rules_instance_global_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_global_default", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "alert_rules_instance_id_instance_settings_id_fk": { + "name": "alert_rules_instance_id_instance_settings_id_fk", + "tableFrom": "alert_rules", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alert_rules_host_id_hosts_id_fk": { + "name": "alert_rules_host_id_hosts_id_fk", + "tableFrom": "alert_rules", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alert_silences": { + "name": "alert_silences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rule_id": { + "name": "rule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "alert_silences_instance_host_idx": { + "name": "alert_silences_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alert_silences_instance_active_idx": { + "name": "alert_silences_instance_active_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ends_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "alert_silences_instance_id_instance_settings_id_fk": { + "name": "alert_silences_instance_id_instance_settings_id_fk", + "tableFrom": "alert_silences", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alert_silences_host_id_hosts_id_fk": { + "name": "alert_silences_host_id_hosts_id_fk", + "tableFrom": "alert_silences", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alert_silences_rule_id_alert_rules_id_fk": { + "name": "alert_silences_rule_id_alert_rules_id_fk", + "tableFrom": "alert_silences", + "tableTo": "alert_rules", + "columnsFrom": [ + "rule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alert_silences_created_by_user_id_fk": { + "name": "alert_silences_created_by_user_id_fk", + "tableFrom": "alert_silences", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_channels": { + "name": "notification_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notification_channels_instance_enabled_idx": { + "name": "notification_channels_instance_enabled_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_channels_instance_id_instance_settings_id_fk": { + "name": "notification_channels_instance_id_instance_settings_id_fk", + "tableFrom": "notification_channels", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alert_instance_id": { + "name": "alert_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_instance_user_idx": { + "name": "notifications_instance_user_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_created_idx": { + "name": "notifications_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_deleted_at_idx": { + "name": "notifications_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"notifications\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_instance_id_instance_settings_id_fk": { + "name": "notifications_instance_id_instance_settings_id_fk", + "tableFrom": "notifications", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notifications_user_id_user_id_fk": { + "name": "notifications_user_id_user_id_fk", + "tableFrom": "notifications", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notifications_alert_instance_id_alert_instances_id_fk": { + "name": "notifications_alert_instance_id_alert_instances_id_fk", + "tableFrom": "notifications", + "tableTo": "alert_instances", + "columnsFrom": [ + "alert_instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate_events": { + "name": "certificate_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificate_id": { + "name": "certificate_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_status": { + "name": "previous_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_status": { + "name": "new_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cert_events_cert_time_idx": { + "name": "cert_events_cert_time_idx", + "columns": [ + { + "expression": "certificate_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cert_events_instance_time_idx": { + "name": "cert_events_instance_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "certificate_events_instance_id_instance_settings_id_fk": { + "name": "certificate_events_instance_id_instance_settings_id_fk", + "tableFrom": "certificate_events", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "certificate_events_certificate_id_certificates_id_fk": { + "name": "certificate_events_certificate_id_certificates_id_fk", + "tableFrom": "certificate_events", + "tableTo": "certificates", + "columnsFrom": [ + "certificate_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificates": { + "name": "certificates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "discovered_by_host_id": { + "name": "discovered_by_host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'discovered'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sans": { + "name": "sans", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "not_before": { + "name": "not_before", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "not_after": { + "name": "not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'valid'" + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tracked_url": { + "name": "tracked_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_interval_seconds": { + "name": "refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_refresh_error": { + "name": "last_refresh_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "certificates_identity_idx": { + "name": "certificates_identity_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "certificates_instance_expiry_idx": { + "name": "certificates_instance_expiry_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "not_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "certificates_instance_status_idx": { + "name": "certificates_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "certificates_instance_host_idx": { + "name": "certificates_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "discovered_by_host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "certificates_refresh_due_idx": { + "name": "certificates_refresh_due_idx", + "columns": [ + { + "expression": "tracked_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_refreshed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "certificates_instance_id_instance_settings_id_fk": { + "name": "certificates_instance_id_instance_settings_id_fk", + "tableFrom": "certificates", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "certificates_discovered_by_host_id_hosts_id_fk": { + "name": "certificates_discovered_by_host_id_hosts_id_fk", + "tableFrom": "certificates", + "tableTo": "hosts", + "columnsFrom": [ + "discovered_by_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "certificates_check_id_checks_id_fk": { + "name": "certificates_check_id_checks_id_fk", + "tableFrom": "certificates", + "tableTo": "checks", + "columnsFrom": [ + "check_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_events": { + "name": "identity_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_account_id": { + "name": "service_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_key_id": { + "name": "ssh_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "identity_events_instance_time_idx": { + "name": "identity_events_instance_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_events_account_time_idx": { + "name": "identity_events_account_time_idx", + "columns": [ + { + "expression": "service_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_events_key_time_idx": { + "name": "identity_events_key_time_idx", + "columns": [ + { + "expression": "ssh_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_events_host_time_idx": { + "name": "identity_events_host_time_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identity_events_instance_id_instance_settings_id_fk": { + "name": "identity_events_instance_id_instance_settings_id_fk", + "tableFrom": "identity_events", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "identity_events_service_account_id_service_accounts_id_fk": { + "name": "identity_events_service_account_id_service_accounts_id_fk", + "tableFrom": "identity_events", + "tableTo": "service_accounts", + "columnsFrom": [ + "service_account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "identity_events_ssh_key_id_ssh_keys_id_fk": { + "name": "identity_events_ssh_key_id_ssh_keys_id_fk", + "tableFrom": "identity_events", + "tableTo": "ssh_keys", + "columnsFrom": [ + "ssh_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "identity_events_host_id_hosts_id_fk": { + "name": "identity_events_host_id_hosts_id_fk", + "tableFrom": "identity_events", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.service_accounts": { + "name": "service_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gid": { + "name": "gid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "home_directory": { + "name": "home_directory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shell": { + "name": "shell", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'service'" + }, + "has_login_capability": { + "name": "has_login_capability", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_running_processes": { + "name": "has_running_processes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "account_locked": { + "name": "account_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "password_expires_at": { + "name": "password_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "password_last_changed_at": { + "name": "password_last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "service_accounts_identity_idx": { + "name": "service_accounts_identity_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "service_accounts_instance_type_idx": { + "name": "service_accounts_instance_type_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "service_accounts_instance_status_idx": { + "name": "service_accounts_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "service_accounts_instance_host_idx": { + "name": "service_accounts_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "service_accounts_instance_id_instance_settings_id_fk": { + "name": "service_accounts_instance_id_instance_settings_id_fk", + "tableFrom": "service_accounts", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "service_accounts_host_id_hosts_id_fk": { + "name": "service_accounts_host_id_hosts_id_fk", + "tableFrom": "service_accounts", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_keys": { + "name": "ssh_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_account_id": { + "name": "service_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_type": { + "name": "key_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "bit_length": { + "name": "bit_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_source": { + "name": "key_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'authorized_keys'" + }, + "associated_username": { + "name": "associated_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "key_age_seconds": { + "name": "key_age_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ssh_keys_identity_idx": { + "name": "ssh_keys_identity_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ssh_keys_instance_fingerprint_idx": { + "name": "ssh_keys_instance_fingerprint_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ssh_keys_instance_type_idx": { + "name": "ssh_keys_instance_type_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ssh_keys_instance_status_idx": { + "name": "ssh_keys_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ssh_keys_instance_host_idx": { + "name": "ssh_keys_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ssh_keys_account_idx": { + "name": "ssh_keys_account_idx", + "columns": [ + { + "expression": "service_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_keys_instance_id_instance_settings_id_fk": { + "name": "ssh_keys_instance_id_instance_settings_id_fk", + "tableFrom": "ssh_keys", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ssh_keys_host_id_hosts_id_fk": { + "name": "ssh_keys_host_id_hosts_id_fk", + "tableFrom": "ssh_keys", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ssh_keys_service_account_id_service_accounts_id_fk": { + "name": "ssh_keys_service_account_id_service_accounts_id_fk", + "tableFrom": "ssh_keys", + "tableTo": "service_accounts", + "columnsFrom": [ + "service_account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain_accounts": { + "name": "domain_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "password_expires_at": { + "name": "password_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "domain_accounts_instance_username_idx": { + "name": "domain_accounts_instance_username_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "domain_accounts_instance_status_idx": { + "name": "domain_accounts_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "domain_accounts_instance_id_instance_settings_id_fk": { + "name": "domain_accounts_instance_id_instance_settings_id_fk", + "tableFrom": "domain_accounts", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ldap_configurations": { + "name": "ldap_configurations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 389 + }, + "use_tls": { + "name": "use_tls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_start_tls": { + "name": "use_start_tls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tls_certificate": { + "name": "tls_certificate", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_dn": { + "name": "base_dn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bind_dn": { + "name": "bind_dn", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bind_password": { + "name": "bind_password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_search_base": { + "name": "user_search_base", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_search_filter": { + "name": "user_search_filter", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'(uid={{username}})'" + }, + "group_search_base": { + "name": "group_search_base", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_search_filter": { + "name": "group_search_filter", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username_attribute": { + "name": "username_attribute", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'uid'" + }, + "email_attribute": { + "name": "email_attribute", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mail'" + }, + "display_name_attribute": { + "name": "display_name_attribute", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cn'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_login": { + "name": "allow_login", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ldap_configurations_instance_id_instance_settings_id_fk": { + "name": "ldap_configurations_instance_id_instance_settings_id_fk", + "tableFrom": "ldap_configurations", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_group_members": { + "name": "host_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_group_members_instance_id_instance_settings_id_fk": { + "name": "host_group_members_instance_id_instance_settings_id_fk", + "tableFrom": "host_group_members", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_group_members_group_id_host_groups_id_fk": { + "name": "host_group_members_group_id_host_groups_id_fk", + "tableFrom": "host_group_members", + "tableTo": "host_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_group_members_host_id_hosts_id_fk": { + "name": "host_group_members_host_id_hosts_id_fk", + "tableFrom": "host_group_members", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_groups": { + "name": "host_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "host_groups_instance_id_instance_settings_id_fk": { + "name": "host_groups_instance_id_instance_settings_id_fk", + "tableFrom": "host_groups", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_run_hosts": { + "name": "task_run_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_run_id": { + "name": "task_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "raw_output": { + "name": "raw_output", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_run_hosts_run_idx": { + "name": "task_run_hosts_run_idx", + "columns": [ + { + "expression": "task_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_run_hosts_host_status_idx": { + "name": "task_run_hosts_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_run_hosts_instance_id_instance_settings_id_fk": { + "name": "task_run_hosts_instance_id_instance_settings_id_fk", + "tableFrom": "task_run_hosts", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "task_run_hosts_task_run_id_task_runs_id_fk": { + "name": "task_run_hosts_task_run_id_task_runs_id_fk", + "tableFrom": "task_run_hosts", + "tableTo": "task_runs", + "columnsFrom": [ + "task_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "task_run_hosts_host_id_hosts_id_fk": { + "name": "task_run_hosts_host_id_hosts_id_fk", + "tableFrom": "task_run_hosts", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_runs": { + "name": "task_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggered_by": { + "name": "triggered_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduled_from_id": { + "name": "scheduled_from_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "max_parallel": { + "name": "max_parallel", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_runs_instance_idx": { + "name": "task_runs_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_target_idx": { + "name": "task_runs_target_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_scheduled_from_idx": { + "name": "task_runs_scheduled_from_idx", + "columns": [ + { + "expression": "scheduled_from_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_runs_instance_id_instance_settings_id_fk": { + "name": "task_runs_instance_id_instance_settings_id_fk", + "tableFrom": "task_runs", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "task_runs_triggered_by_user_id_fk": { + "name": "task_runs_triggered_by_user_id_fk", + "tableFrom": "task_runs", + "tableTo": "user", + "columnsFrom": [ + "triggered_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_schedules": { + "name": "task_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_parallel": { + "name": "max_parallel", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_run_task_run_id": { + "name": "last_run_task_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_schedules_instance_idx": { + "name": "task_schedules_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_schedules_due_idx": { + "name": "task_schedules_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_schedules_instance_id_instance_settings_id_fk": { + "name": "task_schedules_instance_id_instance_settings_id_fk", + "tableFrom": "task_schedules", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "task_schedules_created_by_user_id_fk": { + "name": "task_schedules_created_by_user_id_fk", + "tableFrom": "task_schedules", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ansible_credential_profiles": { + "name": "ansible_credential_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_encrypted": { + "name": "private_key_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ansible_credential_profiles_instance_idx": { + "name": "ansible_credential_profiles_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ansible_credential_profiles_instance_id_instance_settings_id_fk": { + "name": "ansible_credential_profiles_instance_id_instance_settings_id_fk", + "tableFrom": "ansible_credential_profiles", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ansible_credential_profiles_created_by_user_id_fk": { + "name": "ansible_credential_profiles_created_by_user_id_fk", + "tableFrom": "ansible_credential_profiles", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.terminal_sessions": { + "name": "terminal_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "websocket_token_hash": { + "name": "websocket_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "recording": { + "name": "recording", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "terminal_sessions_instance_host_idx": { + "name": "terminal_sessions_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "terminal_sessions_session_id_idx": { + "name": "terminal_sessions_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "terminal_sessions_instance_id_instance_settings_id_fk": { + "name": "terminal_sessions_instance_id_instance_settings_id_fk", + "tableFrom": "terminal_sessions", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "terminal_sessions_host_id_hosts_id_fk": { + "name": "terminal_sessions_host_id_hosts_id_fk", + "tableFrom": "terminal_sessions", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "terminal_sessions_user_id_user_id_fk": { + "name": "terminal_sessions_user_id_user_id_fk", + "tableFrom": "terminal_sessions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "terminal_sessions_session_id_unique": { + "name": "terminal_sessions_session_id_unique", + "nullsNotDistinct": false, + "columns": [ + "session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_software_reports": { + "name": "saved_software_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "saved_sw_reports_user_idx": { + "name": "saved_sw_reports_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_software_reports_instance_id_instance_settings_id_fk": { + "name": "saved_software_reports_instance_id_instance_settings_id_fk", + "tableFrom": "saved_software_reports", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "saved_software_reports_user_id_user_id_fk": { + "name": "saved_software_reports_user_id_user_id_fk", + "tableFrom": "saved_software_reports", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.software_packages": { + "name": "software_packages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "architecture": { + "name": "architecture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publisher": { + "name": "publisher", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "distro_id": { + "name": "distro_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "distro_version_id": { + "name": "distro_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "distro_codename": { + "name": "distro_codename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "distro_id_like": { + "name": "distro_id_like", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_name": { + "name": "source_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_epoch": { + "name": "package_epoch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_release": { + "name": "package_release", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "install_date": { + "name": "install_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cve_matches": { + "name": "cve_matches", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sw_pkg_uniq": { + "name": "sw_pkg_uniq", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "architecture", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sw_pkg_instance_name_idx": { + "name": "sw_pkg_instance_name_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sw_pkg_source_name_idx": { + "name": "sw_pkg_source_name_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "distro_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "distro_codename", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sw_pkg_host_idx": { + "name": "sw_pkg_host_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sw_pkg_first_seen_idx": { + "name": "sw_pkg_first_seen_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "first_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "software_packages_instance_id_instance_settings_id_fk": { + "name": "software_packages_instance_id_instance_settings_id_fk", + "tableFrom": "software_packages", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "software_packages_host_id_hosts_id_fk": { + "name": "software_packages_host_id_hosts_id_fk", + "tableFrom": "software_packages", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.software_scans": { + "name": "software_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_run_host_id": { + "name": "task_run_host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_count": { + "name": "package_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "added_count": { + "name": "added_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "removed_count": { + "name": "removed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unchanged_count": { + "name": "unchanged_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sw_scan_host_idx": { + "name": "sw_scan_host_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sw_scan_instance_idx": { + "name": "sw_scan_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "software_scans_instance_id_instance_settings_id_fk": { + "name": "software_scans_instance_id_instance_settings_id_fk", + "tableFrom": "software_scans", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "software_scans_host_id_hosts_id_fk": { + "name": "software_scans_host_id_hosts_id_fk", + "tableFrom": "software_scans", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "software_scans_task_run_host_id_task_run_hosts_id_fk": { + "name": "software_scans_task_run_host_id_task_run_hosts_id_fk", + "tableFrom": "software_scans", + "tableTo": "task_run_hosts", + "columnsFrom": [ + "task_run_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_package_updates": { + "name": "host_package_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_version": { + "name": "current_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "available_version": { + "name": "available_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "architecture": { + "name": "architecture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_manager": { + "name": "package_manager", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "host_package_updates_current_uniq": { + "name": "host_package_updates_current_uniq", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "architecture", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "package_manager", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_package_updates_instance_status_idx": { + "name": "host_package_updates_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_package_updates_host_status_idx": { + "name": "host_package_updates_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_package_updates_instance_id_instance_settings_id_fk": { + "name": "host_package_updates_instance_id_instance_settings_id_fk", + "tableFrom": "host_package_updates", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_package_updates_host_id_hosts_id_fk": { + "name": "host_package_updates_host_id_hosts_id_fk", + "tableFrom": "host_package_updates", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_patch_statuses": { + "name": "host_patch_statuses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_patched_at": { + "name": "last_patched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "patch_age_days": { + "name": "patch_age_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_age_days": { + "name": "max_age_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "package_manager": { + "name": "package_manager", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updates_supported": { + "name": "updates_supported", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updates_count": { + "name": "updates_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updates_truncated": { + "name": "updates_truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "warnings": { + "name": "warnings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "host_patch_statuses_check_uniq": { + "name": "host_patch_statuses_check_uniq", + "columns": [ + { + "expression": "check_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_patch_statuses_instance_status_idx": { + "name": "host_patch_statuses_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_patch_statuses_host_checked_idx": { + "name": "host_patch_statuses_host_checked_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_patch_statuses_instance_id_instance_settings_id_fk": { + "name": "host_patch_statuses_instance_id_instance_settings_id_fk", + "tableFrom": "host_patch_statuses", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_patch_statuses_host_id_hosts_id_fk": { + "name": "host_patch_statuses_host_id_hosts_id_fk", + "tableFrom": "host_patch_statuses", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_patch_statuses_check_id_checks_id_fk": { + "name": "host_patch_statuses_check_id_checks_id_fk", + "tableFrom": "host_patch_statuses", + "tableTo": "checks", + "columnsFrom": [ + "check_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_vulnerability_findings": { + "name": "host_vulnerability_findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "software_package_id": { + "name": "software_package_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installed_version": { + "name": "installed_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fixed_version": { + "name": "fixed_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cvss_score": { + "name": "cvss_score", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "known_exploited": { + "name": "known_exploited", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'confirmed'" + }, + "match_reason": { + "name": "match_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "host_vuln_findings_uniq": { + "name": "host_vuln_findings_uniq", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "software_package_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cve_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_vuln_findings_instance_status_idx": { + "name": "host_vuln_findings_instance_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_vuln_findings_host_status_idx": { + "name": "host_vuln_findings_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_vuln_findings_cve_idx": { + "name": "host_vuln_findings_cve_idx", + "columns": [ + { + "expression": "cve_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_vuln_findings_confidence_idx": { + "name": "host_vuln_findings_confidence_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "confidence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_vulnerability_findings_instance_id_instance_settings_id_fk": { + "name": "host_vulnerability_findings_instance_id_instance_settings_id_fk", + "tableFrom": "host_vulnerability_findings", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_vulnerability_findings_host_id_hosts_id_fk": { + "name": "host_vulnerability_findings_host_id_hosts_id_fk", + "tableFrom": "host_vulnerability_findings", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_vulnerability_findings_software_package_id_software_packages_id_fk": { + "name": "host_vulnerability_findings_software_package_id_software_packages_id_fk", + "tableFrom": "host_vulnerability_findings", + "tableTo": "software_packages", + "columnsFrom": [ + "software_package_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_vulnerability_findings_cve_id_vulnerability_cves_cve_id_fk": { + "name": "host_vulnerability_findings_cve_id_vulnerability_cves_cve_id_fk", + "tableFrom": "host_vulnerability_findings", + "tableTo": "vulnerability_cves", + "columnsFrom": [ + "cve_id" + ], + "columnsTo": [ + "cve_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vulnerability_cves": { + "name": "vulnerability_cves", + "schema": "", + "columns": { + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cvss_score": { + "name": "cvss_score", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "modified_at": { + "name": "modified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected": { + "name": "rejected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "known_exploited": { + "name": "known_exploited", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "kev_due_date": { + "name": "kev_due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "kev_vendor_project": { + "name": "kev_vendor_project", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kev_product": { + "name": "kev_product", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kev_required_action": { + "name": "kev_required_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "vulnerability_cves_severity_idx": { + "name": "vulnerability_cves_severity_idx", + "columns": [ + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "vulnerability_cves_kev_idx": { + "name": "vulnerability_cves_kev_idx", + "columns": [ + { + "expression": "known_exploited", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_config": { + "name": "system_config", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_network_memberships": { + "name": "host_network_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "network_id": { + "name": "network_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auto_assigned": { + "name": "auto_assigned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "host_network_memberships_network_host_uniq": { + "name": "host_network_memberships_network_host_uniq", + "columns": [ + { + "expression": "network_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_network_memberships_instance_id_instance_settings_id_fk": { + "name": "host_network_memberships_instance_id_instance_settings_id_fk", + "tableFrom": "host_network_memberships", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_network_memberships_network_id_networks_id_fk": { + "name": "host_network_memberships_network_id_networks_id_fk", + "tableFrom": "host_network_memberships", + "tableTo": "networks", + "columnsFrom": [ + "network_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_network_memberships_host_id_hosts_id_fk": { + "name": "host_network_memberships_host_id_hosts_id_fk", + "tableFrom": "host_network_memberships", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.networks": { + "name": "networks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cidr": { + "name": "cidr", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "networks_instance_id_instance_settings_id_fk": { + "name": "networks_instance_id_instance_settings_id_fk", + "tableFrom": "networks", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.note_reactions": { + "name": "note_reactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note_id": { + "name": "note_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reaction": { + "name": "reaction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "note_reactions_unique_uidx": { + "name": "note_reactions_unique_uidx", + "columns": [ + { + "expression": "note_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reaction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "note_reactions_note_idx": { + "name": "note_reactions_note_idx", + "columns": [ + { + "expression": "note_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "note_reactions_instance_id_instance_settings_id_fk": { + "name": "note_reactions_instance_id_instance_settings_id_fk", + "tableFrom": "note_reactions", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "note_reactions_note_id_notes_id_fk": { + "name": "note_reactions_note_id_notes_id_fk", + "tableFrom": "note_reactions", + "tableTo": "notes", + "columnsFrom": [ + "note_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "note_reactions_user_id_user_id_fk": { + "name": "note_reactions_user_id_user_id_fk", + "tableFrom": "note_reactions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.note_revisions": { + "name": "note_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note_id": { + "name": "note_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "editor_id": { + "name": "editor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "note_revisions_note_created_idx": { + "name": "note_revisions_note_created_idx", + "columns": [ + { + "expression": "note_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "note_revisions_instance_id_instance_settings_id_fk": { + "name": "note_revisions_instance_id_instance_settings_id_fk", + "tableFrom": "note_revisions", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "note_revisions_note_id_notes_id_fk": { + "name": "note_revisions_note_id_notes_id_fk", + "tableFrom": "note_revisions", + "tableTo": "notes", + "columnsFrom": [ + "note_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "note_revisions_editor_id_user_id_fk": { + "name": "note_revisions_editor_id_user_id_fk", + "tableFrom": "note_revisions", + "tableTo": "user", + "columnsFrom": [ + "editor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.note_targets": { + "name": "note_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note_id": { + "name": "note_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag_selector": { + "name": "tag_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "note_targets_type_id_idx": { + "name": "note_targets_type_id_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "note_targets_note_idx": { + "name": "note_targets_note_idx", + "columns": [ + { + "expression": "note_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "note_targets_direct_unique_uidx": { + "name": "note_targets_direct_unique_uidx", + "columns": [ + { + "expression": "note_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"note_targets\".\"target_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "note_targets_instance_id_instance_settings_id_fk": { + "name": "note_targets_instance_id_instance_settings_id_fk", + "tableFrom": "note_targets", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "note_targets_note_id_notes_id_fk": { + "name": "note_targets_note_id_notes_id_fk", + "tableFrom": "note_targets", + "tableTo": "notes", + "columnsFrom": [ + "note_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notes": { + "name": "notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_edited_by_id": { + "name": "last_edited_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(body, '')), 'B')", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notes_instance_active_updated_idx": { + "name": "notes_instance_active_updated_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notes_author_idx": { + "name": "notes_author_idx", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notes_search_vector_idx": { + "name": "notes_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "notes_instance_id_instance_settings_id_fk": { + "name": "notes_instance_id_instance_settings_id_fk", + "tableFrom": "notes", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notes_author_id_user_id_fk": { + "name": "notes_author_id_user_id_fk", + "tableFrom": "notes", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "notes_last_edited_by_id_user_id_fk": { + "name": "notes_last_edited_by_id_user_id_fk", + "tableFrom": "notes", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate_authorities": { + "name": "certificate_authorities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_pem_encrypted": { + "name": "key_pem_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "not_before": { + "name": "not_before", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "not_after": { + "name": "not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cert_authorities_purpose_idx": { + "name": "cert_authorities_purpose_idx", + "columns": [ + { + "expression": "purpose", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "certificate_authorities_instance_id_instance_settings_id_fk": { + "name": "certificate_authorities_instance_id_instance_settings_id_fk", + "tableFrom": "certificate_authorities", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_authorities_fingerprint_sha256_unique": { + "name": "certificate_authorities_fingerprint_sha256_unique", + "nullsNotDistinct": false, + "columns": [ + "fingerprint_sha256" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.revoked_certificates": { + "name": "revoked_certificates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serial": { + "name": "serial", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "revoked_certs_instance_idx": { + "name": "revoked_certs_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "revoked_certificates_instance_id_instance_settings_id_fk": { + "name": "revoked_certificates_instance_id_instance_settings_id_fk", + "tableFrom": "revoked_certificates", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "revoked_certificates_serial_unique": { + "name": "revoked_certificates_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_cert_signings": { + "name": "pending_cert_signings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "csr_der": { + "name": "csr_der", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "pending_cert_signings_requested_at_idx": { + "name": "pending_cert_signings_requested_at_idx", + "columns": [ + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_cert_signings_agent_id_agents_id_fk": { + "name": "pending_cert_signings_agent_id_agents_id_fk", + "tableFrom": "pending_cert_signings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_events_instance_time_idx": { + "name": "audit_events_instance_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_actor_time_idx": { + "name": "audit_events_actor_time_idx", + "columns": [ + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_target_time_idx": { + "name": "audit_events_target_time_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_events_instance_id_instance_settings_id_fk": { + "name": "audit_events_instance_id_instance_settings_id_fk", + "tableFrom": "audit_events", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "audit_events_actor_user_id_user_id_fk": { + "name": "audit_events_actor_user_id_user_id_fk", + "tableFrom": "audit_events", + "tableTo": "user", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_throttles": { + "name": "security_throttles", + "schema": "", + "columns": { + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hits": { + "name": "hits", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "lockout_level": { + "name": "lockout_level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "security_throttles_pk": { + "name": "security_throttles_pk", + "columns": [ + "scope", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_asset_storage_settings": { + "name": "build_doc_asset_storage_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by_id": { + "name": "updated_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'filesystem'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"provider\":\"filesystem\"}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "build_doc_asset_storage_settings_instance_uidx": { + "name": "build_doc_asset_storage_settings_instance_uidx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "build_doc_asset_storage_settings_instance_id_instance_settings_id_fk": { + "name": "build_doc_asset_storage_settings_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_asset_storage_settings", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_asset_storage_settings_updated_by_id_user_id_fk": { + "name": "build_doc_asset_storage_settings_updated_by_id_user_id_fk", + "tableFrom": "build_doc_asset_storage_settings", + "tableTo": "user", + "columnsFrom": [ + "updated_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_assets": { + "name": "build_doc_assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "build_doc_id": { + "name": "build_doc_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by_id": { + "name": "uploaded_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "checksum_sha256": { + "name": "checksum_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "build_doc_assets_doc_idx": { + "name": "build_doc_assets_doc_idx", + "columns": [ + { + "expression": "build_doc_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_assets_section_idx": { + "name": "build_doc_assets_section_idx", + "columns": [ + { + "expression": "section_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_assets_storage_key_uidx": { + "name": "build_doc_assets_storage_key_uidx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "build_doc_assets_instance_id_instance_settings_id_fk": { + "name": "build_doc_assets_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_assets", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_assets_build_doc_id_build_docs_id_fk": { + "name": "build_doc_assets_build_doc_id_build_docs_id_fk", + "tableFrom": "build_doc_assets", + "tableTo": "build_docs", + "columnsFrom": [ + "build_doc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "build_doc_assets_section_id_build_doc_sections_id_fk": { + "name": "build_doc_assets_section_id_build_doc_sections_id_fk", + "tableFrom": "build_doc_assets", + "tableTo": "build_doc_sections", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "build_doc_assets_uploaded_by_id_user_id_fk": { + "name": "build_doc_assets_uploaded_by_id_user_id_fk", + "tableFrom": "build_doc_assets", + "tableTo": "user", + "columnsFrom": [ + "uploaded_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_revisions": { + "name": "build_doc_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "build_doc_id": { + "name": "build_doc_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "editor_id": { + "name": "editor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "build_doc_revisions_doc_created_idx": { + "name": "build_doc_revisions_doc_created_idx", + "columns": [ + { + "expression": "build_doc_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "build_doc_revisions_instance_id_instance_settings_id_fk": { + "name": "build_doc_revisions_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_revisions", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_revisions_build_doc_id_build_docs_id_fk": { + "name": "build_doc_revisions_build_doc_id_build_docs_id_fk", + "tableFrom": "build_doc_revisions", + "tableTo": "build_docs", + "columnsFrom": [ + "build_doc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "build_doc_revisions_editor_id_user_id_fk": { + "name": "build_doc_revisions_editor_id_user_id_fk", + "tableFrom": "build_doc_revisions", + "tableTo": "user", + "columnsFrom": [ + "editor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_sections": { + "name": "build_doc_sections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "build_doc_id": { + "name": "build_doc_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "field_values": { + "name": "field_values", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "source_snippet_id": { + "name": "source_snippet_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_snippet_version": { + "name": "source_snippet_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(body, '')), 'B')", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "build_doc_sections_doc_position_idx": { + "name": "build_doc_sections_doc_position_idx", + "columns": [ + { + "expression": "build_doc_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_sections_instance_idx": { + "name": "build_doc_sections_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_sections_search_vector_idx": { + "name": "build_doc_sections_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "build_doc_sections_instance_id_instance_settings_id_fk": { + "name": "build_doc_sections_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_sections", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_sections_build_doc_id_build_docs_id_fk": { + "name": "build_doc_sections_build_doc_id_build_docs_id_fk", + "tableFrom": "build_doc_sections", + "tableTo": "build_docs", + "columnsFrom": [ + "build_doc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "build_doc_sections_source_snippet_id_build_doc_snippets_id_fk": { + "name": "build_doc_sections_source_snippet_id_build_doc_snippets_id_fk", + "tableFrom": "build_doc_sections", + "tableTo": "build_doc_snippets", + "columnsFrom": [ + "source_snippet_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_snippets": { + "name": "build_doc_snippets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_edited_by_id": { + "name": "last_edited_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(body, '')), 'B') || setweight(to_tsvector('english', coalesce(category, '')), 'C')", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "build_doc_snippets_instance_updated_idx": { + "name": "build_doc_snippets_instance_updated_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_snippets_search_vector_idx": { + "name": "build_doc_snippets_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "build_doc_snippets_instance_id_instance_settings_id_fk": { + "name": "build_doc_snippets_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_snippets", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_snippets_created_by_id_user_id_fk": { + "name": "build_doc_snippets_created_by_id_user_id_fk", + "tableFrom": "build_doc_snippets", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "build_doc_snippets_last_edited_by_id_user_id_fk": { + "name": "build_doc_snippets_last_edited_by_id_user_id_fk", + "tableFrom": "build_doc_snippets", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_template_versions": { + "name": "build_doc_template_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "build_doc_template_versions_template_version_uidx": { + "name": "build_doc_template_versions_template_version_uidx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_template_versions_instance_idx": { + "name": "build_doc_template_versions_instance_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "build_doc_template_versions_instance_id_instance_settings_id_fk": { + "name": "build_doc_template_versions_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_template_versions", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_template_versions_template_id_build_doc_templates_id_fk": { + "name": "build_doc_template_versions_template_id_build_doc_templates_id_fk", + "tableFrom": "build_doc_template_versions", + "tableTo": "build_doc_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "build_doc_template_versions_created_by_id_user_id_fk": { + "name": "build_doc_template_versions_created_by_id_user_id_fk", + "tableFrom": "build_doc_template_versions", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_doc_templates": { + "name": "build_doc_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "current_version": { + "name": "current_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "build_doc_templates_instance_active_idx": { + "name": "build_doc_templates_instance_active_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_doc_templates_default_uidx": { + "name": "build_doc_templates_default_uidx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_default", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"build_doc_templates\".\"is_default\" = TRUE AND \"build_doc_templates\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "build_doc_templates_instance_id_instance_settings_id_fk": { + "name": "build_doc_templates_instance_id_instance_settings_id_fk", + "tableFrom": "build_doc_templates", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_doc_templates_created_by_id_user_id_fk": { + "name": "build_doc_templates_created_by_id_user_id_fk", + "tableFrom": "build_doc_templates", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.build_docs": { + "name": "build_docs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_version_id": { + "name": "template_version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_edited_by_id": { + "name": "last_edited_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "host_name": { + "name": "host_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_name": { + "name": "project_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "field_values": { + "name": "field_values", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(host_name, '')), 'B') || setweight(to_tsvector('english', coalesce(customer_name, '')), 'B') || setweight(to_tsvector('english', coalesce(project_name, '')), 'B')", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "build_docs_instance_updated_idx": { + "name": "build_docs_instance_updated_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_docs_template_idx": { + "name": "build_docs_template_idx", + "columns": [ + { + "expression": "template_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_docs_status_idx": { + "name": "build_docs_status_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "build_docs_search_vector_idx": { + "name": "build_docs_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "build_docs_instance_id_instance_settings_id_fk": { + "name": "build_docs_instance_id_instance_settings_id_fk", + "tableFrom": "build_docs", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "build_docs_template_version_id_build_doc_template_versions_id_fk": { + "name": "build_docs_template_version_id_build_doc_template_versions_id_fk", + "tableFrom": "build_docs", + "tableTo": "build_doc_template_versions", + "columnsFrom": [ + "template_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "build_docs_author_id_user_id_fk": { + "name": "build_docs_author_id_user_id_fk", + "tableFrom": "build_docs", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "build_docs_last_edited_by_id_user_id_fk": { + "name": "build_docs_last_edited_by_id_user_id_fk", + "tableFrom": "build_docs", + "tableTo": "user", + "columnsFrom": [ + "last_edited_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ingest_server_snapshots": { + "name": "ingest_server_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "active_requests": { + "name": "active_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "messages_received_total": { + "name": "messages_received_total", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "queue_depth": { + "name": "queue_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "queue_capacity": { + "name": "queue_capacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "goroutines": { + "name": "goroutines", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "heap_alloc_bytes": { + "name": "heap_alloc_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "heap_sys_bytes": { + "name": "heap_sys_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "db_open_connections": { + "name": "db_open_connections", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "db_acquired_connections": { + "name": "db_acquired_connections", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "gc_pause_total_ns": { + "name": "gc_pause_total_ns", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ingest_server_snapshots_server_time_idx": { + "name": "ingest_server_snapshots_server_time_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ingest_server_snapshots_observed_idx": { + "name": "ingest_server_snapshots_observed_idx", + "columns": [ + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ct_cve_service_nonces": { + "name": "ct_cve_service_nonces", + "schema": "", + "columns": { + "token_id": { + "name": "token_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nonce": { + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ct_cve_service_nonces_expires_at_idx": { + "name": "ct_cve_service_nonces_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ct_cve_service_nonces_pk": { + "name": "ct_cve_service_nonces_pk", + "columns": [ + "token_id", + "nonce" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ct_cve_connector_settings": { + "name": "ct_cve_connector_settings", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Primary CT-CVE'" + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inventory_token_id": { + "name": "inventory_token_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inventory_token_secret_encrypted": { + "name": "inventory_token_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ct_cve_token_id": { + "name": "ct_cve_token_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ct_cve_token_secret_encrypted": { + "name": "ct_cve_token_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ct_cve_connector_settings_enabled_idx": { + "name": "ct_cve_connector_settings_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ct_cve_connector_settings_instance_id_instance_settings_id_fk": { + "name": "ct_cve_connector_settings_instance_id_instance_settings_id_fk", + "tableFrom": "ct_cve_connector_settings", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.calendar_event_hosts": { + "name": "calendar_event_hosts", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "calendar_event_hosts_instance_host_idx": { + "name": "calendar_event_hosts_instance_host_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "calendar_event_hosts_instance_id_instance_settings_id_fk": { + "name": "calendar_event_hosts_instance_id_instance_settings_id_fk", + "tableFrom": "calendar_event_hosts", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "calendar_event_hosts_event_id_calendar_events_id_fk": { + "name": "calendar_event_hosts_event_id_calendar_events_id_fk", + "tableFrom": "calendar_event_hosts", + "tableTo": "calendar_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "calendar_event_hosts_host_id_hosts_id_fk": { + "name": "calendar_event_hosts_host_id_hosts_id_fk", + "tableFrom": "calendar_event_hosts", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "calendar_event_hosts_pk": { + "name": "calendar_event_hosts_pk", + "columns": [ + "event_id", + "host_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.calendar_event_participants": { + "name": "calendar_event_participants", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "calendar_event_participants_instance_user_idx": { + "name": "calendar_event_participants_instance_user_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "calendar_event_participants_instance_id_instance_settings_id_fk": { + "name": "calendar_event_participants_instance_id_instance_settings_id_fk", + "tableFrom": "calendar_event_participants", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "calendar_event_participants_event_id_calendar_events_id_fk": { + "name": "calendar_event_participants_event_id_calendar_events_id_fk", + "tableFrom": "calendar_event_participants", + "tableTo": "calendar_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "calendar_event_participants_user_id_user_id_fk": { + "name": "calendar_event_participants_user_id_user_id_fk", + "tableFrom": "calendar_event_participants", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "calendar_event_participants_pk": { + "name": "calendar_event_participants_pk", + "columns": [ + "event_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.calendar_events": { + "name": "calendar_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "all_day": { + "name": "all_day", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'maintenance'" + }, + "recurrence_rule": { + "name": "recurrence_rule", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "series_id": { + "name": "series_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recurrence_instance_start_at": { + "name": "recurrence_instance_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exception_type": { + "name": "exception_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "calendar_events_instance_range_idx": { + "name": "calendar_events_instance_range_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ends_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "calendar_events_instance_series_idx": { + "name": "calendar_events_instance_series_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recurrence_instance_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "calendar_events_instance_client_request_idx": { + "name": "calendar_events_instance_client_request_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "calendar_events_instance_series_occurrence_idx": { + "name": "calendar_events_instance_series_occurrence_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recurrence_instance_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"calendar_events\".\"series_id\" IS NOT NULL AND \"calendar_events\".\"recurrence_instance_start_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "calendar_events_instance_id_instance_settings_id_fk": { + "name": "calendar_events_instance_id_instance_settings_id_fk", + "tableFrom": "calendar_events", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "calendar_events_created_by_user_id_fk": { + "name": "calendar_events_created_by_user_id_fk", + "tableFrom": "calendar_events", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.host_docker_status": { + "name": "host_docker_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "runtime_version": { + "name": "runtime_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "host_docker_status_host_uidx": { + "name": "host_docker_status_host_uidx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "host_docker_status_instance_status_checked_idx": { + "name": "host_docker_status_instance_status_checked_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "host_docker_status_instance_id_instance_settings_id_fk": { + "name": "host_docker_status_instance_id_instance_settings_id_fk", + "tableFrom": "host_docker_status", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "host_docker_status_host_id_hosts_id_fk": { + "name": "host_docker_status_host_id_hosts_id_fk", + "tableFrom": "host_docker_status", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docker_containers": { + "name": "docker_containers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "docker_container_id": { + "name": "docker_container_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_name": { + "name": "primary_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "names_json": { + "name": "names_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_id": { + "name": "image_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "labels_json": { + "name": "labels_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at_source": { + "name": "created_at_source", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at_source": { + "name": "started_at_source", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at_source": { + "name": "finished_at_source", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_inventory_at": { + "name": "last_inventory_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "restart_count": { + "name": "restart_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_present": { + "name": "is_present", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": "true" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docker_containers_host_container_uidx": { + "name": "docker_containers_host_container_uidx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "docker_container_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_containers_instance_host_present_seen_idx": { + "name": "docker_containers_instance_host_present_seen_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_present", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_containers_instance_image_idx": { + "name": "docker_containers_instance_image_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "image", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "docker_containers_instance_id_instance_settings_id_fk": { + "name": "docker_containers_instance_id_instance_settings_id_fk", + "tableFrom": "docker_containers", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "docker_containers_host_id_hosts_id_fk": { + "name": "docker_containers_host_id_hosts_id_fk", + "tableFrom": "docker_containers", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docker_container_metrics": { + "name": "docker_container_metrics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "docker_container_row_id": { + "name": "docker_container_row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "docker_container_id": { + "name": "docker_container_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cpu_percent": { + "name": "cpu_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "memory_usage_bytes": { + "name": "memory_usage_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "memory_limit_bytes": { + "name": "memory_limit_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "memory_percent": { + "name": "memory_percent", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "network_rx_bytes": { + "name": "network_rx_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "network_tx_bytes": { + "name": "network_tx_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "block_read_bytes": { + "name": "block_read_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "block_write_bytes": { + "name": "block_write_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "pids_current": { + "name": "pids_current", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restart_count": { + "name": "restart_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docker_container_metrics_instance_host_time_idx": { + "name": "docker_container_metrics_instance_host_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recorded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_container_metrics_instance_container_time_idx": { + "name": "docker_container_metrics_instance_container_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "docker_container_row_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recorded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "docker_container_metrics_instance_id_instance_settings_id_fk": { + "name": "docker_container_metrics_instance_id_instance_settings_id_fk", + "tableFrom": "docker_container_metrics", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "docker_container_metrics_host_id_hosts_id_fk": { + "name": "docker_container_metrics_host_id_hosts_id_fk", + "tableFrom": "docker_container_metrics", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "docker_container_metrics_docker_container_row_id_docker_containers_id_fk": { + "name": "docker_container_metrics_docker_container_row_id_docker_containers_id_fk", + "tableFrom": "docker_container_metrics", + "tableTo": "docker_containers", + "columnsFrom": [ + "docker_container_row_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "docker_container_metrics_id_recorded_at_pk": { + "name": "docker_container_metrics_id_recorded_at_pk", + "columns": [ + "id", + "recorded_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docker_telemetry_batches": { + "name": "docker_telemetry_batches", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "inventory_count": { + "name": "inventory_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "docker_telemetry_batches_host_batch_uidx": { + "name": "docker_telemetry_batches_host_batch_uidx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_telemetry_batches_received_idx": { + "name": "docker_telemetry_batches_received_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "docker_telemetry_batches_instance_id_instance_settings_id_fk": { + "name": "docker_telemetry_batches_instance_id_instance_settings_id_fk", + "tableFrom": "docker_telemetry_batches", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "docker_telemetry_batches_host_id_hosts_id_fk": { + "name": "docker_telemetry_batches_host_id_hosts_id_fk", + "tableFrom": "docker_telemetry_batches", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docker_container_lifecycle_events": { + "name": "docker_container_lifecycle_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "docker_container_row_id": { + "name": "docker_container_row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "docker_container_id": { + "name": "docker_container_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "primary_name": { + "name": "primary_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restart_count": { + "name": "restart_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docker_container_lifecycle_events_host_event_uidx": { + "name": "docker_container_lifecycle_events_host_event_uidx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "docker_container_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_container_lifecycle_events_instance_host_time_idx": { + "name": "docker_container_lifecycle_events_instance_host_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_container_lifecycle_events_instance_container_time_idx": { + "name": "docker_container_lifecycle_events_instance_container_time_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "docker_container_row_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "docker_container_lifecycle_events_instance_id_instance_settings_id_fk": { + "name": "docker_container_lifecycle_events_instance_id_instance_settings_id_fk", + "tableFrom": "docker_container_lifecycle_events", + "tableTo": "instance_settings", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "docker_container_lifecycle_events_host_id_hosts_id_fk": { + "name": "docker_container_lifecycle_events_host_id_hosts_id_fk", + "tableFrom": "docker_container_lifecycle_events", + "tableTo": "hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "docker_container_lifecycle_events_docker_container_row_id_docker_containers_id_fk": { + "name": "docker_container_lifecycle_events_docker_container_row_id_docker_containers_id_fk", + "tableFrom": "docker_container_lifecycle_events", + "tableTo": "docker_containers", + "columnsFrom": [ + "docker_container_row_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/web/lib/db/migrations/meta/_journal.json b/apps/web/lib/db/migrations/meta/_journal.json index 18538743..585659b4 100644 --- a/apps/web/lib/db/migrations/meta/_journal.json +++ b/apps/web/lib/db/migrations/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1778659600000, "tag": "0006_docker_container_lifecycle_events", "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1778663200000, + "tag": "0007_instance_role_residue", + "breakpoints": true } ] } diff --git a/apps/web/lib/db/schema/alerts.ts b/apps/web/lib/db/schema/alerts.ts index 8d122220..7bdbf413 100644 --- a/apps/web/lib/db/schema/alerts.ts +++ b/apps/web/lib/db/schema/alerts.ts @@ -88,9 +88,9 @@ export const alertRules = pgTable('alert_rules', { deletedAt: timestamp('deleted_at', { withTimezone: true }), metadata: jsonb('metadata'), }, (table) => [ - index('alert_rules_org_host_idx').on(table.instanceId, table.hostId), - index('alert_rules_org_enabled_idx').on(table.instanceId, table.enabled), - index('alert_rules_org_global_idx').on(table.instanceId, table.isGlobalDefault), + index('alert_rules_instance_host_idx').on(table.instanceId, table.hostId), + index('alert_rules_instance_enabled_idx').on(table.instanceId, table.enabled), + index('alert_rules_instance_global_idx').on(table.instanceId, table.isGlobalDefault), ]) export const alertInstances = pgTable('alert_instances', { @@ -106,7 +106,7 @@ export const alertInstances = pgTable('alert_instances', { acknowledgedBy: text('acknowledged_by'), metadata: jsonb('metadata'), }, (table) => [ - index('alert_instances_org_status_idx').on(table.instanceId, table.status), + index('alert_instances_instance_status_idx').on(table.instanceId, table.status), index('alert_instances_rule_host_status_idx').on(table.ruleId, table.hostId, table.status), ]) @@ -122,7 +122,7 @@ export const notificationChannels = pgTable('notification_channels', { deletedAt: timestamp('deleted_at', { withTimezone: true }), metadata: jsonb('metadata'), }, (table) => [ - index('notification_channels_org_enabled_idx').on(table.instanceId, table.enabled), + index('notification_channels_instance_enabled_idx').on(table.instanceId, table.enabled), ]) export const notifications = pgTable('notifications', { @@ -140,7 +140,7 @@ export const notifications = pgTable('notifications', { deletedAt: timestamp('deleted_at', { withTimezone: true }), }, (table) => [ index('notifications_user_read_idx').on(table.userId, table.read), - index('notifications_org_user_idx').on(table.instanceId, table.userId), + index('notifications_instance_user_idx').on(table.instanceId, table.userId), index('notifications_user_created_idx').on(table.userId, table.createdAt), index('notifications_deleted_at_idx').on(table.deletedAt).where(sql`${table.deletedAt} IS NOT NULL`), ]) @@ -148,7 +148,7 @@ export const notifications = pgTable('notifications', { export const alertSilences = pgTable('alert_silences', { id: text('id').primaryKey().$defaultFn(() => createId()), instanceId: text('instance_id').notNull().references(() => instanceSettings.id), - hostId: text('host_id').references(() => hosts.id), // null = org-wide silence + hostId: text('host_id').references(() => hosts.id), // null = instance-wide silence ruleId: text('rule_id').references(() => alertRules.id), // null = silence all rules reason: text('reason').notNull(), startsAt: timestamp('starts_at', { withTimezone: true }).notNull(), @@ -159,8 +159,8 @@ export const alertSilences = pgTable('alert_silences', { deletedAt: timestamp('deleted_at', { withTimezone: true }), metadata: jsonb('metadata'), }, (table) => [ - index('alert_silences_org_host_idx').on(table.instanceId, table.hostId), - index('alert_silences_org_active_idx').on(table.instanceId, table.startsAt, table.endsAt), + index('alert_silences_instance_host_idx').on(table.instanceId, table.hostId), + index('alert_silences_instance_active_idx').on(table.instanceId, table.startsAt, table.endsAt), ]) export type AlertRule = typeof alertRules.$inferSelect diff --git a/apps/web/lib/db/schema/audit-events.ts b/apps/web/lib/db/schema/audit-events.ts index 99576dbf..e855c080 100644 --- a/apps/web/lib/db/schema/audit-events.ts +++ b/apps/web/lib/db/schema/audit-events.ts @@ -14,7 +14,7 @@ export const auditEvents = pgTable('audit_events', { metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ - index('audit_events_org_time_idx').on(t.instanceId, t.createdAt), + index('audit_events_instance_time_idx').on(t.instanceId, t.createdAt), index('audit_events_actor_time_idx').on(t.actorUserId, t.createdAt), index('audit_events_target_time_idx').on(t.targetType, t.targetId, t.createdAt), ]) diff --git a/apps/web/lib/db/schema/auth.ts b/apps/web/lib/db/schema/auth.ts index 2644e8fd..31490758 100644 --- a/apps/web/lib/db/schema/auth.ts +++ b/apps/web/lib/db/schema/auth.ts @@ -24,7 +24,7 @@ export const users = pgTable( notificationsEnabled: boolean('notifications_enabled').notNull().default(true), }, (table) => [ - index('users_org_active_deleted_name_email_idx').on( + index('users_instance_active_deleted_name_email_idx').on( table.instanceId, table.isActive, table.deletedAt, diff --git a/apps/web/lib/db/schema/build-docs.ts b/apps/web/lib/db/schema/build-docs.ts index 1c72934b..c5d854bf 100644 --- a/apps/web/lib/db/schema/build-docs.ts +++ b/apps/web/lib/db/schema/build-docs.ts @@ -97,7 +97,7 @@ export const buildDocTemplates = pgTable( deletedAt: timestamp('deleted_at', { withTimezone: true }), }, (t) => [ - index('build_doc_templates_org_active_idx').on(t.instanceId, t.deletedAt, t.isActive), + index('build_doc_templates_instance_active_idx').on(t.instanceId, t.deletedAt, t.isActive), uniqueIndex('build_doc_templates_default_uidx') .on(t.instanceId, t.isDefault) .where(sql`${t.isDefault} = TRUE AND ${t.deletedAt} IS NULL`), @@ -145,7 +145,7 @@ export const buildDocSnippets = pgTable( metadata: jsonb('metadata').$type(), }, (t) => [ - index('build_doc_snippets_org_updated_idx').on(t.instanceId, t.deletedAt, t.updatedAt), + index('build_doc_snippets_instance_updated_idx').on(t.instanceId, t.deletedAt, t.updatedAt), index('build_doc_snippets_search_vector_idx').using('gin', t.searchVector), ], ) @@ -174,7 +174,7 @@ export const buildDocs = pgTable( deletedAt: timestamp('deleted_at', { withTimezone: true }), }, (t) => [ - index('build_docs_org_updated_idx').on(t.instanceId, t.deletedAt, t.updatedAt), + index('build_docs_instance_updated_idx').on(t.instanceId, t.deletedAt, t.updatedAt), index('build_docs_template_idx').on(t.templateVersionId), index('build_docs_status_idx').on(t.instanceId, t.status), index('build_docs_search_vector_idx').using('gin', t.searchVector), @@ -255,7 +255,7 @@ export const buildDocAssetStorageSettings = pgTable( createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, - (t) => [uniqueIndex('build_doc_asset_storage_settings_org_uidx').on(t.instanceId)], + (t) => [uniqueIndex('build_doc_asset_storage_settings_instance_uidx').on(t.instanceId)], ) export type BuildDocTemplate = typeof buildDocTemplates.$inferSelect diff --git a/apps/web/lib/db/schema/calendar.ts b/apps/web/lib/db/schema/calendar.ts index add772af..3be2eb53 100644 --- a/apps/web/lib/db/schema/calendar.ts +++ b/apps/web/lib/db/schema/calendar.ts @@ -47,10 +47,10 @@ export const calendarEvents = pgTable('calendar_events', { updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), deletedAt: timestamp('deleted_at', { withTimezone: true }), }, (t) => [ - index('calendar_events_org_range_idx').on(t.instanceId, t.startsAt, t.endsAt), - index('calendar_events_org_series_idx').on(t.instanceId, t.seriesId, t.recurrenceInstanceStartAt), - uniqueIndex('calendar_events_org_client_request_idx').on(t.instanceId, t.clientRequestId), - uniqueIndex('calendar_events_org_series_occurrence_idx') + index('calendar_events_instance_range_idx').on(t.instanceId, t.startsAt, t.endsAt), + index('calendar_events_instance_series_idx').on(t.instanceId, t.seriesId, t.recurrenceInstanceStartAt), + uniqueIndex('calendar_events_instance_client_request_idx').on(t.instanceId, t.clientRequestId), + uniqueIndex('calendar_events_instance_series_occurrence_idx') .on(t.instanceId, t.seriesId, t.recurrenceInstanceStartAt) .where(sql`${t.seriesId} IS NOT NULL AND ${t.recurrenceInstanceStartAt} IS NOT NULL`), ]) @@ -62,7 +62,7 @@ export const calendarEventHosts = pgTable('calendar_event_hosts', { createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ primaryKey({ columns: [t.eventId, t.hostId], name: 'calendar_event_hosts_pk' }), - index('calendar_event_hosts_org_host_idx').on(t.instanceId, t.hostId), + index('calendar_event_hosts_instance_host_idx').on(t.instanceId, t.hostId), ]) export const calendarEventParticipants = pgTable('calendar_event_participants', { @@ -73,7 +73,7 @@ export const calendarEventParticipants = pgTable('calendar_event_participants', createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ primaryKey({ columns: [t.eventId, t.userId], name: 'calendar_event_participants_pk' }), - index('calendar_event_participants_org_user_idx').on(t.instanceId, t.userId), + index('calendar_event_participants_instance_user_idx').on(t.instanceId, t.userId), ]) export type CalendarEvent = typeof calendarEvents.$inferSelect diff --git a/apps/web/lib/db/schema/certificates.ts b/apps/web/lib/db/schema/certificates.ts index 87e628eb..50075143 100644 --- a/apps/web/lib/db/schema/certificates.ts +++ b/apps/web/lib/db/schema/certificates.ts @@ -62,9 +62,9 @@ export const certificates = pgTable('certificates', { uniqueIndex('certificates_identity_idx').on( t.instanceId, t.host, t.port, t.serverName, t.fingerprintSha256, ), - index('certificates_org_expiry_idx').on(t.instanceId, t.notAfter), - index('certificates_org_status_idx').on(t.instanceId, t.status), - index('certificates_org_host_idx').on(t.instanceId, t.discoveredByHostId), + index('certificates_instance_expiry_idx').on(t.instanceId, t.notAfter), + index('certificates_instance_status_idx').on(t.instanceId, t.status), + index('certificates_instance_host_idx').on(t.instanceId, t.discoveredByHostId), index('certificates_refresh_due_idx').on(t.trackedUrl, t.lastRefreshedAt), ]) @@ -80,7 +80,7 @@ export const certificateEvents = pgTable('certificate_events', { metadata: jsonb('metadata'), }, (t) => [ index('cert_events_cert_time_idx').on(t.certificateId, t.occurredAt), - index('cert_events_org_time_idx').on(t.instanceId, t.occurredAt), + index('cert_events_instance_time_idx').on(t.instanceId, t.occurredAt), ]) export type Certificate = typeof certificates.$inferSelect diff --git a/apps/web/lib/db/schema/checks.ts b/apps/web/lib/db/schema/checks.ts index 2bb42139..a9697af3 100644 --- a/apps/web/lib/db/schema/checks.ts +++ b/apps/web/lib/db/schema/checks.ts @@ -65,7 +65,7 @@ export const checks = pgTable('checks', { deletedAt: timestamp('deleted_at', { withTimezone: true }), metadata: jsonb('metadata'), }, (table) => [ - index('checks_org_host_idx').on(table.instanceId, table.hostId), + index('checks_instance_host_idx').on(table.instanceId, table.hostId), ]) export const checkResults = pgTable('check_results', { diff --git a/apps/web/lib/db/schema/docker.ts b/apps/web/lib/db/schema/docker.ts index cb428db2..6b287071 100644 --- a/apps/web/lib/db/schema/docker.ts +++ b/apps/web/lib/db/schema/docker.ts @@ -30,7 +30,7 @@ export const hostDockerStatus = pgTable( }, (t) => [ uniqueIndex('host_docker_status_host_uidx').on(t.hostId), - index('host_docker_status_org_status_checked_idx').on(t.instanceId, t.status, t.checkedAt), + index('host_docker_status_instance_status_checked_idx').on(t.instanceId, t.status, t.checkedAt), ], ) @@ -68,8 +68,8 @@ export const dockerContainers = pgTable( }, (t) => [ uniqueIndex('docker_containers_host_container_uidx').on(t.hostId, t.dockerContainerId), - index('docker_containers_org_host_present_seen_idx').on(t.instanceId, t.hostId, t.isPresent, t.lastSeenAt), - index('docker_containers_org_image_idx').on(t.instanceId, t.image), + index('docker_containers_instance_host_present_seen_idx').on(t.instanceId, t.hostId, t.isPresent, t.lastSeenAt), + index('docker_containers_instance_image_idx').on(t.instanceId, t.image), ], ) @@ -107,8 +107,8 @@ export const dockerContainerLifecycleEvents = pgTable( }, (t) => [ uniqueIndex('docker_container_lifecycle_events_host_event_uidx').on(t.hostId, t.dockerContainerId, t.eventType, t.occurredAt), - index('docker_container_lifecycle_events_org_host_time_idx').on(t.instanceId, t.hostId, t.occurredAt), - index('docker_container_lifecycle_events_org_container_time_idx').on(t.instanceId, t.dockerContainerRowId, t.occurredAt), + index('docker_container_lifecycle_events_instance_host_time_idx').on(t.instanceId, t.hostId, t.occurredAt), + index('docker_container_lifecycle_events_instance_container_time_idx').on(t.instanceId, t.dockerContainerRowId, t.occurredAt), ], ) @@ -144,8 +144,8 @@ export const dockerContainerMetrics = pgTable( }, (t) => [ primaryKey({ columns: [t.id, t.recordedAt] }), - index('docker_container_metrics_org_host_time_idx').on(t.instanceId, t.hostId, t.recordedAt), - index('docker_container_metrics_org_container_time_idx').on(t.instanceId, t.dockerContainerRowId, t.recordedAt), + index('docker_container_metrics_instance_host_time_idx').on(t.instanceId, t.hostId, t.recordedAt), + index('docker_container_metrics_instance_container_time_idx').on(t.instanceId, t.dockerContainerRowId, t.recordedAt), ], ) diff --git a/apps/web/lib/db/schema/domain-accounts.ts b/apps/web/lib/db/schema/domain-accounts.ts index d783e997..92c3ee72 100644 --- a/apps/web/lib/db/schema/domain-accounts.ts +++ b/apps/web/lib/db/schema/domain-accounts.ts @@ -19,8 +19,8 @@ export const domainAccounts = pgTable('domain_accounts', { deletedAt: timestamp('deleted_at', { withTimezone: true }), metadata: jsonb('metadata'), }, (t) => [ - uniqueIndex('domain_accounts_org_username_idx').on(t.instanceId, t.username), - index('domain_accounts_org_status_idx').on(t.instanceId, t.status), + uniqueIndex('domain_accounts_instance_username_idx').on(t.instanceId, t.username), + index('domain_accounts_instance_status_idx').on(t.instanceId, t.status), ]) export type DomainAccount = typeof domainAccounts.$inferSelect diff --git a/apps/web/lib/db/schema/hosts.ts b/apps/web/lib/db/schema/hosts.ts index 7105f5c3..90ef0eb5 100644 --- a/apps/web/lib/db/schema/hosts.ts +++ b/apps/web/lib/db/schema/hosts.ts @@ -140,7 +140,7 @@ export interface HostMetadata { lastSoftwareScanAt?: string // ISO timestamp; avoid Date in JSONB (use .toISOString()) dockerSettings?: HostDockerSettings // Tags supplied via the agent CLI --tag flag / token metadata at register - // time. Stashed here until approveAgent merges them with org defaults and + // time. Stashed here until approveAgent merges them with instance defaults and // writes canonical rows into resource_tags. pendingTags?: Array<{ key: string; value: string }> } diff --git a/apps/web/lib/db/schema/instance-settings.ts b/apps/web/lib/db/schema/instance-settings.ts index c41f3369..88978f63 100644 --- a/apps/web/lib/db/schema/instance-settings.ts +++ b/apps/web/lib/db/schema/instance-settings.ts @@ -8,7 +8,7 @@ import { normaliseFeatureFlagOverrides, type FeatureFlagOverrides } from '../../ export interface InstanceNotificationSettings { inAppEnabled?: boolean // default true — master switch for in-app notifications - inAppRoles?: string[] // default ['super_admin','org_admin','engineer'] + inAppRoles?: string[] // default ['super_admin','instance_admin','engineer'] allowUserOptOut?: boolean // default true — whether users can individually opt out smtpRelay?: SmtpRelaySettings } diff --git a/apps/web/lib/db/schema/metadata-validation.test.mjs b/apps/web/lib/db/schema/metadata-validation.test.mjs index d16d341f..a8a7cbec 100644 --- a/apps/web/lib/db/schema/metadata-validation.test.mjs +++ b/apps/web/lib/db/schema/metadata-validation.test.mjs @@ -110,7 +110,7 @@ test('parseInstanceMetadata preserves valid settings and drops malformed nested assert.deepEqual(parsed.defaultTags, []) assert.deepEqual(parsed.notificationSettings, { inAppEnabled: true, - inAppRoles: ['super_admin', 'org_admin', 'engineer'], + inAppRoles: ['super_admin', 'instance_admin', 'engineer'], allowUserOptOut: false, }) assert.deepEqual(parsed.softwareInventorySettings, { diff --git a/apps/web/lib/db/schema/metrics.ts b/apps/web/lib/db/schema/metrics.ts index 03ff3656..ebf02bf7 100644 --- a/apps/web/lib/db/schema/metrics.ts +++ b/apps/web/lib/db/schema/metrics.ts @@ -22,10 +22,10 @@ export const hostMetrics = pgTable( }, (t) => [ primaryKey({ columns: [t.id, t.recordedAt] }), - // Composite index for the most common query pattern: org + host within a time range. + // Composite index for the most common query pattern: instance + host within a time range. // Critical for TimescaleDB hypertables — without this every time-series query falls // back to a full table scan, which degrades the entire cluster for all instances. - index('host_metrics_org_host_time_idx').on( + index('host_metrics_instance_host_time_idx').on( t.instanceId, t.hostId, t.recordedAt, diff --git a/apps/web/lib/db/schema/notes.ts b/apps/web/lib/db/schema/notes.ts index db9ea04b..bbf2d860 100644 --- a/apps/web/lib/db/schema/notes.ts +++ b/apps/web/lib/db/schema/notes.ts @@ -75,7 +75,7 @@ export const notes = pgTable( metadata: jsonb('metadata').$type(), }, (t) => [ - index('notes_org_active_updated_idx').on(t.instanceId, t.deletedAt, t.updatedAt), + index('notes_instance_active_updated_idx').on(t.instanceId, t.deletedAt, t.updatedAt), index('notes_author_idx').on(t.authorId), index('notes_search_vector_idx').using('gin', t.searchVector), ], diff --git a/apps/web/lib/db/schema/patch-status.ts b/apps/web/lib/db/schema/patch-status.ts index 7fcc4a7d..fb077079 100644 --- a/apps/web/lib/db/schema/patch-status.ts +++ b/apps/web/lib/db/schema/patch-status.ts @@ -30,7 +30,7 @@ export const hostPatchStatuses = pgTable( }, (t) => [ uniqueIndex('host_patch_statuses_check_uniq').on(t.checkId), - index('host_patch_statuses_org_status_idx').on(t.instanceId, t.status), + index('host_patch_statuses_instance_status_idx').on(t.instanceId, t.status), index('host_patch_statuses_host_checked_idx').on(t.hostId, t.checkedAt), ], ) @@ -64,7 +64,7 @@ export const hostPackageUpdates = pgTable( t.architecture, t.packageManager, ), - index('host_package_updates_org_status_idx').on(t.instanceId, t.status), + index('host_package_updates_instance_status_idx').on(t.instanceId, t.status), index('host_package_updates_host_status_idx').on(t.hostId, t.status), ], ) diff --git a/apps/web/lib/db/schema/service-accounts.ts b/apps/web/lib/db/schema/service-accounts.ts index bf6a3185..45f0cad4 100644 --- a/apps/web/lib/db/schema/service-accounts.ts +++ b/apps/web/lib/db/schema/service-accounts.ts @@ -41,9 +41,9 @@ export const serviceAccounts = pgTable('service_accounts', { metadata: jsonb('metadata'), }, (t) => [ uniqueIndex('service_accounts_identity_idx').on(t.instanceId, t.hostId, t.username), - index('service_accounts_org_type_idx').on(t.instanceId, t.accountType), - index('service_accounts_org_status_idx').on(t.instanceId, t.status), - index('service_accounts_org_host_idx').on(t.instanceId, t.hostId), + index('service_accounts_instance_type_idx').on(t.instanceId, t.accountType), + index('service_accounts_instance_status_idx').on(t.instanceId, t.status), + index('service_accounts_instance_host_idx').on(t.instanceId, t.hostId), ]) export const sshKeys = pgTable('ssh_keys', { @@ -68,10 +68,10 @@ export const sshKeys = pgTable('ssh_keys', { metadata: jsonb('metadata'), }, (t) => [ uniqueIndex('ssh_keys_identity_idx').on(t.instanceId, t.hostId, t.fingerprintSha256, t.filePath), - index('ssh_keys_org_fingerprint_idx').on(t.instanceId, t.fingerprintSha256), - index('ssh_keys_org_type_idx').on(t.instanceId, t.keyType), - index('ssh_keys_org_status_idx').on(t.instanceId, t.status), - index('ssh_keys_org_host_idx').on(t.instanceId, t.hostId), + index('ssh_keys_instance_fingerprint_idx').on(t.instanceId, t.fingerprintSha256), + index('ssh_keys_instance_type_idx').on(t.instanceId, t.keyType), + index('ssh_keys_instance_status_idx').on(t.instanceId, t.status), + index('ssh_keys_instance_host_idx').on(t.instanceId, t.hostId), index('ssh_keys_account_idx').on(t.serviceAccountId), ]) @@ -86,7 +86,7 @@ export const identityEvents = pgTable('identity_events', { occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull().defaultNow(), metadata: jsonb('metadata'), }, (t) => [ - index('identity_events_org_time_idx').on(t.instanceId, t.occurredAt), + index('identity_events_instance_time_idx').on(t.instanceId, t.occurredAt), index('identity_events_account_time_idx').on(t.serviceAccountId, t.occurredAt), index('identity_events_key_time_idx').on(t.sshKeyId, t.occurredAt), index('identity_events_host_time_idx').on(t.hostId, t.occurredAt), diff --git a/apps/web/lib/db/schema/software.ts b/apps/web/lib/db/schema/software.ts index 5b5311a3..55756f19 100644 --- a/apps/web/lib/db/schema/software.ts +++ b/apps/web/lib/db/schema/software.ts @@ -18,7 +18,7 @@ export type PackageSource = | 'other' /** - * One row per (org, host, name, version, architecture) combination. + * One row per (instance, host, name, version, architecture) combination. * Packages are upserted on each scan; rows not seen in the latest scan get * `removed_at` stamped instead of being deleted, so `first_seen_at` is stable * for "new in last N days" queries. @@ -58,7 +58,7 @@ export const softwarePackages = pgTable( }, (t) => [ uniqueIndex('sw_pkg_uniq').on(t.instanceId, t.hostId, t.name, t.version, t.architecture), - index('sw_pkg_org_name_idx').on(t.instanceId, t.name), + index('sw_pkg_instance_name_idx').on(t.instanceId, t.name), index('sw_pkg_source_name_idx').on(t.source, t.distroId, t.distroCodename, t.sourceName), index('sw_pkg_host_idx').on(t.hostId), index('sw_pkg_first_seen_idx').on(t.instanceId, t.firstSeenAt), diff --git a/apps/web/lib/db/schema/tags.ts b/apps/web/lib/db/schema/tags.ts index 0e0aaad7..a6cd2805 100644 --- a/apps/web/lib/db/schema/tags.ts +++ b/apps/web/lib/db/schema/tags.ts @@ -3,7 +3,7 @@ import { sql } from 'drizzle-orm' import { createId } from '@paralleldrive/cuid2' import { instanceSettings } from './instance-settings.ts' -// Normalised tag catalogue. One row per distinct (org, key, value) — resource +// Normalised tag catalogue. One row per distinct (instance, key, value) — resource // assignments reference this table via resource_tags.tag_id. The case-insensitive // unique index prevents near-duplicate spellings ("prod" vs "Prod") from // coexisting; the UI powers autocomplete off these rows so users are nudged @@ -21,12 +21,12 @@ export const tags = pgTable( createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ - uniqueIndex('tags_org_key_value_ci_uidx').on( + uniqueIndex('tags_instance_key_value_ci_uidx').on( table.instanceId, sql`lower(${table.key})`, sql`lower(${table.value})`, ), - index('tags_org_key_idx').on(table.instanceId, table.key), + index('tags_instance_key_idx').on(table.instanceId, table.key), ], ) diff --git a/apps/web/lib/db/schema/terminal-sessions.ts b/apps/web/lib/db/schema/terminal-sessions.ts index e50a0803..9c87b890 100644 --- a/apps/web/lib/db/schema/terminal-sessions.ts +++ b/apps/web/lib/db/schema/terminal-sessions.ts @@ -23,7 +23,7 @@ export const terminalSessions = pgTable('terminal_sessions', { createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ - index('terminal_sessions_org_host_idx').on(t.instanceId, t.hostId), + index('terminal_sessions_instance_host_idx').on(t.instanceId, t.hostId), index('terminal_sessions_session_id_idx').on(t.sessionId), ]) diff --git a/apps/web/lib/db/schema/vulnerabilities.ts b/apps/web/lib/db/schema/vulnerabilities.ts index e1205dee..fee26f8e 100644 --- a/apps/web/lib/db/schema/vulnerabilities.ts +++ b/apps/web/lib/db/schema/vulnerabilities.ts @@ -55,7 +55,7 @@ export const hostVulnerabilityFindings = pgTable('host_vulnerability_findings', updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ uniqueIndex('host_vuln_findings_uniq').on(t.instanceId, t.hostId, t.softwarePackageId, t.cveId), - index('host_vuln_findings_org_status_idx').on(t.instanceId, t.status, t.severity), + index('host_vuln_findings_instance_status_idx').on(t.instanceId, t.status, t.severity), index('host_vuln_findings_host_status_idx').on(t.hostId, t.status), index('host_vuln_findings_cve_idx').on(t.cveId), index('host_vuln_findings_confidence_idx').on(t.instanceId, t.status, t.confidence), diff --git a/apps/web/lib/hosts/filter.ts b/apps/web/lib/hosts/filter.ts index a297c721..fa243468 100644 --- a/apps/web/lib/hosts/filter.ts +++ b/apps/web/lib/hosts/filter.ts @@ -22,7 +22,7 @@ export interface HostFilterResult { } // Builds a Drizzle WHERE clause for a host filter. All supplied fields AND -// together. Returned SQL is always org-scoped and excludes soft-deleted hosts. +// together. Returned SQL is always instance-scoped and excludes soft-deleted hosts. export function buildHostFilterWhere(instanceId: string, filter: HostFilter): SQL | undefined { const conditions: SQL[] = [ eq(hosts.instanceId, instanceId), diff --git a/apps/web/lib/hosts/host-detail-tabs.test.mjs b/apps/web/lib/hosts/host-detail-tabs.test.mjs index fda286d7..ddfea88f 100644 --- a/apps/web/lib/hosts/host-detail-tabs.test.mjs +++ b/apps/web/lib/hosts/host-detail-tabs.test.mjs @@ -23,13 +23,13 @@ test('container inventory is available as a top-level host tab', () => { assert.equal(inventory?.children?.includes('containers'), false) }) -test('notes live under the top-level admin host tab', () => { +test('admin host tools live under the top-level admin host tab', () => { const admin = PARENT_TABS.find((tab) => tab.id === 'admin') const notes = PARENT_TABS.find((tab) => tab.id === 'notes') assert.ok(admin) assert.equal(admin.label, 'Admin') assert.equal(admin.defaultTab, 'notes') - assert.deepEqual(admin.children, ['notes']) + assert.deepEqual(admin.children, ['notes', 'calendar']) assert.equal(notes, undefined) }) diff --git a/apps/web/lib/integrations/ct-cve/connection-status.test.mjs b/apps/web/lib/integrations/ct-cve/connection-status.test.mjs index 11044edb..c173703b 100644 --- a/apps/web/lib/integrations/ct-cve/connection-status.test.mjs +++ b/apps/web/lib/integrations/ct-cve/connection-status.test.mjs @@ -14,11 +14,11 @@ function repo(initial = null) { return { repository: { async get(instanceId) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') return stored }, async save(status) { - assert.equal(status.instanceId, 'org_1') + assert.equal(status.instanceId, 'instance_1') stored = status }, }, @@ -29,11 +29,11 @@ function repo(initial = null) { test('returns default CT-CVE connection status when no durable row exists', async () => { const { repository } = repo() - const status = await getCtCveConnectionStatus('org_1', { configured: false, repository }) + const status = await getCtCveConnectionStatus('instance_1', { configured: false, repository }) assert.deepEqual(status, { contractVersion: '2026-04-30', - instanceId: 'org_1', + instanceId: 'instance_1', configured: false, enabled: true, lastInventoryPushAt: null, @@ -47,26 +47,26 @@ test('returns default CT-CVE connection status when no durable row exists', asyn test('records health, finding ingest, inventory push, and clears stale errors on successful data flow', async () => { const { repository, stored } = repo() - await recordCtCveConnectionHealth('org_1', { + await recordCtCveConnectionHealth('instance_1', { repository, now: new Date('2026-04-30T11:00:00.000Z'), }) - await recordCtCveConnectionError('org_1', 'inventory_push_failed', { + await recordCtCveConnectionError('instance_1', 'inventory_push_failed', { repository, now: new Date('2026-04-30T11:01:00.000Z'), }) - await recordCtCveFindingIngest('org_1', { + await recordCtCveFindingIngest('instance_1', { repository, now: new Date('2026-04-30T11:02:00.000Z'), }) - await recordCtCveInventoryPush('org_1', { + await recordCtCveInventoryPush('instance_1', { repository, now: new Date('2026-04-30T11:03:00.000Z'), }) assert.deepEqual(stored(), { contractVersion: '2026-04-30', - instanceId: 'org_1', + instanceId: 'instance_1', configured: true, enabled: true, lastInventoryPushAt: '2026-04-30T11:03:00.000Z', diff --git a/apps/web/lib/integrations/ct-cve/connector-settings.test.mjs b/apps/web/lib/integrations/ct-cve/connector-settings.test.mjs index 81caa35a..030ec57b 100644 --- a/apps/web/lib/integrations/ct-cve/connector-settings.test.mjs +++ b/apps/web/lib/integrations/ct-cve/connector-settings.test.mjs @@ -19,14 +19,14 @@ test('normalises CT-CVE connector settings and generates missing first-save secr ] const saved = normaliseCtCveConnectorSettingsForSave({ - instanceId: 'org_123', + instanceId: 'instance_123', input: { enabled: true, name: ' Production CT-CVE ', baseUrl: 'https://ct-cve.example.invalid/api/?ignored=1#section', - inventoryTokenId: ' ctops_inventory_org_123 ', + inventoryTokenId: ' inventory-token-id ', inventoryTokenSecret: '', - ctCveTokenId: ' ctcve_findings_org_123 ', + ctCveTokenId: ' finding-token-id ', ctCveTokenSecret: '', }, generateSecret: () => generated.shift() ?? 'unexpected', @@ -34,27 +34,27 @@ test('normalises CT-CVE connector settings and generates missing first-save secr }) assert.deepEqual(saved, { - instanceId: 'org_123', + instanceId: 'instance_123', enabled: true, name: 'Production CT-CVE', baseUrl: 'https://ct-cve.example.invalid/api', - inventoryTokenId: 'ctops_inventory_org_123', + inventoryTokenId: 'inventory-token-id', inventoryTokenSecretEncrypted: `enc:${Buffer.from('generated inventory secret for first save').toString('base64url')}`, - ctCveTokenId: 'ctcve_findings_org_123', + ctCveTokenId: 'finding-token-id', ctCveTokenSecretEncrypted: `enc:${Buffer.from('generated ct-cve secret for first save').toString('base64url')}`, }) }) test('preserves stored secrets when update form leaves secret fields blank', () => { const saved = normaliseCtCveConnectorSettingsForSave({ - instanceId: 'org_123', + instanceId: 'instance_123', input: { enabled: false, name: 'Primary CT-CVE', baseUrl: 'https://ct-cve.example.invalid/', - inventoryTokenId: 'ctops_inventory_org_123', + inventoryTokenId: 'inventory-token-id', inventoryTokenSecret: ' ', - ctCveTokenId: 'ctcve_findings_org_123', + ctCveTokenId: 'finding-token-id', ctCveTokenSecret: '', }, existing: { @@ -80,14 +80,14 @@ test('rejects invalid connector URL, token IDs, and weak secrets', () => { ) assert.throws( () => normaliseCtCveConnectorSettingsForSave({ - instanceId: 'org_123', + instanceId: 'instance_123', input: { enabled: true, name: 'Primary CT-CVE', baseUrl: 'https://ct-cve.example.invalid', inventoryTokenId: 'not a token id', inventoryTokenSecret: inventorySecret, - ctCveTokenId: 'ctcve_findings_org_123', + ctCveTokenId: 'finding-token-id', ctCveTokenSecret: ctCveSecret, }, encryptSecret: (value) => value, @@ -96,14 +96,14 @@ test('rejects invalid connector URL, token IDs, and weak secrets', () => { ) assert.throws( () => normaliseCtCveConnectorSettingsForSave({ - instanceId: 'org_123', + instanceId: 'instance_123', input: { enabled: true, name: 'Primary CT-CVE', baseUrl: 'https://ct-cve.example.invalid', - inventoryTokenId: 'ctops_inventory_org_123', + inventoryTokenId: 'inventory-token-id', inventoryTokenSecret: 'short', - ctCveTokenId: 'ctcve_findings_org_123', + ctCveTokenId: 'finding-token-id', ctCveTokenSecret: ctCveSecret, }, encryptSecret: (value) => value, @@ -114,13 +114,13 @@ test('rejects invalid connector URL, token IDs, and weak secrets', () => { test('converts stored connector settings into both CTOPS runtime tokens and CT-CVE config', () => { const settings = { - instanceId: 'org_123', + instanceId: 'instance_123', enabled: true, name: 'Primary CT-CVE', baseUrl: 'https://ct-cve.example.invalid', - inventoryTokenId: 'ctops_inventory_org_123', + inventoryTokenId: 'inventory-token-id', inventoryTokenSecret: inventorySecret, - ctCveTokenId: 'ctcve_findings_org_123', + ctCveTokenId: 'finding-token-id', ctCveTokenSecret: ctCveSecret, } @@ -129,32 +129,32 @@ test('converts stored connector settings into both CTOPS runtime tokens and CT-C enabled: true, baseUrl: 'https://ct-cve.example.invalid', token: { - id: 'ctops_inventory_org_123', + id: 'inventory-token-id', secret: inventorySecret, - instanceId: 'org_123', + instanceId: 'instance_123', scopes: ['inventory:write', 'connection:read'], }, }) assert.deepEqual(toCtCveServiceToken(settings), { - id: 'ctcve_findings_org_123', + id: 'finding-token-id', secret: ctCveSecret, - instanceId: 'org_123', + instanceId: 'instance_123', scopes: ['findings:write', 'connection:read'], revoked: false, }) assert.deepEqual(buildCtCveCtOpsConnectionConfig(settings, 'https://ct-ops.example.invalid/settings'), { name: 'Primary CT-CVE', - instanceId: 'org_123', + instanceId: 'instance_123', ctOpsBaseUrl: 'https://ct-ops.example.invalid', inventoryTokens: [{ - id: 'ctops_inventory_org_123', + id: 'inventory-token-id', secret: inventorySecret, scopes: ['inventory:write', 'connection:read'], }], ctOpsToken: { - id: 'ctcve_findings_org_123', + id: 'finding-token-id', secret: ctCveSecret, scopes: ['findings:write', 'connection:read'], }, diff --git a/apps/web/lib/integrations/ct-cve/connector-settings.ts b/apps/web/lib/integrations/ct-cve/connector-settings.ts index 7929994f..3e26b267 100644 --- a/apps/web/lib/integrations/ct-cve/connector-settings.ts +++ b/apps/web/lib/integrations/ct-cve/connector-settings.ts @@ -90,7 +90,7 @@ interface NormaliseForSaveOptions { } export function defaultCtCveConnectorTokenId(prefix: string, instanceId: string): string { - const suffix = instanceId.replace(/[^A-Za-z0-9._:-]/g, '_').replace(/^_+|_+$/g, '') || 'org' + const suffix = instanceId.replace(/[^A-Za-z0-9._:-]/g, '_').replace(/^_+|_+$/g, '') || 'instance' return `${prefix}_${suffix}`.slice(0, 128) } @@ -234,7 +234,7 @@ function rowToFull(row: CtCveConnectorSettings): CtCveConnectorSettingsFull { } } -async function getConnectorRowForOrg(instanceId: string): Promise { +async function getConnectorRowForInstance(instanceId: string): Promise { const { db } = await import('../../db/index.ts') const row = await db.query.ctCveConnectorSettings.findFirst({ where: eq(ctCveConnectorSettings.instanceId, instanceId), @@ -244,15 +244,15 @@ async function getConnectorRowForOrg(instanceId: string): Promise { const { db } = await import('../../db/index.ts') - const orgs = await db.query.instanceSettings.findMany({ + const instances = await db.query.instanceSettings.findMany({ where: isNull(instanceSettings.deletedAt), columns: { id: true }, orderBy: [asc(instanceSettings.id)], }) const rows: CtCveConnectorSettings[] = [] - for (const org of orgs) { - const row = await getConnectorRowForOrg(org.id) + for (const instance of instances) { + const row = await getConnectorRowForInstance(instance.id) if (row) rows.push(row) } return rows @@ -261,14 +261,14 @@ async function listConnectorRowsAcrossInstances(): Promise { - const row = await getConnectorRowForOrg(instanceId) + const row = await getConnectorRowForInstance(instanceId) return row ? rowToSummary(row) : null } export async function getCtCveConnectorSettingsForAdmin( instanceId: string, ): Promise { - const row = await getConnectorRowForOrg(instanceId) + const row = await getConnectorRowForInstance(instanceId) return row ? rowToFull(row) : null } @@ -276,7 +276,7 @@ export async function saveCtCveConnectorSettings( instanceId: string, input: CtCveConnectorSettingsInput, ): Promise { - const existing = await getConnectorRowForOrg(instanceId) + const existing = await getConnectorRowForInstance(instanceId) const now = new Date() const values = normaliseCtCveConnectorSettingsForSave({ instanceId, @@ -306,7 +306,7 @@ export async function saveCtCveConnectorSettings( }, }) - const saved = await getConnectorRowForOrg(instanceId) + const saved = await getConnectorRowForInstance(instanceId) if (!saved) { throw new Error('Failed to save CT-CVE connector settings') } @@ -346,11 +346,11 @@ export function toCtCveServiceToken(settings: CtCveConnectorSettingsFull): CtCve } } -export async function getCtCveServiceTokensForOrg( +export async function getCtCveServiceTokensForInstance( instanceId: string, env: NodeJS.ProcessEnv = process.env, ): Promise { - const row = await getConnectorRowForOrg(instanceId) + const row = await getConnectorRowForInstance(instanceId) if (row) { if (!row.enabled) return [] try { diff --git a/apps/web/lib/integrations/ct-cve/finding-ingest.test.mjs b/apps/web/lib/integrations/ct-cve/finding-ingest.test.mjs index 5aeceac6..c17f3260 100644 --- a/apps/web/lib/integrations/ct-cve/finding-ingest.test.mjs +++ b/apps/web/lib/integrations/ct-cve/finding-ingest.test.mjs @@ -40,8 +40,8 @@ function finding(overrides = {}) { function batch(findings = [finding()], overrides = {}) { return { contractVersion: '2026-04-30', - instanceId: 'org_1', - batchId: 'findings_20260430_092000_org_1', + instanceId: 'instance_1', + batchId: 'findings_20260430_092000_instance_1', generatedAt, findings, ...overrides, @@ -50,8 +50,8 @@ function batch(findings = [finding()], overrides = {}) { function repo(overrides = {}) { const state = { - hosts: new Map([['host_1', { id: 'host_1', instanceId: 'org_1', deletedAt: null }]]), - packages: new Map([['pkg_1', { id: 'pkg_1', instanceId: 'org_1', hostId: 'host_1', removedAt: null, deletedAt: null }]]), + hosts: new Map([['host_1', { id: 'host_1', instanceId: 'instance_1', deletedAt: null }]]), + packages: new Map([['pkg_1', { id: 'pkg_1', instanceId: 'instance_1', hostId: 'host_1', removedAt: null, deletedAt: null }]]), existing: new Map(), cves: [], findings: [], @@ -90,7 +90,7 @@ test('ingests a valid CT-CVE finding batch', async () => { assert.deepEqual(result, { accepted: true, - batchId: 'findings_20260430_092000_org_1', + batchId: 'findings_20260430_092000_instance_1', findingsAccepted: 1, findingsRejected: 0, findingsSkipped: 0, @@ -98,7 +98,7 @@ test('ingests a valid CT-CVE finding batch', async () => { assert.equal(state.cves.length, 1) assert.equal(state.findings.length, 1) assert.equal(state.findings[0].metadata.ctCveFindingId, 'ctcve_find_1') - assert.equal(state.findings[0].metadata.ctCveBatchId, 'findings_20260430_092000_org_1') + assert.equal(state.findings[0].metadata.ctCveBatchId, 'findings_20260430_092000_instance_1') }) test('rejects open findings for unknown or deleted hosts and inactive packages', async () => { diff --git a/apps/web/lib/integrations/ct-cve/inventory-export.test.mjs b/apps/web/lib/integrations/ct-cve/inventory-export.test.mjs index cfd342c6..edcacc18 100644 --- a/apps/web/lib/integrations/ct-cve/inventory-export.test.mjs +++ b/apps/web/lib/integrations/ct-cve/inventory-export.test.mjs @@ -11,18 +11,18 @@ const generatedAt = new Date('2026-04-30T10:15:00.000Z') const token = { id: 'ctops_inventory_token', secret: Buffer.from('ct-ops outbound inventory signing key only').toString('base64url'), - instanceId: 'org_1', + instanceId: 'instance_1', scopes: ['inventory:write'], } function repo() { return { async getInstance(instanceId) { - assert.equal(instanceId, 'org_1') - return { id: 'org_1', slug: 'acme' } + assert.equal(instanceId, 'instance_1') + return { id: 'instance_1', slug: 'acme' } }, async listInventoryHosts(instanceId, options) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') assert.equal(options.limit, 500) return [ { @@ -52,7 +52,7 @@ function repo() { ] }, async listInventoryPackages(instanceId, options) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') assert.equal(options.limit, 25_000) return [ { @@ -109,11 +109,11 @@ function statusRepo() { return { repository: { async get(instanceId) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') return stored }, async save(status) { - assert.equal(status.instanceId, 'org_1') + assert.equal(status.instanceId, 'instance_1') stored = status }, }, @@ -121,16 +121,16 @@ function statusRepo() { } } -test('builds an org-scoped CT-CVE inventory snapshot without deleted or removed rows', async () => { +test('builds an instance-scoped CT-CVE inventory snapshot without deleted or removed rows', async () => { const snapshot = await buildCtCveInventorySnapshot({ - instanceId: 'org_1', + instanceId: 'instance_1', repository: repo(), generatedAt, }) assert.equal(snapshot.contractVersion, '2026-04-30') - assert.equal(snapshot.instanceId, 'org_1') - assert.equal(snapshot.orgSlug, 'acme') + assert.equal(snapshot.instanceId, 'instance_1') + assert.equal(snapshot.instanceSlug, 'acme') assert.equal(snapshot.snapshotType, 'full') assert.equal(snapshot.generatedAt, generatedAt.toISOString()) assert.equal(snapshot.hosts.length, 1) @@ -144,11 +144,11 @@ test('builds an org-scoped CT-CVE inventory snapshot without deleted or removed test('adds an opaque cursor when either inventory page reaches its limit', async () => { const repository = repo() const snapshot = await buildCtCveInventorySnapshot({ - instanceId: 'org_1', + instanceId: 'instance_1', repository: { ...repository, async listInventoryHosts() { - return [(await repository.listInventoryHosts('org_1', { limit: 500 }))[0]] + return [(await repository.listInventoryHosts('instance_1', { limit: 500 }))[0]] }, }, limits: { hosts: 1, packages: 25_000 }, @@ -157,7 +157,7 @@ test('adds an opaque cursor when either inventory page reaches its limit', async assert.match(snapshot.cursor ?? '', /^[A-Za-z0-9_-]+$/) const next = await buildCtCveInventorySnapshot({ - instanceId: 'org_1', + instanceId: 'instance_1', repository, cursor: snapshot.cursor ?? undefined, generatedAt, @@ -168,7 +168,7 @@ test('adds an opaque cursor when either inventory page reaches its limit', async test('pushes a snapshot to CT-CVE with the inventory service-token signature', async () => { const snapshot = await buildCtCveInventorySnapshot({ - instanceId: 'org_1', + instanceId: 'instance_1', repository: repo(), generatedAt, }) @@ -217,7 +217,7 @@ test('pushes a snapshot to CT-CVE with the inventory service-token signature', a test('records a CT-CVE inventory connection error when the push fails', async () => { const snapshot = await buildCtCveInventorySnapshot({ - instanceId: 'org_1', + instanceId: 'instance_1', repository: repo(), generatedAt, }) diff --git a/apps/web/lib/integrations/ct-cve/inventory-export.ts b/apps/web/lib/integrations/ct-cve/inventory-export.ts index d842de10..6f099781 100644 --- a/apps/web/lib/integrations/ct-cve/inventory-export.ts +++ b/apps/web/lib/integrations/ct-cve/inventory-export.ts @@ -53,7 +53,7 @@ export interface CtCveInventoryRepository { export interface CtCveInventorySnapshot { contractVersion: '2026-04-30' instanceId: string - orgSlug: string + instanceSlug: string snapshotId: string snapshotType: 'full' | 'incremental' generatedAt: string @@ -193,7 +193,7 @@ export async function buildCtCveInventorySnapshot(options: { return { contractVersion: CONTRACT_VERSION, instanceId, - orgSlug: instance.slug, + instanceSlug: instance.slug, snapshotId: snapshotIdFor(instanceId, generatedAt, options.cursor), snapshotType: options.snapshotType ?? 'full', generatedAt: generatedAt.toISOString(), diff --git a/apps/web/lib/integrations/ct-cve/inventory-push-job.test.mjs b/apps/web/lib/integrations/ct-cve/inventory-push-job.test.mjs index 61845952..7a7034c7 100644 --- a/apps/web/lib/integrations/ct-cve/inventory-push-job.test.mjs +++ b/apps/web/lib/integrations/ct-cve/inventory-push-job.test.mjs @@ -9,7 +9,7 @@ import { const token = { id: 'ctops_inventory_token', secret: Buffer.from('ct-ops outbound inventory signing key only').toString('base64url'), - instanceId: 'org_1', + instanceId: 'instance_1', scopes: ['inventory:write'], } @@ -69,7 +69,7 @@ test('pushes every paged inventory snapshot for configured targets', async () => return { contractVersion: '2026-04-30', instanceId, - orgSlug: 'acme', + instanceSlug: 'acme', snapshotId: cursor ? 'snapshot_page_2' : 'snapshot_page_1', snapshotType, generatedAt: '2026-04-30T10:00:00.000Z', @@ -93,8 +93,8 @@ test('pushes every paged inventory snapshot for configured targets', async () => }) assert.deepEqual(snapshots, [ - { instanceId: 'org_1', cursor: undefined, snapshotType: 'full' }, - { instanceId: 'org_1', cursor: 'next-page', snapshotType: 'full' }, + { instanceId: 'instance_1', cursor: undefined, snapshotType: 'full' }, + { instanceId: 'instance_1', cursor: 'next-page', snapshotType: 'full' }, ]) assert.equal(pushes.length, 2) assert.equal(pushes[0].baseUrl, 'https://ct-cve.example.invalid') @@ -126,7 +126,7 @@ test('loads CT-CVE inventory push targets from app settings when no explicit tar buildSnapshot: async ({ instanceId }) => ({ contractVersion: '2026-04-30', instanceId, - orgSlug: 'acme', + instanceSlug: 'acme', snapshotId: 'snapshot', snapshotType: 'full', generatedAt: '2026-04-30T10:00:00.000Z', @@ -178,7 +178,7 @@ test('reports target failures without stopping later targets', async () => { buildSnapshot: async ({ instanceId }) => ({ contractVersion: '2026-04-30', instanceId, - orgSlug: 'acme', + instanceSlug: 'acme', snapshotId: 'snapshot', snapshotType: 'full', generatedAt: '2026-04-30T10:00:00.000Z', diff --git a/apps/web/lib/integrations/ct-cve/service-token.test.mjs b/apps/web/lib/integrations/ct-cve/service-token.test.mjs index 73a54a89..0b1625f0 100644 --- a/apps/web/lib/integrations/ct-cve/service-token.test.mjs +++ b/apps/web/lib/integrations/ct-cve/service-token.test.mjs @@ -11,7 +11,7 @@ import { const TOKEN = { id: 'ctcve_test_token', secret: Buffer.from('ct-cve unit test signing key only').toString('base64url'), - instanceId: 'org_123', + instanceId: 'instance_123', scopes: ['findings:write', 'connection:read'], } @@ -187,7 +187,7 @@ test('rejects tokens without the required scope or instance binding', async () = assert.equal(wrongScope.ok, false) assert.equal(!wrongScope.ok && wrongScope.error.code, 'insufficient_scope') - const wrongOrg = await verifyCtCveServiceRequest({ + const wrongInstance = await verifyCtCveServiceRequest({ method: 'POST', path: '/api/integrations/ct-cve/v1/finding-batches', body, @@ -198,8 +198,8 @@ test('rejects tokens without the required scope or instance binding', async () = tokens: [TOKEN], nonceStore: createInMemoryCtCveNonceStore(), }) - assert.equal(wrongOrg.ok, false) - assert.equal(!wrongOrg.ok && wrongOrg.error.code, 'org_scope_mismatch') + assert.equal(wrongInstance.ok, false) + assert.equal(!wrongInstance.ok && wrongInstance.error.code, 'instance_scope_mismatch') }) test('parses configured CT-CVE service tokens and rejects weak secrets', () => { diff --git a/apps/web/lib/integrations/ct-cve/service-token.ts b/apps/web/lib/integrations/ct-cve/service-token.ts index 98cd1675..bda4ecd3 100644 --- a/apps/web/lib/integrations/ct-cve/service-token.ts +++ b/apps/web/lib/integrations/ct-cve/service-token.ts @@ -26,7 +26,7 @@ export interface CtCveServiceAuthError { | 'content_hash_mismatch' | 'invalid_signature' | 'insufficient_scope' - | 'org_scope_mismatch' + | 'instance_scope_mismatch' | 'replayed_nonce' message: string retryable: boolean @@ -159,7 +159,7 @@ export async function verifyCtCveServiceRequest(options: { return fail('insufficient_scope', 403, 'CT-CVE service token is not allowed to perform this action.') } if (token.instanceId !== options.instanceId) { - return fail('org_scope_mismatch', 403, 'CT-CVE service token is not scoped to the requested instance.') + return fail('instance_scope_mismatch', 403, 'CT-CVE service token is not scoped to the requested instance.') } const timestamp = getHeader(options.headers, 'x-ct-timestamp') diff --git a/apps/web/lib/integrations/ct-cve/setup-status.test.mjs b/apps/web/lib/integrations/ct-cve/setup-status.test.mjs index 326debaa..c62d355f 100644 --- a/apps/web/lib/integrations/ct-cve/setup-status.test.mjs +++ b/apps/web/lib/integrations/ct-cve/setup-status.test.mjs @@ -5,17 +5,17 @@ import { buildCtCveConnectorSetupOverview } from './setup-status.ts' test('summarises DB-backed CT-CVE connector settings without exposing token identifiers or secrets', async () => { const overview = await buildCtCveConnectorSetupOverview({ - instanceId: 'org_1', + instanceId: 'instance_1', settingsRepository: { async getSummary(instanceId) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') return { - instanceId: 'org_1', + instanceId: 'instance_1', enabled: true, name: 'Primary CT-CVE', baseUrl: 'https://ct-cve.example.invalid', - inventoryTokenId: 'ctops_inventory_org_1', - ctCveTokenId: 'ctcve_findings_org_1', + inventoryTokenId: 'inventory-token-id', + ctCveTokenId: 'finding-token-id', hasInventoryTokenSecret: true, hasCtCveTokenSecret: true, } @@ -23,10 +23,10 @@ test('summarises DB-backed CT-CVE connector settings without exposing token iden }, statusRepository: { async get(instanceId) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') return { contractVersion: '2026-04-30', - instanceId: 'org_1', + instanceId: 'instance_1', configured: true, enabled: true, lastInventoryPushAt: '2026-05-01T09:00:00.000Z', @@ -58,16 +58,16 @@ test('summarises DB-backed CT-CVE connector settings without exposing token iden targets: [{ name: 'Primary CT-CVE', baseUrl: 'https://ct-cve.example.invalid' }], error: null, }) - assert.equal(JSON.stringify(overview).includes('ctcve_findings_org_1'), false) - assert.equal(JSON.stringify(overview).includes('ctops_inventory_org_1'), false) + assert.equal(JSON.stringify(overview).includes('finding-token-id'), false) + assert.equal(JSON.stringify(overview).includes('inventory-token-id'), false) }) test('reports missing CT-CVE app settings as unconfigured', async () => { const overview = await buildCtCveConnectorSetupOverview({ - instanceId: 'org_1', + instanceId: 'instance_1', settingsRepository: { async getSummary(instanceId) { - assert.equal(instanceId, 'org_1') + assert.equal(instanceId, 'instance_1') return null }, }, diff --git a/apps/web/lib/ldap/config-client.test.mjs b/apps/web/lib/ldap/config-client.test.mjs index ccb522ff..08608ca0 100644 --- a/apps/web/lib/ldap/config-client.test.mjs +++ b/apps/web/lib/ldap/config-client.test.mjs @@ -6,7 +6,7 @@ import { sanitiseLdapConfigurationForClient } from './config-client.ts' test('sanitiseLdapConfigurationForClient omits bindPassword from client payloads', () => { const safe = sanitiseLdapConfigurationForClient({ id: 'cfg_123', - instanceId: 'org_123', + instanceId: 'instance_123', name: 'Primary LDAP', host: 'ldap.example.com', port: 636, diff --git a/apps/web/lib/licence-activation-token.ts b/apps/web/lib/licence-activation-token.ts index 09802a7a..3f182bf4 100644 --- a/apps/web/lib/licence-activation-token.ts +++ b/apps/web/lib/licence-activation-token.ts @@ -17,8 +17,8 @@ const MAX_TOKEN_AGE_DAYS = 30 export type ActivationTokenPayload = { v: number - installOrgId: string - installOrgName: string + installInstanceId: string + installInstanceName: string generatedAt: string nonce: string } @@ -46,8 +46,8 @@ export function encodeActivationToken( ): string { const payload: ActivationTokenPayload = { v: TOKEN_VERSION, - installOrgId: input.installOrgId, - installOrgName: input.installOrgName, + installInstanceId: input.installInstanceId, + installInstanceName: input.installInstanceName, generatedAt: (input.generatedAt ?? new Date()).toISOString(), nonce: input.nonce, } @@ -79,11 +79,11 @@ export function decodeActivationToken(raw: string): ActivationTokenDecodeResult if (p.v !== TOKEN_VERSION) { return { ok: false, error: `Unsupported activation token version: ${String(p.v)}` } } - if (typeof p.installOrgId !== 'string' || !p.installOrgId) { - return { ok: false, error: 'Activation token is missing installOrgId' } + if (typeof p.installInstanceId !== 'string' || !p.installInstanceId) { + return { ok: false, error: 'Activation token is missing installInstanceId' } } - if (typeof p.installOrgName !== 'string' || !p.installOrgName) { - return { ok: false, error: 'Activation token is missing installOrgName' } + if (typeof p.installInstanceName !== 'string' || !p.installInstanceName) { + return { ok: false, error: 'Activation token is missing installInstanceName' } } if (typeof p.generatedAt !== 'string' || !p.generatedAt) { return { ok: false, error: 'Activation token is missing generatedAt' } @@ -108,8 +108,8 @@ export function decodeActivationToken(raw: string): ActivationTokenDecodeResult ok: true, payload: { v: TOKEN_VERSION, - installOrgId: p.installOrgId, - installOrgName: p.installOrgName, + installInstanceId: p.installInstanceId, + installInstanceName: p.installInstanceName, generatedAt: p.generatedAt, nonce: p.nonce, }, diff --git a/apps/web/lib/licence.test.mjs b/apps/web/lib/licence.test.mjs index 8814366a..c1ae050a 100644 --- a/apps/web/lib/licence.test.mjs +++ b/apps/web/lib/licence.test.mjs @@ -49,7 +49,7 @@ async function signLicence(privateKeyPem, claims = {}, options = {}) { name: 'Example Corp', email: 'ops@example.com', }, - sub: 'org_123', + sub: 'instance_123', jti: 'lic_123', ...claims, }, diff --git a/apps/web/lib/password-manager/launch-assertion.test.mjs b/apps/web/lib/password-manager/launch-assertion.test.mjs index b98cd724..01aada7b 100644 --- a/apps/web/lib/password-manager/launch-assertion.test.mjs +++ b/apps/web/lib/password-manager/launch-assertion.test.mjs @@ -55,8 +55,8 @@ test('signPasswordManagerLaunchAssertion emits the required claims', async () => const assertion = await signPasswordManagerLaunchAssertion( { - instanceId: 'org-123', - instanceName: 'Example Org', + instanceId: 'instance-123', + instanceName: 'Example Instance', userId: 'user-456', email: 'ops@example.com', name: 'Example Operator', @@ -75,8 +75,8 @@ test('signPasswordManagerLaunchAssertion emits the required claims', async () => assert.equal(protectedHeader.alg, 'EdDSA') assert.equal(payload.product, 'ct-password-manager') assert.equal(payload.ct_ops_instance_id, 'ct-ops-prod-1') - assert.equal(payload.ct_ops_organization_id, 'org-123') - assert.equal(payload.ct_ops_organization_name, 'Example Org') + assert.equal(payload.ct_ops_instance_settings_id, 'instance-123') + assert.equal(payload.ct_ops_instance_name, 'Example Instance') assert.equal(payload.ct_ops_user_id, 'user-456') assert.equal(payload.email, 'ops@example.com') assert.equal(payload.name, 'Example Operator') @@ -95,7 +95,7 @@ test('signPasswordManagerLaunchAssertion omits the optional instance name when a const assertion = await signPasswordManagerLaunchAssertion( { - instanceId: 'org-123', + instanceId: 'instance-123', userId: 'user-456', email: 'ops@example.com', name: 'Example Operator', @@ -111,5 +111,5 @@ test('signPasswordManagerLaunchAssertion omits the optional instance name when a currentDate: new Date('2026-05-05T17:45:30.000Z'), }) - assert.equal('ct_ops_organization_name' in payload, false) + assert.equal('ct_ops_instance_name' in payload, false) }) diff --git a/apps/web/lib/password-manager/launch-assertion.ts b/apps/web/lib/password-manager/launch-assertion.ts index 0e06779b..47ffb9ff 100644 --- a/apps/web/lib/password-manager/launch-assertion.ts +++ b/apps/web/lib/password-manager/launch-assertion.ts @@ -112,7 +112,7 @@ export async function signPasswordManagerLaunchAssertion( const payload: Record = { product: config.product, ct_ops_instance_id: config.ctOpsInstanceId, - ct_ops_organization_id: assertNonEmptyPrincipalField(principal.instanceId, 'instanceId'), + ct_ops_instance_settings_id: assertNonEmptyPrincipalField(principal.instanceId, 'instanceId'), ct_ops_user_id: assertNonEmptyPrincipalField(principal.userId, 'userId'), email: assertNonEmptyPrincipalField(principal.email, 'email'), name: assertNonEmptyPrincipalField(principal.name, 'name'), @@ -120,7 +120,7 @@ export async function signPasswordManagerLaunchAssertion( const instanceName = principal.instanceName?.trim() if (instanceName) { - payload.ct_ops_organization_name = instanceName + payload.ct_ops_instance_name = instanceName } return new SignJWT(payload) diff --git a/apps/web/lib/password-manager/shell.test.mjs b/apps/web/lib/password-manager/shell.test.mjs index 828d8fd6..a4409fae 100644 --- a/apps/web/lib/password-manager/shell.test.mjs +++ b/apps/web/lib/password-manager/shell.test.mjs @@ -9,14 +9,14 @@ import { } from './shell.ts' test('launch success enters the locked shell and records setup status', () => { - const initial = createInitialPasswordManagerShellState('org-alpha') + const initial = createInitialPasswordManagerShellState('instance-alpha') const next = reducePasswordManagerShellState(initial, { type: 'launch-succeeded', setupConfigured: false, }) assert.deepEqual(next, { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: null, encryptedPrivateKeyEnvelope: null, launchNonce: 0, @@ -30,7 +30,7 @@ test('launch success enters the locked shell and records setup status', () => { }) test('setup completion stores encrypted materials and unlocks without persisting plaintext', () => { - const initial = reducePasswordManagerShellState(createInitialPasswordManagerShellState('org-alpha'), { + const initial = reducePasswordManagerShellState(createInitialPasswordManagerShellState('instance-alpha'), { type: 'launch-succeeded', setupConfigured: false, }) @@ -43,7 +43,7 @@ test('setup completion stores encrypted materials and unlocks without persisting }) assert.deepEqual(next, { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: keyPair, encryptedPrivateKeyEnvelope: { ciphertext_b64: 'ciphertext' }, launchNonce: 0, @@ -58,7 +58,7 @@ test('setup completion stores encrypted materials and unlocks without persisting test('manual lock clears decrypted key material without forcing setup again', () => { const unlocked = { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: { privateKey: { kind: 'private' }, publicKey: { kind: 'public' } }, encryptedPrivateKeyEnvelope: { ciphertext_b64: 'ciphertext' }, launchNonce: 0, @@ -73,7 +73,7 @@ test('manual lock clears decrypted key material without forcing setup again', () const next = reducePasswordManagerShellState(unlocked, { type: 'lock' }) assert.deepEqual(next, { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: null, encryptedPrivateKeyEnvelope: { ciphertext_b64: 'ciphertext' }, launchNonce: 0, @@ -88,7 +88,7 @@ test('manual lock clears decrypted key material without forcing setup again', () test('generic unlock failures keep the shell locked and avoid revealing the root cause', () => { const locked = { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: { privateKey: { kind: 'private' }, publicKey: { kind: 'public' } }, encryptedPrivateKeyEnvelope: null, launchNonce: 0, @@ -107,7 +107,7 @@ test('generic unlock failures keep the shell locked and avoid revealing the root }) assert.deepEqual(next, { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: null, encryptedPrivateKeyEnvelope: null, launchNonce: 0, @@ -122,7 +122,7 @@ test('generic unlock failures keep the shell locked and avoid revealing the root test('instance changes tear down local state and restart launch', () => { const locked = { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: { privateKey: { kind: 'private' }, publicKey: { kind: 'public' } }, encryptedPrivateKeyEnvelope: { ciphertext_b64: 'ciphertext' }, launchNonce: 0, @@ -136,11 +136,11 @@ test('instance changes tear down local state and restart launch', () => { const next = reducePasswordManagerShellState(locked, { type: 'instance-changed', - instanceId: 'org-bravo', + instanceId: 'instance-bravo', }) assert.deepEqual(next, { - instanceId: 'org-bravo', + instanceId: 'instance-bravo', activeKeyPair: null, encryptedPrivateKeyEnvelope: null, launchNonce: 1, @@ -155,7 +155,7 @@ test('instance changes tear down local state and restart launch', () => { test('explicit relaunch clears local shell state and increments launch nonce', () => { const locked = { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: null, encryptedPrivateKeyEnvelope: { ciphertext_b64: 'ciphertext' }, launchNonce: 3, @@ -170,7 +170,7 @@ test('explicit relaunch clears local shell state and increments launch nonce', ( const next = reducePasswordManagerShellState(locked, { type: 'restart-launch' }) assert.deepEqual(next, { - instanceId: 'org-alpha', + instanceId: 'instance-alpha', activeKeyPair: null, encryptedPrivateKeyEnvelope: null, launchNonce: 4, diff --git a/apps/web/lib/pdf/software-report.tsx b/apps/web/lib/pdf/software-report.tsx index b728a08c..d1718047 100644 --- a/apps/web/lib/pdf/software-report.tsx +++ b/apps/web/lib/pdf/software-report.tsx @@ -160,7 +160,7 @@ interface VersionFilter { } interface Props { - orgName: string + instanceName: string packageName: string versionFilter?: VersionFilter osFamily?: string @@ -186,7 +186,7 @@ function fmtDate(d: Date): string { } export function SoftwareReportPDF({ - orgName, + instanceName, packageName, versionFilter, osFamily, @@ -202,7 +202,7 @@ export function SoftwareReportPDF({ {/* Header */} Installed Software Report - {orgName} + {instanceName} Generated: {generatedAt.toLocaleString()} @@ -259,7 +259,7 @@ export function SoftwareReportPDF({ {/* Footer */} - {orgName} — Installed Software Report — {generatedAt.toLocaleDateString()} + {instanceName} — Installed Software Report — {generatedAt.toLocaleDateString()} `Page ${pageNumber} of ${totalPages}` diff --git a/apps/web/lib/seat-admission.test.mjs b/apps/web/lib/seat-admission.test.mjs index 5bc1aab5..819b5d5f 100644 --- a/apps/web/lib/seat-admission.test.mjs +++ b/apps/web/lib/seat-admission.test.mjs @@ -21,10 +21,10 @@ test('seat admission preserves a super admin before pinned free-seat users', () ) }) -test('seat admission falls back to an org admin when no super admin exists', () => { +test('seat admission falls back to an instance admin when no super admin exists', () => { const activeUsers = [ user('engineer_1', 'engineer', '2026-01-01T00:00:00Z'), - user('admin_1', 'org_admin', '2026-01-03T00:00:00Z'), + user('admin_1', 'instance_admin', '2026-01-03T00:00:00Z'), user('engineer_2', 'engineer', '2026-01-04T00:00:00Z'), ] diff --git a/apps/web/lib/seat-admission.ts b/apps/web/lib/seat-admission.ts index a517c17c..06e3f8a0 100644 --- a/apps/web/lib/seat-admission.ts +++ b/apps/web/lib/seat-admission.ts @@ -18,14 +18,14 @@ export class SeatAdmissionError extends Error { } async function getSeatLimit(instanceId: string): Promise { - const org = await db.query.instanceSettings.findFirst({ + const instance = await db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { licenceKey: true, licenceVerifierPublicKey: true }, }) - if (!org?.licenceKey) return FREE_INCLUDED_USER_SEATS + if (!instance?.licenceKey) return FREE_INCLUDED_USER_SEATS - const result = await validateLicenceKey(org.licenceKey, { - publicKeyPem: org.licenceVerifierPublicKey ?? undefined, + const result = await validateLicenceKey(instance.licenceKey, { + publicKeyPem: instance.licenceVerifierPublicKey ?? undefined, }) if (!result.valid || result.payload.sub !== instanceId) { return FREE_INCLUDED_USER_SEATS @@ -35,14 +35,14 @@ async function getSeatLimit(instanceId: string): Promise { } export async function canUserAccessSeat(instanceId: string, userId: string): Promise { - const [org, maxUsers] = await Promise.all([ + const [instance, maxUsers] = await Promise.all([ db.query.instanceSettings.findFirst({ where: eq(instanceSettings.id, instanceId), columns: { metadata: true }, }), getSeatLimit(instanceId), ]) - if (!org) return false + if (!instance) return false const activeUsers = await db.query.users.findMany({ where: and(eq(users.instanceId, instanceId), eq(users.isActive, true), isNull(users.deletedAt)), @@ -51,7 +51,7 @@ export async function canUserAccessSeat(instanceId: string, userId: string): Pro }) if (activeUsers.length <= maxUsers) return true - const metadata = parseInstanceMetadata(org.metadata) + const metadata = parseInstanceMetadata(instance.metadata) const admitted = selectAdmittedSeatUserIds( activeUsers, metadata.freeSeatUserIds ?? [], diff --git a/apps/web/lib/seat-selection.ts b/apps/web/lib/seat-selection.ts index 255b112e..87194849 100644 --- a/apps/web/lib/seat-selection.ts +++ b/apps/web/lib/seat-selection.ts @@ -31,7 +31,7 @@ export function selectAdmittedSeatUserIds( const seen = new Set() const firstAdmin = sortedUsers.find((user) => hasRole(user, 'super_admin')) - ?? sortedUsers.find((user) => hasRole(user, 'org_admin')) + ?? sortedUsers.find((user) => hasRole(user, 'instance_admin')) if (firstAdmin) { pushUnique(admitted, seen, firstAdmin.id, maxUsers) } @@ -43,7 +43,7 @@ export function selectAdmittedSeatUserIds( } for (const user of sortedUsers) { - if (hasRole(user, ['super_admin', 'org_admin'])) { + if (hasRole(user, ['super_admin', 'instance_admin'])) { pushUnique(admitted, seen, user.id, maxUsers) } } diff --git a/apps/web/lib/terminal/tab-state.test.mjs b/apps/web/lib/terminal/tab-state.test.mjs index 649dd577..160aacda 100644 --- a/apps/web/lib/terminal/tab-state.test.mjs +++ b/apps/web/lib/terminal/tab-state.test.mjs @@ -12,7 +12,7 @@ test('clearTerminalPasswordForTab removes the cached password from the ended tab hostname: 'alpha.internal', username: 'alice', password: 'secret-one', - instanceId: 'org-1', + instanceId: 'instance-1', directAccess: false, }, }, @@ -23,7 +23,7 @@ test('clearTerminalPasswordForTab removes the cached password from the ended tab hostname: 'beta.internal', username: 'bob', password: 'secret-two', - instanceId: 'org-1', + instanceId: 'instance-1', directAccess: false, }, }, diff --git a/apps/web/tests/e2e/alerts/alerts.spec.ts b/apps/web/tests/e2e/alerts/alerts.spec.ts index 2fc775a7..0b132df0 100644 --- a/apps/web/tests/e2e/alerts/alerts.spec.ts +++ b/apps/web/tests/e2e/alerts/alerts.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -21,7 +21,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('admin can review, filter, acknowledge, and clean up alert settings', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts ( @@ -231,7 +231,7 @@ test('admin can review, filter, acknowledge, and clean up alert settings', async test('admin can create a silence, email channel, and webhook channel from the alerts page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/auth.setup.ts b/apps/web/tests/e2e/auth.setup.ts index 2a4b1d40..30e32ee6 100644 --- a/apps/web/tests/e2e/auth.setup.ts +++ b/apps/web/tests/e2e/auth.setup.ts @@ -1,6 +1,6 @@ import { test as setup, type APIRequestContext, type Page } from '@playwright/test' import { getStorageStatePath } from './fixtures/auth' -import { seedOrgAndUser } from './fixtures/seed' +import { seedInstanceAndUser } from './fixtures/seed' const publicRoutes = [ '/login', @@ -132,7 +132,7 @@ async function warmupRequest(request: APIRequestContext, route: string): Promise setup('seed test instance and warm Next routes', async ({ browser, baseURL }) => { if (!baseURL) throw new Error('baseURL must be configured') - await seedOrgAndUser() + await seedInstanceAndUser() const publicContext = await browser.newContext() const publicPage = await publicContext.newPage() diff --git a/apps/web/tests/e2e/auth/authenticated-redirects.spec.ts b/apps/web/tests/e2e/auth/authenticated-redirects.spec.ts index 6b9fe517..0e2210a9 100644 --- a/apps/web/tests/e2e/auth/authenticated-redirects.spec.ts +++ b/apps/web/tests/e2e/auth/authenticated-redirects.spec.ts @@ -35,6 +35,6 @@ test('pending users are redirected to approval and can sign out', async ({ authe await page.waitForURL('**/login') await expect(page.getByTestId('login-submit')).toBeVisible() } finally { - await sql`UPDATE "user" SET role = 'org_admin', updated_at = NOW() WHERE id = ${userId}` + await sql`UPDATE "user" SET role = 'instance_admin', updated_at = NOW() WHERE id = ${userId}` } }) diff --git a/apps/web/tests/e2e/auth/invite-acceptance.spec.ts b/apps/web/tests/e2e/auth/invite-acceptance.spec.ts index cea7ad82..a7c814fb 100644 --- a/apps/web/tests/e2e/auth/invite-acceptance.spec.ts +++ b/apps/web/tests/e2e/auth/invite-acceptance.spec.ts @@ -46,7 +46,7 @@ test('invite acceptance rejects logged-in users whose email does not match the i VALUES ( ${`invite-${suffix}`}, ${invitedEmail}, - 'org_admin', + 'instance_admin', ${inviteToken}, ${instanceId}, ${invitedById}, @@ -126,7 +126,7 @@ test('invite acceptance attaches the matching user to the instance', async ({ pa VALUES ( ${`invite-success-${suffix}`}, ${invitedEmail}, - 'org_admin', + 'instance_admin', ${inviteToken}, ${instanceId}, ${invitedById}, @@ -155,7 +155,7 @@ test('invite acceptance attaches the matching user to the instance', async ({ pa LIMIT 1 ` expect(invitee?.instance_id).not.toBeNull() - expect(invitee?.role).toBe('org_admin') + expect(invitee?.role).toBe('instance_admin') const [invite] = await sql>` SELECT accepted_at @@ -193,7 +193,7 @@ test('invite signup without required email verification joins the inviting insta VALUES ( ${`invite-signup-${suffix}`}, ${invitedEmail}, - 'org_admin', + 'instance_admin', ${inviteToken}, ${instanceId}, ${invitedById}, @@ -226,8 +226,8 @@ test('invite signup without required email verification joins the inviting insta LIMIT 1 ` expect(invitee?.instance_id).not.toBeNull() - expect(invitee?.role).toBe('org_admin') - expect(invitee?.roles).toEqual(['org_admin']) + expect(invitee?.role).toBe('instance_admin') + expect(invitee?.roles).toEqual(['instance_admin']) expect(invitee?.email_verified).toBe(false) const [invite] = await sql>` diff --git a/apps/web/tests/e2e/auth/login.spec.ts b/apps/web/tests/e2e/auth/login.spec.ts index daba11d0..6eb1065f 100644 --- a/apps/web/tests/e2e/auth/login.spec.ts +++ b/apps/web/tests/e2e/auth/login.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' import { waitForPasswordResetUrl } from '../fixtures/email' import { TEST_USER } from '../fixtures/seed' import { generateTotpCode } from '../../../lib/auth/ldap-two-factor' @@ -131,9 +131,9 @@ test('password reset ignores unsafe callback urls and returns to the login notic await sql` UPDATE "user" - SET instance_id = (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}), + SET instance_id = (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}), email_verified = true, - role = 'org_admin', + role = 'instance_admin', is_active = true WHERE email = ${email} ` diff --git a/apps/web/tests/e2e/auth/stale-sessions.spec.ts b/apps/web/tests/e2e/auth/stale-sessions.spec.ts index 287fa9fc..90512e75 100644 --- a/apps/web/tests/e2e/auth/stale-sessions.spec.ts +++ b/apps/web/tests/e2e/auth/stale-sessions.spec.ts @@ -1,21 +1,21 @@ import { createId } from '@paralleldrive/cuid2' import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' import { createStorageStateForUser } from '../fixtures/auth' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id } -async function createOrgUser( +async function createInstanceUser( sql: ReturnType, instanceId: string, input: { email: string; isActive?: boolean; deleted?: boolean }, @@ -66,8 +66,8 @@ test('deactivating a user revokes existing sessions and blocks stale dashboard/a baseURL, }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) - const userId = await createOrgUser(sql, instanceId, { + const instanceId = await getInstanceId(sql) + const userId = await createInstanceUser(sql, instanceId, { email: 'stale-deactivated@example.com', }) @@ -103,8 +103,8 @@ test('removing a user clears any leftover sessions', async ({ baseURL, }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) - const userId = await createOrgUser(sql, instanceId, { + const instanceId = await getInstanceId(sql) + const userId = await createInstanceUser(sql, instanceId, { email: 'stale-removed@example.com', isActive: false, }) diff --git a/apps/web/tests/e2e/build-docs/build-docs.spec.ts b/apps/web/tests/e2e/build-docs/build-docs.spec.ts index 01e070c4..747cc500 100644 --- a/apps/web/tests/e2e/build-docs/build-docs.spec.ts +++ b/apps/web/tests/e2e/build-docs/build-docs.spec.ts @@ -3,14 +3,14 @@ import path from 'node:path' import type { Locator } from '@playwright/test' import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -32,7 +32,7 @@ test('admin can create and export a build document from a template and snippet', }) const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) await page.goto('/build-docs') await expect(page.getByTestId('build-docs-heading')).toBeVisible() diff --git a/apps/web/tests/e2e/calendar/calendar.spec.ts b/apps/web/tests/e2e/calendar/calendar.spec.ts index cdb35aaa..75a76e32 100644 --- a/apps/web/tests/e2e/calendar/calendar.spec.ts +++ b/apps/web/tests/e2e/calendar/calendar.spec.ts @@ -1,15 +1,15 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' const CALENDAR_TEST_NOW = new Date('2026-05-09T12:00:00Z') -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -22,7 +22,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('engineer can create a host-linked calendar event with participant roles across calendar views', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) await sql` UPDATE "user" @@ -37,7 +37,7 @@ test('engineer can create a host-linked calendar event with participant roles ac await sql` INSERT INTO "user" (id, name, email, email_verified, created_at, updated_at, instance_id, role, roles, is_active) - VALUES ('calendar-approver-1', 'Calendar Approver', 'calendar-approver@example.com', true, NOW(), NOW(), ${instanceId}, 'org_admin', '["org_admin"]'::jsonb, true) + VALUES ('calendar-approver-1', 'Calendar Approver', 'calendar-approver@example.com', true, NOW(), NOW(), ${instanceId}, 'instance_admin', '["instance_admin"]'::jsonb, true) ` await page.clock.setFixedTime(CALENDAR_TEST_NOW) @@ -100,7 +100,7 @@ test('engineer can create a host-linked calendar event with participant roles ac test('dragging one recurring occurrence creates an exception without moving the series', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO calendar_events ( @@ -167,7 +167,7 @@ test('dragging one recurring occurrence creates an exception without moving the test('overlapping timed calendar events render side by side', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO calendar_events (id, instance_id, created_by, title, starts_at, ends_at, all_day, timezone, status, category) @@ -202,7 +202,7 @@ test('overlapping timed calendar events render side by side', async ({ authentic test('read-only users can view calendar events but cannot create them', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` UPDATE "user" diff --git a/apps/web/tests/e2e/certificate-checker/url-check.spec.ts b/apps/web/tests/e2e/certificate-checker/url-check.spec.ts index 354cfd63..4b17dd45 100644 --- a/apps/web/tests/e2e/certificate-checker/url-check.spec.ts +++ b/apps/web/tests/e2e/certificate-checker/url-check.spec.ts @@ -2,7 +2,7 @@ import { mkdir, writeFile } from 'node:fs/promises' import path from 'node:path' import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' const TRACKING_TEST_CERT_PEM = `-----BEGIN CERTIFICATE----- MIIDZTCCAk2gAwIBAgIUGqljnkaJw0tIYv9uPQ+dGmImmaMwDQYJKoZIhvcNAQEL @@ -26,11 +26,11 @@ z9EhcxayTDhI7Oo2n2yorFz7WHBpytSIN1vQTrhRAUPbNXAcOaUie5TserTteazP sNone70z9cF/ -----END CERTIFICATE-----` -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -247,7 +247,7 @@ test('authenticated user can analyse an uploaded certificate file with a matchin test('authenticated user can track an uploaded certificate after analysis', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await page.route('**/api/tools/certificate-checker', async (route) => { await route.fulfill({ status: 200, diff --git a/apps/web/tests/e2e/certificates/inventory.spec.ts b/apps/web/tests/e2e/certificates/inventory.spec.ts index 74f01101..76cf397c 100644 --- a/apps/web/tests/e2e/certificates/inventory.spec.ts +++ b/apps/web/tests/e2e/certificates/inventory.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can review, filter, and delete tracked certificates', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO certificates ( id, diff --git a/apps/web/tests/e2e/dashboard/overview.spec.ts b/apps/web/tests/e2e/dashboard/overview.spec.ts index f9948657..b53b3f73 100644 --- a/apps/web/tests/e2e/dashboard/overview.spec.ts +++ b/apps/web/tests/e2e/dashboard/overview.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('overview aggregates agent, certificate, and alert counts for the instance', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO agents (id, instance_id, hostname, public_key, status, os, arch) @@ -122,8 +122,8 @@ test('overview aggregates agent, certificate, and alert counts for the instance' await page.goto('/dashboard') await expect(page.getByTestId('dashboard-heading')).toBeVisible() - await expect(page).toHaveTitle(`Overview | ${TEST_ORG.name}`) - await expect(page.getByTestId('dashboard-instance-name')).toHaveText(TEST_ORG.name) + await expect(page).toHaveTitle(`Overview | ${TEST_INSTANCE.name}`) + await expect(page.getByTestId('dashboard-instance-name')).toHaveText(TEST_INSTANCE.name) await expect(page.getByTestId('dashboard-agents-total')).toContainText('2') await expect(page.getByTestId('dashboard-agents-online')).toContainText('1') await expect(page.getByTestId('dashboard-agents-offline')).toContainText('1') diff --git a/apps/web/tests/e2e/directory-lookup/empty-state.spec.ts b/apps/web/tests/e2e/directory-lookup/empty-state.spec.ts index 54c9d188..3cec9f48 100644 --- a/apps/web/tests/e2e/directory-lookup/empty-state.spec.ts +++ b/apps/web/tests/e2e/directory-lookup/empty-state.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('authenticated user sees the LDAP setup empty state when no directory is configured', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` DELETE FROM ldap_configurations @@ -41,7 +41,7 @@ test('authenticated user sees the LDAP setup empty state when no directory is co test('authenticated user can access the directory lookup UI when enabled LDAP configs exist', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO ldap_configurations ( diff --git a/apps/web/tests/e2e/fixtures/password-manager.ts b/apps/web/tests/e2e/fixtures/password-manager.ts index 811611ac..bb0a937f 100644 --- a/apps/web/tests/e2e/fixtures/password-manager.ts +++ b/apps/web/tests/e2e/fixtures/password-manager.ts @@ -264,7 +264,7 @@ export async function createPasswordManagerMock(context: BrowserContext): Promis const payload = decodeJwtPayload(assertion) state.launchAssertions.push(assertion) state.currentUserId = payload.ct_ops_user_id || state.currentUserId - state.currentInstanceId = payload.ct_ops_organization_id || state.currentInstanceId + state.currentInstanceId = payload.ct_ops_instance_settings_id || state.currentInstanceId state.sessionToken = randomUUID() await fulfillEmpty(route, 204, { @@ -673,10 +673,10 @@ export async function createPasswordManagerMock(context: BrowserContext): Promis }, async switchAuthenticatedInstance() { const sql = getTestDb() - const instanceId = `org-switched-${Date.now()}` + const instanceId = `instance-switched-${Date.now()}` await sql` INSERT INTO instance_settings (id, name, slug) - VALUES (${instanceId}, ${'Switched Org'}, ${`switched-org-${Date.now()}`}) + VALUES (${instanceId}, ${'Switched Instance'}, ${`switched-instance-${Date.now()}`}) ` await sql` UPDATE "user" diff --git a/apps/web/tests/e2e/fixtures/seed.ts b/apps/web/tests/e2e/fixtures/seed.ts index 1b7e7f79..67f24f6b 100644 --- a/apps/web/tests/e2e/fixtures/seed.ts +++ b/apps/web/tests/e2e/fixtures/seed.ts @@ -13,9 +13,9 @@ export const TEST_PASSWORD_MANAGER_MEMBER = { name: 'Password Manager Member', } as const -export const TEST_ORG = { - name: 'E2E Test Org', - slug: 'e2e-test-org', +export const TEST_INSTANCE = { + name: 'E2E Test Instance', + slug: 'e2e-test-instance', } as const export async function getSeededTestUserContext(): Promise<{ @@ -49,7 +49,7 @@ async function getTestUserPasswordHash(): Promise { // Idempotent deterministic baseline for E2E tests. It writes Better Auth's // required user/account rows directly so per-test isolation does not have to // round-trip through the app server before each spec. -export async function seedOrgAndUser(): Promise { +export async function seedInstanceAndUser(): Promise { const sql = getTestDb() const instanceId = createId() const userId = createId() @@ -57,7 +57,7 @@ export async function seedOrgAndUser(): Promise { await sql` INSERT INTO instance_settings (id, name, slug) - VALUES (${instanceId}, ${TEST_ORG.name}, ${TEST_ORG.slug}) + VALUES (${instanceId}, ${TEST_INSTANCE.name}, ${TEST_INSTANCE.slug}) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name ` @@ -82,8 +82,8 @@ export async function seedOrgAndUser(): Promise { true, NOW(), NOW(), - (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}), - 'org_admin', + (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}), + 'instance_admin', '[]'::jsonb, true ) @@ -117,7 +117,7 @@ export async function seedOrgAndUser(): Promise { true, NOW(), NOW(), - (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}), + (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}), 'member', '[]'::jsonb, true diff --git a/apps/web/tests/e2e/fixtures/test.ts b/apps/web/tests/e2e/fixtures/test.ts index e72fef24..f0f6cee2 100644 --- a/apps/web/tests/e2e/fixtures/test.ts +++ b/apps/web/tests/e2e/fixtures/test.ts @@ -2,7 +2,7 @@ import { test as base, expect, type Page } from '@playwright/test' import { truncateAppTables } from './db' import { getStorageStatePath } from './auth' import { createPasswordManagerMock, type PasswordManagerMockController } from './password-manager' -import { seedOrgAndUser } from './seed' +import { seedInstanceAndUser } from './seed' type Fixtures = { autoTruncate: void @@ -14,7 +14,7 @@ export const test = base.extend({ autoTruncate: [ async ({}, use) => { await truncateAppTables() - await seedOrgAndUser() + await seedInstanceAndUser() await use() }, { auto: true }, diff --git a/apps/web/tests/e2e/hosts/access-control.spec.ts b/apps/web/tests/e2e/hosts/access-control.spec.ts index ddf0d5ad..9f723455 100644 --- a/apps/web/tests/e2e/hosts/access-control.spec.ts +++ b/apps/web/tests/e2e/hosts/access-control.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -20,7 +20,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('read-only users cannot delete hosts from the host detail page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts ( @@ -67,6 +67,6 @@ test('read-only users cannot delete hosts from the host detail page', async ({ a }) .toBe('present') } finally { - await sql`UPDATE "user" SET role = 'org_admin', updated_at = NOW() WHERE id = ${userId}` + await sql`UPDATE "user" SET role = 'instance_admin', updated_at = NOW() WHERE id = ${userId}` } }) diff --git a/apps/web/tests/e2e/hosts/automated-logs.spec.ts b/apps/web/tests/e2e/hosts/automated-logs.spec.ts index e3fe648d..a720ea22 100644 --- a/apps/web/tests/e2e/hosts/automated-logs.spec.ts +++ b/apps/web/tests/e2e/hosts/automated-logs.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -16,7 +16,7 @@ async function getOrgId(sql: ReturnType): Promise { test('authenticated user can bulk delete automated host logs from the host detail page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/hosts/bulk-tag.spec.ts b/apps/web/tests/e2e/hosts/bulk-tag.spec.ts index 4a91d55f..c971bbaf 100644 --- a/apps/web/tests/e2e/hosts/bulk-tag.spec.ts +++ b/apps/web/tests/e2e/hosts/bulk-tag.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -16,7 +16,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can preview, apply, and save bulk host tags', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/hosts/compare.spec.ts b/apps/web/tests/e2e/hosts/compare.spec.ts index defb33d8..b973d916 100644 --- a/apps/web/tests/e2e/hosts/compare.spec.ts +++ b/apps/web/tests/e2e/hosts/compare.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('authenticated user can compare packages between two hosts', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( @@ -123,7 +123,7 @@ test('authenticated user can compare packages between two hosts', async ({ authe test('authenticated user sees an empty-state message when no comparison host is supplied', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/hosts/docker-containers.spec.ts b/apps/web/tests/e2e/hosts/docker-containers.spec.ts index 42cd2daf..5f260dc0 100644 --- a/apps/web/tests/e2e/hosts/docker-containers.spec.ts +++ b/apps/web/tests/e2e/hosts/docker-containers.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -61,7 +61,7 @@ async function seedDockerStatus(sql: ReturnType, instanceId: s test('host Containers tab lists containers and filters by name and state', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-containers-host') await seedDockerStatus(sql, instanceId, 'docker-containers-host', 'installed') @@ -152,7 +152,7 @@ test('host Containers tab lists containers and filters by name and state', async test('host Containers tab shows per-container metric charts with max spike values', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-container-metrics-host') await seedDockerStatus(sql, instanceId, 'docker-container-metrics-host', 'installed') @@ -313,7 +313,7 @@ test('host Containers tab shows per-container metric charts with max spike value test('host Containers tab shows lifecycle in a sub tab scoped to the selected container', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-lifecycle-host') await seedDockerStatus(sql, instanceId, 'docker-lifecycle-host', 'installed') @@ -377,7 +377,7 @@ test('host Containers tab shows lifecycle in a sub tab scoped to the selected co test('host Containers tab ranks top containers by selected metric and statistic', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-top-containers-host') await seedDockerStatus(sql, instanceId, 'docker-top-containers-host', 'installed') @@ -450,7 +450,7 @@ test('host Containers tab ranks top containers by selected metric and statistic' test('host Containers tab explains Docker unavailable states', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-containers-denied') await seedDockerStatus(sql, instanceId, 'docker-containers-denied', 'permission_denied') diff --git a/apps/web/tests/e2e/hosts/docker-retention.spec.ts b/apps/web/tests/e2e/hosts/docker-retention.spec.ts index 87d28b63..d7280a87 100644 --- a/apps/web/tests/e2e/hosts/docker-retention.spec.ts +++ b/apps/web/tests/e2e/hosts/docker-retention.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can set and clear a host Docker retention override', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` UPDATE instance_settings diff --git a/apps/web/tests/e2e/hosts/docker-status.spec.ts b/apps/web/tests/e2e/hosts/docker-status.spec.ts index 0048c6a2..3a9da374 100644 --- a/apps/web/tests/e2e/hosts/docker-status.spec.ts +++ b/apps/web/tests/e2e/hosts/docker-status.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -42,7 +42,7 @@ async function seedHost(sql: ReturnType, instanceId: string, h test('host overview shows unknown Docker status for older agents', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-status-unknown') await page.goto('/hosts/docker-status-unknown') @@ -55,7 +55,7 @@ test('host overview shows unknown Docker status for older agents', async ({ auth test('host overview shows installed Docker status with version details', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-status-installed') await sql` @@ -88,7 +88,7 @@ test('host overview shows installed Docker status with version details', async ( test('host overview distinguishes Docker permission denied from not installed', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await seedHost(sql, instanceId, 'docker-status-denied') await seedHost(sql, instanceId, 'docker-status-missing') diff --git a/apps/web/tests/e2e/hosts/groups.spec.ts b/apps/web/tests/e2e/hosts/groups.spec.ts index 617d78a2..e8d080ba 100644 --- a/apps/web/tests/e2e/hosts/groups.spec.ts +++ b/apps/web/tests/e2e/hosts/groups.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -12,7 +12,7 @@ async function getOrgId(sql: ReturnType): Promise { test('authenticated user can create, edit, and delete a host group', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await page.goto('/hosts/groups') diff --git a/apps/web/tests/e2e/hosts/host-calendar.spec.ts b/apps/web/tests/e2e/hosts/host-calendar.spec.ts index a54dac48..c2863539 100644 --- a/apps/web/tests/e2e/hosts/host-calendar.spec.ts +++ b/apps/web/tests/e2e/hosts/host-calendar.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -20,7 +20,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('host admin calendar shows only events linked to the current host', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts (id, instance_id, hostname, display_name, os, arch, ip_addresses, status, last_seen_at) @@ -73,7 +73,7 @@ test('host admin calendar shows only events linked to the current host', async ( test('host admin calendar shows an empty state when no events are linked', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts (id, instance_id, hostname, display_name, os, arch, ip_addresses, status, last_seen_at) diff --git a/apps/web/tests/e2e/hosts/host-detail-memberships.spec.ts b/apps/web/tests/e2e/hosts/host-detail-memberships.spec.ts index d1dbbe83..baaaba43 100644 --- a/apps/web/tests/e2e/hosts/host-detail-memberships.spec.ts +++ b/apps/web/tests/e2e/hosts/host-detail-memberships.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('authenticated user can manage group and network memberships from the host detail page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/hosts/inventory.spec.ts b/apps/web/tests/e2e/hosts/inventory.spec.ts index cfa7af8d..4f323ca6 100644 --- a/apps/web/tests/e2e/hosts/inventory.spec.ts +++ b/apps/web/tests/e2e/hosts/inventory.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = 'e2e@example.com' LIMIT 1 ` @@ -20,7 +20,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('authenticated user can search and filter the host inventory', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts ( @@ -101,7 +101,7 @@ test('authenticated user can search and filter the host inventory', async ({ aut test('authenticated user can paginate through the host inventory', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts ( @@ -167,7 +167,7 @@ test('authenticated user can paginate through the host inventory', async ({ auth test('admin can approve a pending agent from the host inventory page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO agents ( @@ -239,7 +239,7 @@ test('admin can approve a pending agent from the host inventory page', async ({ test('admin can reject a pending agent from the host inventory page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO agents ( @@ -337,7 +337,7 @@ test('admin can reject a pending agent from the host inventory page', async ({ a test('authenticated user can sort and paginate the host inventory', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) for (let index = 1; index <= 55; index += 1) { const hostNumber = String(index).padStart(3, '0') diff --git a/apps/web/tests/e2e/hosts/local-users.spec.ts b/apps/web/tests/e2e/hosts/local-users.spec.ts index 4cb2bd66..5b3dcb7a 100644 --- a/apps/web/tests/e2e/hosts/local-users.spec.ts +++ b/apps/web/tests/e2e/hosts/local-users.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('authenticated user can review host local users and open a user detail page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( id, diff --git a/apps/web/tests/e2e/hosts/networks.spec.ts b/apps/web/tests/e2e/hosts/networks.spec.ts index 06afedb1..44251eac 100644 --- a/apps/web/tests/e2e/hosts/networks.spec.ts +++ b/apps/web/tests/e2e/hosts/networks.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can create, edit, and delete a network and manage its hosts', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/hosts/patch-status.spec.ts b/apps/web/tests/e2e/hosts/patch-status.spec.ts index 68269ceb..9859097b 100644 --- a/apps/web/tests/e2e/hosts/patch-status.spec.ts +++ b/apps/web/tests/e2e/hosts/patch-status.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -12,7 +12,7 @@ async function getOrgId(sql: ReturnType): Promise { test('host infrastructure tab shows network memberships and patch status for the selected host', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( @@ -161,7 +161,7 @@ test('host infrastructure tab shows network memberships and patch status for the test('deleting a host removes patch status data before checks', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/hosts/vulnerabilities.spec.ts b/apps/web/tests/e2e/hosts/vulnerabilities.spec.ts index 876732e3..34acee31 100644 --- a/apps/web/tests/e2e/hosts/vulnerabilities.spec.ts +++ b/apps/web/tests/e2e/hosts/vulnerabilities.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -12,7 +12,7 @@ async function getOrgId(sql: ReturnType): Promise { test('host inventory tab shows Linux package CVE findings', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts (id, instance_id, hostname, display_name, os, arch, ip_addresses, status, last_seen_at) diff --git a/apps/web/tests/e2e/notifications/bell.spec.ts b/apps/web/tests/e2e/notifications/bell.spec.ts index ad158ee1..f1e56dce 100644 --- a/apps/web/tests/e2e/notifications/bell.spec.ts +++ b/apps/web/tests/e2e/notifications/bell.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -20,7 +20,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('authenticated user can review topbar notifications and open the linked resource', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/notifications/inbox.spec.ts b/apps/web/tests/e2e/notifications/inbox.spec.ts index dfe59360..f382bbe0 100644 --- a/apps/web/tests/e2e/notifications/inbox.spec.ts +++ b/apps/web/tests/e2e/notifications/inbox.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -20,7 +20,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('authenticated user can review, filter, and bulk delete notifications', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO notifications ( @@ -134,7 +134,7 @@ test('authenticated user can review, filter, and bulk delete notifications', asy test('authenticated user can mark all unread notifications as read', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO notifications ( @@ -222,7 +222,7 @@ test('authenticated user can mark all unread notifications as read', async ({ au test('authenticated user can mark selected read notifications as unread', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO notifications ( @@ -321,7 +321,7 @@ test('authenticated user can mark selected read notifications as unread', async test('authenticated user can expand a notification, change its read state, and delete it', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` INSERT INTO notifications ( diff --git a/apps/web/tests/e2e/profile/profile.spec.ts b/apps/web/tests/e2e/profile/profile.spec.ts index 4658e653..2af026bc 100644 --- a/apps/web/tests/e2e/profile/profile.spec.ts +++ b/apps/web/tests/e2e/profile/profile.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from '../fixtures/test' import { request as playwrightRequest } from '@playwright/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' import { generateTotpCode } from '../../../lib/auth/ldap-two-factor' const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' @@ -22,12 +22,12 @@ function decodeBase32Secret(encoded: string): string { return new TextDecoder().decode(new Uint8Array(bytes)) } -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` SELECT instanceSettings.id AS instance_id, "user".id AS user_id FROM instance_settings JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + WHERE instanceSettings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -226,7 +226,7 @@ test('authenticated user can switch profile theme preferences and persist the se test('authenticated user can opt out of in-app notifications when the instance allows it', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` UPDATE instance_settings @@ -275,7 +275,7 @@ test('authenticated user can opt out of in-app notifications when the instance a test('authenticated user cannot opt out of in-app notifications when the instance requires them', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await sql` UPDATE instance_settings diff --git a/apps/web/tests/e2e/reports/patch-status.spec.ts b/apps/web/tests/e2e/reports/patch-status.spec.ts index 26094a40..837c397d 100644 --- a/apps/web/tests/e2e/reports/patch-status.spec.ts +++ b/apps/web/tests/e2e/reports/patch-status.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -12,7 +12,7 @@ async function getOrgId(sql: ReturnType): Promise { test('patch status report shows instance compliance by network and host', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( id, diff --git a/apps/web/tests/e2e/reports/software.spec.ts b/apps/web/tests/e2e/reports/software.spec.ts index c74c25e0..31d85615 100644 --- a/apps/web/tests/e2e/reports/software.spec.ts +++ b/apps/web/tests/e2e/reports/software.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('software report supports search, saved filters, new packages, and drift views', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( id, diff --git a/apps/web/tests/e2e/reports/vulnerabilities.spec.ts b/apps/web/tests/e2e/reports/vulnerabilities.spec.ts index 7ba71236..849a8f08 100644 --- a/apps/web/tests/e2e/reports/vulnerabilities.spec.ts +++ b/apps/web/tests/e2e/reports/vulnerabilities.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -12,7 +12,7 @@ async function getOrgId(sql: ReturnType): Promise { test('vulnerability report filters open findings', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts (id, instance_id, hostname, display_name, os, arch, status) VALUES ('report-vuln-host-1', ${instanceId}, 'report-vuln-node-1', 'Report Vuln Node', 'linux', 'x86_64', 'online') @@ -56,7 +56,7 @@ test('vulnerability report filters open findings', async ({ authenticatedPage: p test('vulnerability report applies host group, severity, KEV, and fix filters', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO host_groups ( id, diff --git a/apps/web/tests/e2e/service-accounts/service-accounts.spec.ts b/apps/web/tests/e2e/service-accounts/service-accounts.spec.ts index bc218f0d..f383896a 100644 --- a/apps/web/tests/e2e/service-accounts/service-accounts.spec.ts +++ b/apps/web/tests/e2e/service-accounts/service-accounts.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can review, filter, and add service accounts', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO domain_accounts ( id, @@ -129,7 +129,7 @@ test('admin can review, filter, and add service accounts', async ({ authenticate test('admin can update and delete a service account from the detail page', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO domain_accounts ( id, diff --git a/apps/web/tests/e2e/settings/activation-token.spec.ts b/apps/web/tests/e2e/settings/activation-token.spec.ts index 7f79ccd3..14582ca2 100644 --- a/apps/web/tests/e2e/settings/activation-token.spec.ts +++ b/apps/web/tests/e2e/settings/activation-token.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { decodeActivationToken } from '@/lib/licence-activation-token' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' test('admin can generate an activation token from settings', async ({ authenticatedPage: page }) => { await page.context().grantPermissions(['clipboard-read', 'clipboard-write']) await page.goto('/settings/licence') await expect(page.getByTestId('settings-heading')).toContainText('Instance') - await expect(page.getByTestId('licence-instance-name')).toHaveText(TEST_ORG.name) + await expect(page.getByTestId('licence-instance-name')).toHaveText(TEST_INSTANCE.name) await page.getByTestId('activation-token-generate').click() const activationToken = page.getByTestId('activation-token') @@ -20,9 +20,9 @@ test('admin can generate an activation token from settings', async ({ authentica const firstDecoded = decodeActivationToken(firstToken ?? '') expect(firstDecoded.ok).toBe(true) if (firstDecoded.ok) { - expect(firstDecoded.payload.installOrgName).toBe(TEST_ORG.name) - expect(firstDecoded.payload.installOrgId).toBeTruthy() - await expect(page.getByTestId('licence-instance-id')).toHaveText(firstDecoded.payload.installOrgId) + expect(firstDecoded.payload.installInstanceName).toBe(TEST_INSTANCE.name) + expect(firstDecoded.payload.installInstanceId).toBeTruthy() + await expect(page.getByTestId('licence-instance-id')).toHaveText(firstDecoded.payload.installInstanceId) } await page.getByTestId('activation-token-copy').click() @@ -39,8 +39,8 @@ test('admin can generate an activation token from settings', async ({ authentica const secondDecoded = decodeActivationToken(secondToken ?? '') expect(secondDecoded.ok).toBe(true) if (firstDecoded.ok && secondDecoded.ok) { - expect(secondDecoded.payload.installOrgId).toBe(firstDecoded.payload.installOrgId) - expect(secondDecoded.payload.installOrgName).toBe(TEST_ORG.name) + expect(secondDecoded.payload.installInstanceId).toBe(firstDecoded.payload.installInstanceId) + expect(secondDecoded.payload.installInstanceName).toBe(TEST_INSTANCE.name) expect(secondDecoded.payload.nonce).not.toBe(firstDecoded.payload.nonce) } }) diff --git a/apps/web/tests/e2e/settings/agent-enrolment.spec.ts b/apps/web/tests/e2e/settings/agent-enrolment.spec.ts index 178f0bdc..a58e6e95 100644 --- a/apps/web/tests/e2e/settings/agent-enrolment.spec.ts +++ b/apps/web/tests/e2e/settings/agent-enrolment.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG, TEST_USER } from '../fixtures/seed' +import { TEST_INSTANCE, TEST_USER } from '../fixtures/seed' -async function getOrgAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { +async function getInstanceAndUserIds(sql: ReturnType): Promise<{ instanceId: string; userId: string }> { const rows = await sql>` - SELECT instanceSettings.id AS instance_id, "user".id AS user_id + SELECT instance_settings.id AS instance_id, "user".id AS user_id FROM instance_settings - JOIN "user" ON "user".instance_id = instanceSettings.id - WHERE instanceSettings.slug = ${TEST_ORG.slug} + JOIN "user" ON "user".instance_id = instance_settings.id + WHERE instance_settings.slug = ${TEST_INSTANCE.slug} AND "user".email = ${TEST_USER.email} LIMIT 1 ` @@ -20,7 +20,7 @@ async function getOrgAndUserIds(sql: ReturnType): Promise<{ in test('admin can create and revoke an enrolment token from agent settings', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) await page.goto('/settings/agents') @@ -109,7 +109,7 @@ test('admin can create and revoke an enrolment token from agent settings', async test('admin cannot create an auto-approved token without super admin privileges', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId } = await getOrgAndUserIds(sql) + const { instanceId } = await getInstanceAndUserIds(sql) await page.goto('/settings/agents') @@ -137,7 +137,7 @@ test('admin cannot create an auto-approved token without super admin privileges' test('admin can generate an install bundle with an existing token and bundle tags', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const { instanceId, userId } = await getOrgAndUserIds(sql) + const { instanceId, userId } = await getInstanceAndUserIds(sql) let capturedBody: Record | null = null await sql` diff --git a/apps/web/tests/e2e/settings/alert-defaults.spec.ts b/apps/web/tests/e2e/settings/alert-defaults.spec.ts index 57d1dddf..d58daaf4 100644 --- a/apps/web/tests/e2e/settings/alert-defaults.spec.ts +++ b/apps/web/tests/e2e/settings/alert-defaults.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' test('admin can create and delete global alert defaults from monitoring settings', async ({ authenticatedPage: page }) => { const sql = getTestDb() @@ -29,7 +29,7 @@ test('admin can create and delete global alert defaults from monitoring settings }>>` SELECT id, name, severity, condition_type, config FROM alert_rules - WHERE instance_id = (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}) + WHERE instance_id = (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}) AND host_id IS NULL AND is_global_default = true AND deleted_at IS NULL @@ -54,7 +54,7 @@ test('admin can create and delete global alert defaults from monitoring settings const createdRows = await sql>` SELECT id FROM alert_rules - WHERE instance_id = (SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug}) + WHERE instance_id = (SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug}) AND host_id IS NULL AND is_global_default = true AND deleted_at IS NULL diff --git a/apps/web/tests/e2e/settings/instance-name.spec.ts b/apps/web/tests/e2e/settings/instance-name.spec.ts new file mode 100644 index 00000000..e26546c8 --- /dev/null +++ b/apps/web/tests/e2e/settings/instance-name.spec.ts @@ -0,0 +1,79 @@ +import { test, expect } from '../fixtures/test' +import { getTestDb } from '../fixtures/db' +import { TEST_INSTANCE } from '../fixtures/seed' + +test('admin can update the instance name from settings', async ({ authenticatedPage: page }) => { + const updatedName = `Updated Instance ${Date.now()}` + const sql = getTestDb() + + await page.goto('/settings') + await expect(page.getByTestId('settings-heading')).toBeVisible() + + const instanceNameInput = page.getByTestId('settings-instance-name-input') + await instanceNameInput.click() + await instanceNameInput.press(`${process.platform === 'darwin' ? 'Meta' : 'Control'}+A`) + await instanceNameInput.fill('A') + await page.getByTestId('settings-instance-name-save').click() + + await expect(page.getByText('Name must be at least 2 characters')).toBeVisible() + + const beforeRows = await sql>` + SELECT name + FROM instance_settings + WHERE slug = ${TEST_INSTANCE.slug} + LIMIT 1 + ` + + expect(beforeRows).toHaveLength(1) + expect(beforeRows[0]?.name).toBe(TEST_INSTANCE.name) + + await instanceNameInput.click() + await instanceNameInput.press(`${process.platform === 'darwin' ? 'Meta' : 'Control'}+A`) + await instanceNameInput.fill(updatedName) + await page.getByTestId('settings-instance-name-save').click() + + await expect(page.getByTestId('settings-instance-name-success')).toHaveText('Saved') + await expect(instanceNameInput).toHaveValue(updatedName) + + const rows = await sql>` + SELECT name + FROM instance_settings + WHERE slug = ${TEST_INSTANCE.slug} + LIMIT 1 + ` + + expect(rows).toHaveLength(1) + expect(rows[0]?.name).toBe(updatedName) +}) + +test('instance name validation rejects names shorter than two characters', async ({ authenticatedPage: page }) => { + const sql = getTestDb() + + await sql` + UPDATE instance_settings + SET name = ${TEST_INSTANCE.name} + WHERE slug = ${TEST_INSTANCE.slug} + ` + + await page.goto('/settings') + await expect(page.getByTestId('settings-heading')).toBeVisible() + + const instanceNameInput = page.getByTestId('settings-instance-name-input') + await instanceNameInput.click() + await instanceNameInput.press(`${process.platform === 'darwin' ? 'Meta' : 'Control'}+A`) + await instanceNameInput.fill('A') + await page.getByTestId('settings-instance-name-save').click() + + await expect(page.getByText('Name must be at least 2 characters')).toBeVisible() + await expect(page.getByTestId('settings-instance-name-success')).toHaveCount(0) + + const rows = await sql>` + SELECT name + FROM instance_settings + WHERE slug = ${TEST_INSTANCE.slug} + LIMIT 1 + ` + + expect(rows).toHaveLength(1) + expect(rows[0]?.name).toBe(TEST_INSTANCE.name) +}) diff --git a/apps/web/tests/e2e/settings/ldap.spec.ts b/apps/web/tests/e2e/settings/ldap.spec.ts index dcf61dee..8f09cfee 100644 --- a/apps/web/tests/e2e/settings/ldap.spec.ts +++ b/apps/web/tests/e2e/settings/ldap.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -12,7 +12,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can create, edit, toggle, and delete an LDAP configuration', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await page.goto('/settings/integrations') diff --git a/apps/web/tests/e2e/settings/licence.spec.ts b/apps/web/tests/e2e/settings/licence.spec.ts index 46faa485..3dedf49b 100644 --- a/apps/web/tests/e2e/settings/licence.spec.ts +++ b/apps/web/tests/e2e/settings/licence.spec.ts @@ -1,13 +1,13 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' import { issueTestLicence } from '../fixtures/licence' -async function getOrg(sql: ReturnType): Promise<{ id: string; licence_tier: string }> { +async function getInstance(sql: ReturnType): Promise<{ id: string; licence_tier: string }> { const rows = await sql>` SELECT id, licence_tier FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -16,9 +16,9 @@ async function getOrg(sql: ReturnType): Promise<{ id: string; test('admin can validate and save a seat-capacity licence key from licence settings', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const org = await getOrg(sql) + const instance = await getInstance(sql) const licenceKey = await issueTestLicence({ - instanceId: org.id, + instanceId: instance.id, tier: 'community', maxUsers: 8, }) @@ -46,7 +46,7 @@ test('admin can validate and save a seat-capacity licence key from licence setti const rows = await sql>` SELECT licence_tier, licence_key FROM instance_settings - WHERE id = ${org.id} + WHERE id = ${instance.id} LIMIT 1 ` return rows[0] ?? null diff --git a/apps/web/tests/e2e/settings/organisation-name.spec.ts b/apps/web/tests/e2e/settings/organisation-name.spec.ts deleted file mode 100644 index 0d7fae09..00000000 --- a/apps/web/tests/e2e/settings/organisation-name.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { test, expect } from '../fixtures/test' -import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' - -test('admin can update the instance name from settings', async ({ authenticatedPage: page }) => { - const updatedName = `Updated Org ${Date.now()}` - const sql = getTestDb() - - await page.goto('/settings') - await expect(page.getByTestId('settings-heading')).toBeVisible() - - const orgNameInput = page.getByTestId('settings-org-name-input') - await orgNameInput.click() - await orgNameInput.press(`${process.platform === 'darwin' ? 'Meta' : 'Control'}+A`) - await orgNameInput.fill('A') - await page.getByTestId('settings-org-name-save').click() - - await expect(page.getByText('Name must be at least 2 characters')).toBeVisible() - - const beforeRows = await sql>` - SELECT name - FROM instance_settings - WHERE slug = ${TEST_ORG.slug} - LIMIT 1 - ` - - expect(beforeRows).toHaveLength(1) - expect(beforeRows[0]?.name).toBe(TEST_ORG.name) - - await orgNameInput.click() - await orgNameInput.press(`${process.platform === 'darwin' ? 'Meta' : 'Control'}+A`) - await orgNameInput.fill(updatedName) - await page.getByTestId('settings-org-name-save').click() - - await expect(page.getByTestId('settings-org-name-success')).toHaveText('Saved') - await expect(orgNameInput).toHaveValue(updatedName) - - const rows = await sql>` - SELECT name - FROM instance_settings - WHERE slug = ${TEST_ORG.slug} - LIMIT 1 - ` - - expect(rows).toHaveLength(1) - expect(rows[0]?.name).toBe(updatedName) -}) - -test('instance name validation rejects names shorter than two characters', async ({ authenticatedPage: page }) => { - const sql = getTestDb() - - await sql` - UPDATE instance_settings - SET name = ${TEST_ORG.name} - WHERE slug = ${TEST_ORG.slug} - ` - - await page.goto('/settings') - await expect(page.getByTestId('settings-heading')).toBeVisible() - - const orgNameInput = page.getByTestId('settings-org-name-input') - await orgNameInput.click() - await orgNameInput.press(`${process.platform === 'darwin' ? 'Meta' : 'Control'}+A`) - await orgNameInput.fill('A') - await page.getByTestId('settings-org-name-save').click() - - await expect(page.getByText('Name must be at least 2 characters')).toBeVisible() - await expect(page.getByTestId('settings-org-name-success')).toHaveCount(0) - - const rows = await sql>` - SELECT name - FROM instance_settings - WHERE slug = ${TEST_ORG.slug} - LIMIT 1 - ` - - expect(rows).toHaveLength(1) - expect(rows[0]?.name).toBe(TEST_ORG.name) -}) diff --git a/apps/web/tests/e2e/settings/retention-and-agent-defaults.spec.ts b/apps/web/tests/e2e/settings/retention-and-agent-defaults.spec.ts index 7b36ca30..87581def 100644 --- a/apps/web/tests/e2e/settings/retention-and-agent-defaults.spec.ts +++ b/apps/web/tests/e2e/settings/retention-and-agent-defaults.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' test('admin can update metric retention from monitoring settings', async ({ authenticatedPage: page }) => { const sql = getTestDb() @@ -18,7 +18,7 @@ test('admin can update metric retention from monitoring settings', async ({ auth const rows = await sql>` SELECT metric_retention_days FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -43,7 +43,7 @@ test('admin can update Docker metric retention from monitoring settings', async const rows = await sql>` SELECT docker_metric_retention_days FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -77,7 +77,7 @@ test('admin can update default host collection settings from agent defaults', as }>>` SELECT metadata FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -118,7 +118,7 @@ test('admin can update software inventory defaults from agent settings', async ( }>>` SELECT metadata FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` diff --git a/apps/web/tests/e2e/settings/system-health.spec.ts b/apps/web/tests/e2e/settings/system-health.spec.ts index 9c9b2751..d3ebd812 100644 --- a/apps/web/tests/e2e/settings/system-health.spec.ts +++ b/apps/web/tests/e2e/settings/system-health.spec.ts @@ -1,11 +1,11 @@ import { createId } from '@paralleldrive/cuid2' import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` - SELECT id FROM instance_settings WHERE slug = ${TEST_ORG.slug} LIMIT 1 + SELECT id FROM instance_settings WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id @@ -13,7 +13,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can view ingest status, agent errors, and upgrade counts', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const agentId = createId() const hostId = createId() diff --git a/apps/web/tests/e2e/settings/tag-rules.spec.ts b/apps/web/tests/e2e/settings/tag-rules.spec.ts index e96fdff0..9a5b32ab 100644 --- a/apps/web/tests/e2e/settings/tag-rules.spec.ts +++ b/apps/web/tests/e2e/settings/tag-rules.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id AS instance_id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can run, disable, and delete a saved tag rule', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) await sql` INSERT INTO hosts ( diff --git a/apps/web/tests/e2e/settings/terminal-settings.spec.ts b/apps/web/tests/e2e/settings/terminal-settings.spec.ts index 6ae5ebbd..70ac6d31 100644 --- a/apps/web/tests/e2e/settings/terminal-settings.spec.ts +++ b/apps/web/tests/e2e/settings/terminal-settings.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' test('admin can update instance terminal settings from settings', async ({ authenticatedPage: page }) => { const sql = getTestDb() @@ -26,7 +26,7 @@ test('admin can update instance terminal settings from settings', async ({ authe }>>` SELECT metadata FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -53,7 +53,7 @@ test('admin can update instance terminal settings from settings', async ({ authe }>>` SELECT metadata FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -98,7 +98,7 @@ test('admin can update instance notification settings from settings', async ({ a }>>` SELECT metadata FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -106,7 +106,7 @@ test('admin can update instance notification settings from settings', async ({ a }) .toEqual({ inAppEnabled: false, - inAppRoles: ['super_admin', 'org_admin', 'engineer'], + inAppRoles: ['super_admin', 'instance_admin', 'engineer'], allowUserOptOut: true, }) @@ -131,7 +131,7 @@ test('admin can update instance notification settings from settings', async ({ a }>>` SELECT metadata FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` @@ -139,7 +139,7 @@ test('admin can update instance notification settings from settings', async ({ a }) .toEqual({ inAppEnabled: true, - inAppRoles: ['super_admin', 'org_admin', 'read_only'], + inAppRoles: ['super_admin', 'instance_admin', 'read_only'], allowUserOptOut: false, }) }) @@ -175,7 +175,7 @@ test('admin can save SMTP relay settings and sees validation for an invalid test }>>` SELECT metadata FROM instance_settings - WHERE slug = 'e2e-test-org' + WHERE slug = 'e2e-test-instance' LIMIT 1 ` diff --git a/apps/web/tests/e2e/setup/setup-email.spec.ts b/apps/web/tests/e2e/setup/setup-email.spec.ts index f45fad12..0f9f329e 100644 --- a/apps/web/tests/e2e/setup/setup-email.spec.ts +++ b/apps/web/tests/e2e/setup/setup-email.spec.ts @@ -23,7 +23,7 @@ test('ldap placeholder users must set a real email before reaching the dashboard UPDATE "user" SET instance_id = ${instanceId}, email_verified = true, - role = 'org_admin', + role = 'instance_admin', is_active = true WHERE email = ${placeholderEmail} ` diff --git a/apps/web/tests/e2e/team/invitations.spec.ts b/apps/web/tests/e2e/team/invitations.spec.ts index cd596d98..a3903b60 100644 --- a/apps/web/tests/e2e/team/invitations.spec.ts +++ b/apps/web/tests/e2e/team/invitations.spec.ts @@ -1,20 +1,20 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' import { issueTestLicence } from '../fixtures/licence' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) return rows[0]!.id } -async function setOrgSeatLimit(sql: ReturnType, instanceId: string, maxUsers: number): Promise { +async function setInstanceSeatLimit(sql: ReturnType, instanceId: string, maxUsers: number): Promise { const licenceKey = await issueTestLicence({ instanceId, tier: 'community', maxUsers }) await sql` UPDATE instance_settings @@ -24,7 +24,7 @@ async function setOrgSeatLimit(sql: ReturnType, instanceId: st ` } -async function clearOrgLicence(sql: ReturnType, instanceId: string): Promise { +async function clearInstanceLicence(sql: ReturnType, instanceId: string): Promise { await sql` UPDATE instance_settings SET licence_key = NULL, @@ -93,7 +93,7 @@ async function ensureAtLeastActiveMembers( test('admin can create and cancel a team invitation', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const inviteeEmail = 'teammate@example.com' await page.goto('/team') @@ -101,7 +101,7 @@ test('admin can create and cancel a team invitation', async ({ authenticatedPage await expect(page.getByTestId('team-heading')).toBeVisible() await page.getByTestId('team-invite-open').click() await page.getByTestId('team-invite-email').fill(inviteeEmail) - await page.getByTestId('team-invite-role-org_admin').click() + await page.getByTestId('team-invite-role-instance_admin').click() await page.getByTestId('team-invite-role-read_only').click() await page.getByTestId('team-invite-submit').click() @@ -112,7 +112,7 @@ test('admin can create and cancel a team invitation', async ({ authenticatedPage const pendingInviteRow = page.getByTestId('team-pending-invite-row').filter({ hasText: inviteeEmail }) await expect(pendingInviteRow).toBeVisible() - await expect(pendingInviteRow).toContainText('Org Admin') + await expect(pendingInviteRow).toContainText('Instance Admin') await expect(pendingInviteRow).toContainText('Read Only') const inviteRows = await sql>` @@ -125,8 +125,8 @@ test('admin can create and cancel a team invitation', async ({ authenticatedPage ` expect(inviteRows).toHaveLength(1) - expect(inviteRows[0]?.role).toBe('org_admin') - expect(inviteRows[0]?.roles).toEqual(['org_admin', 'read_only']) + expect(inviteRows[0]?.role).toBe('instance_admin') + expect(inviteRows[0]?.roles).toEqual(['instance_admin', 'read_only']) expect(inviteRows[0]?.deleted_at).toBeNull() await pendingInviteRow.getByTestId('team-pending-invite-cancel').click() @@ -145,7 +145,7 @@ test('admin can create and cancel a team invitation', async ({ authenticatedPage test('admin cannot create a duplicate pending invitation for the same email address', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const inviteeEmail = 'duplicate-teammate@example.com' await page.goto('/team') @@ -160,7 +160,7 @@ test('admin cannot create a duplicate pending invitation for the same email addr await page.getByTestId('team-invite-open').click() await page.getByTestId('team-invite-email').fill(inviteeEmail) - await page.getByTestId('team-invite-role-org_admin').click() + await page.getByTestId('team-invite-role-instance_admin').click() await page.getByTestId('team-invite-submit').click() await expect(page.getByText('An invitation has already been sent to this email address')).toBeVisible() @@ -182,11 +182,11 @@ test('admin cannot create a duplicate pending invitation for the same email addr test('admin cannot create a fourth user invite on free Community seats', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const suffix = Date.now().toString() const inviteeEmail = `community-fourth-seat-${suffix}@example.com` - await clearOrgLicence(sql, instanceId) + await clearInstanceLicence(sql, instanceId) await ensureAtLeastActiveMembers(sql, instanceId, 3, `community-seat-member-${suffix}`) await page.goto('/team') @@ -203,13 +203,13 @@ test('admin cannot create a fourth user invite on free Community seats', async ( test('an extra paid seat allows one more active user invitation', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const suffix = Date.now().toString() const inviteeEmail = `paid-fourth-seat-${suffix}@example.com` try { const activeCount = await ensureAtLeastActiveMembers(sql, instanceId, 3, `paid-seat-member-${suffix}`) - await setOrgSeatLimit(sql, instanceId, activeCount + 1) + await setInstanceSeatLimit(sql, instanceId, activeCount + 1) await page.goto('/team') @@ -223,16 +223,16 @@ test('an extra paid seat allows one more active user invitation', async ({ authe await page.getByTestId('team-invite-done').click() await expect(page.getByTestId('team-pending-invite-row').filter({ hasText: inviteeEmail })).toBeVisible() } finally { - await clearOrgLicence(sql, instanceId) + await clearInstanceLicence(sql, instanceId) } }) test('admin cannot create an invitation when user seats are exhausted', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) try { - await setOrgSeatLimit(sql, instanceId, 1) + await setInstanceSeatLimit(sql, instanceId, 1) await page.goto('/team') @@ -253,19 +253,19 @@ test('admin cannot create an invitation when user seats are exhausted', async ({ ` expect(inviteRows).toHaveLength(0) } finally { - await clearOrgLicence(sql, instanceId) + await clearInstanceLicence(sql, instanceId) } }) test('admin re-inviting a removed user restores their membership instead of creating a pending invite', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const removedEmail = 'restored-member@example.com' const activeCount = await countActiveMembers(sql, instanceId) try { - await setOrgSeatLimit(sql, instanceId, activeCount + 1) + await setInstanceSeatLimit(sql, instanceId, activeCount + 1) await sql` INSERT INTO "user" ( @@ -332,6 +332,6 @@ test('admin re-inviting a removed user restores their membership instead of crea expect(inviteRows).toHaveLength(0) } finally { - await clearOrgLicence(sql, instanceId) + await clearInstanceLicence(sql, instanceId) } }) diff --git a/apps/web/tests/e2e/team/members.spec.ts b/apps/web/tests/e2e/team/members.spec.ts index 42e8ed08..384b632b 100644 --- a/apps/web/tests/e2e/team/members.spec.ts +++ b/apps/web/tests/e2e/team/members.spec.ts @@ -1,12 +1,12 @@ import { test, expect } from '../fixtures/test' import { getTestDb } from '../fixtures/db' -import { TEST_ORG } from '../fixtures/seed' +import { TEST_INSTANCE } from '../fixtures/seed' -async function getOrgId(sql: ReturnType): Promise { +async function getInstanceId(sql: ReturnType): Promise { const rows = await sql>` SELECT id FROM instance_settings - WHERE slug = ${TEST_ORG.slug} + WHERE slug = ${TEST_INSTANCE.slug} LIMIT 1 ` expect(rows).toHaveLength(1) @@ -15,7 +15,7 @@ async function getOrgId(sql: ReturnType): Promise { test('admin can change a team member role and manage their lifecycle', async ({ authenticatedPage: page }) => { const sql = getTestDb() - const instanceId = await getOrgId(sql) + const instanceId = await getInstanceId(sql) const memberId = 'team-member-managed' await sql` diff --git a/apps/web/tests/e2e/tooling/access-control.spec.ts b/apps/web/tests/e2e/tooling/access-control.spec.ts index 99c20f43..e2bb1c7e 100644 --- a/apps/web/tests/e2e/tooling/access-control.spec.ts +++ b/apps/web/tests/e2e/tooling/access-control.spec.ts @@ -51,6 +51,6 @@ test('read-only users cannot access tooling pages or certificate checker API', a expect(launchAssertionResponse.status()).toBe(403) await expect(launchAssertionResponse.json()).resolves.toMatchObject({ error: 'Forbidden' }) } finally { - await sql`UPDATE "user" SET role = 'org_admin', updated_at = NOW() WHERE id = ${userId}` + await sql`UPDATE "user" SET role = 'instance_admin', updated_at = NOW() WHERE id = ${userId}` } }) diff --git a/docs/bug-tracker-checks-stopping.md b/docs/bug-tracker-checks-stopping.md index f0d365b4..04a9b3e8 100644 --- a/docs/bug-tracker-checks-stopping.md +++ b/docs/bug-tracker-checks-stopping.md @@ -84,10 +84,10 @@ In `processHeartbeat()`, metrics are saved unconditionally: // Lines 205–231: no hostID check queries.UpdateAgentHeartbeat(...) // uses agentID only queries.UpdateHostVitals(...) // uses agentID only -queries.InsertHostMetricByAgentID(...) // uses orgID + agentID +queries.InsertHostMetricByAgentID(...) // uses instanceID + agentID ``` -All three metric operations use `agentID` or `orgID`, neither of which requires the hosts table lookup. +All three metric operations use `agentID` or `instanceID`, neither of which requires the hosts table lookup. --- diff --git a/docs/ct-cve-migration-plan.md b/docs/ct-cve-migration-plan.md index 33436952..fa9ffed2 100644 --- a/docs/ct-cve-migration-plan.md +++ b/docs/ct-cve-migration-plan.md @@ -376,7 +376,7 @@ Required decisions: - Whether CT-CVE pulls inventory from CT Ops, CT Ops pushes inventory to CT-CVE, or both are supported. - Finding ingestion payload shape. -- Organisation scoping. +- Instance scoping. - Host/package identity model. - Idempotency keys. - Authentication and authorization, preferably signed service tokens. @@ -529,7 +529,7 @@ Expected work: - Include instance, user, role/permission, product, expiry, and replay claims. - Add revocation and rotation paths. -- Add tests for signature validity, issuer/audience binding, org scoping, +- Add tests for signature validity, issuer/audience binding, instance scoping, expiry, replay, role/permission claims, and revoked plugin instances. ### 14. Plugin Entitlement Storage and CT Portal Contract @@ -580,7 +580,7 @@ Append dated notes here as phases progress. Keep notes short and factual. - Continued Phase 14 on the plugin-entitlement design branch by adding `docs/ct-ops-plugin-entitlement-storage.md` and linking it from the product decision record and CT-CVE API contract. The design now covers CT - Portal request-token fields, plugin-licence binding to installation/org/ + Portal request-token fields, plugin-licence binding to installation/instance/ product/instance, derived subscription-status responses, encrypted licence artifact storage, audit expectations, and backend-hidden visibility policy options for future external plugins. Remaining work for Phase 14 is the @@ -641,7 +641,7 @@ Append dated notes here as phases progress. Keep notes short and factual. features, Pro seat capacity, and Enterprise-only capabilities. - Phase 6 completed on branch `ct-cve-api-contract` by adding the first CT Ops/CT-CVE API contract. The contract chooses CT Ops push inventory snapshots - and CT-CVE push finding batches, requires org-scoped signed service tokens, + and CT-CVE push finding batches, requires instance-scoped signed service tokens, defines host/package/finding identity, idempotency keys, rate limits, replay protection, retry semantics, and connection status fields. - Phase 7 started with the CT-CVE repository bootstrap in ct-cve PR #1. @@ -694,14 +694,14 @@ Append dated notes here as phases progress. Keep notes short and factual. - Phase 9 started on branch `feat/ct-ops-ct-cve-connector` by adding CT Ops' inbound signed service-token verifier for CT-CVE, including request body hashes, HMAC signatures, five-minute timestamp skew enforcement, nonce replay - protection, org/scope binding, configured token parsing, and a signed + protection, instance/scope binding, configured token parsing, and a signed connection health endpoint. Finding batch persistence and CT Ops inventory export remain. Validation: targeted service-token unit test, targeted ESLint, web type-check, migration validation, and web unit suite. - Continued Phase 9 on branch `feat/ct-cve-finding-ingest` by adding `POST /api/integrations/ct-cve/v1/finding-batches` for signed CT-CVE finding delivery. The endpoint enforces the 25 MiB payload limit, per-token rate - limiting, token/org scope binding, contract payload validation, row-level + limiting, token/instance scope binding, contract payload validation, row-level rejection for unknown/deleted hosts and inactive packages, CVE upserts, imported finding upserts, CT-CVE finding/batch metadata, and stale replay skipping so older deliveries cannot regress newer finding state. Inventory @@ -713,14 +713,14 @@ Append dated notes here as phases progress. Keep notes short and factual. storage/reporting plus the legacy vulnerability settings page until CT-CVE connector inventory/status slices replace them. - Continued Phase 9 on branch `feat/ct-cve-inventory-export` by adding CT Ops - inventory snapshot export helpers that build org-scoped host/package payloads + inventory snapshot export helpers that build instance-scoped host/package payloads from active hosts and current software packages, carry opaque pagination cursors, and push snapshots to CT-CVE with the signed `inventory:write` service-token contract. Also corrected CT Ops connection health to require `connection:read`. Automated inventory push scheduling and durable connection status timestamps remain. - Continued Phase 9 on branch `feat/ct-cve-connection-status` by adding - durable org-scoped CT-CVE connector status in `system_config`. Signed + durable instance-scoped CT-CVE connector status in `system_config`. Signed connection health now records and returns persisted status, finding ingestion records last ingest time, inventory pushes record success/failure state, and successful data flow clears stale connector errors. Automated inventory push diff --git a/docs/ct-ops-ct-cve-api-contract.md b/docs/ct-ops-ct-cve-api-contract.md index 3028bafa..d537607f 100644 --- a/docs/ct-ops-ct-cve-api-contract.md +++ b/docs/ct-ops-ct-cve-api-contract.md @@ -9,7 +9,7 @@ packages, modules, or service internals. CT Ops owns: -- Organisations, users, seats, hosts, agents, groups, tags, and operational +- Instances, users, seats, hosts, agents, groups, tags, and operational inventory. - Software inventory collection and the current package rows reported by CT Ops agents. diff --git a/docs/ct-ops-licensing-and-ct-cve-product-decision.md b/docs/ct-ops-licensing-and-ct-cve-product-decision.md index 8bd8f6bc..6c0c9b13 100644 --- a/docs/ct-ops-licensing-and-ct-cve-product-decision.md +++ b/docs/ct-ops-licensing-and-ct-cve-product-decision.md @@ -269,7 +269,7 @@ initial contract should avoid CT-CVE reaching directly into CT Ops internals. The phase 6 API contract must define: -- Organisation scoping. +- Instance scoping. - Host and package identity. - Inventory payloads. - Finding payloads. diff --git a/docs/ct-ops-plugin-identity-broker.md b/docs/ct-ops-plugin-identity-broker.md index 08fe29fa..e84bb47f 100644 --- a/docs/ct-ops-plugin-identity-broker.md +++ b/docs/ct-ops-plugin-identity-broker.md @@ -110,7 +110,7 @@ Registry invariants: - The `product` claim and the registered plugin instance must agree. - A plugin instance belongs to exactly one CT Ops installation scope. - Revoked or disabled instances cannot receive fresh user assertions. -- Trust material changes are auditable and require org-admin or super-admin +- Trust material changes are auditable and require instance-admin or super-admin authority. ## Pairing And Trust Exchange diff --git a/docs/docker-container-telemetry-implementation-plan.md b/docs/docker-container-telemetry-implementation-plan.md index 56147742..b3818f1f 100644 --- a/docs/docker-container-telemetry-implementation-plan.md +++ b/docs/docker-container-telemetry-implementation-plan.md @@ -47,7 +47,7 @@ Purpose: define the cross-component contract before parallel implementation star | Task | Owner | Status | Files / Areas | Dependencies | Acceptance Criteria | PR | Notes | | --- | --- | --- | --- | --- | --- | --- | --- | | Define Docker telemetry protobuf contract | Codex | done | `proto/agent/v1/*.proto`, generated Go protobufs | None | Messages represent Docker status, container inventory, metric samples, batch ids, dropped sample counts, and payload limits. Existing agents remain compatible. | [#1338](https://github.com/carrtech-dev/ct-ops/pull/1338) | Merged in #1338. Added heartbeat Docker status/config and dedicated Docker telemetry upload RPC. | -| Define database schema contract | Codex | done | `apps/web/lib/db/schema/*`, migrations | Protobuf contract | Schema covers Docker container inventory and time-series metrics with tenant/host scoping and query indexes. | TBD | Contract documented below in `Database Schema Contract`. Avoid storing only current values. | +| Define database schema contract | Codex | done | `apps/web/lib/db/schema/*`, migrations | Protobuf contract | Schema covers Docker container inventory and time-series metrics with instance/host scoping and query indexes. | TBD | Contract documented below in `Database Schema Contract`. Avoid storing only current values. | | Define settings and retention contract | Codex | done | instance settings, host metadata/settings, docs | Database schema contract | Global default is 30 days; host override is nullable; effective retention behavior is documented. | TBD | Contract documented below in `Settings And Retention Contract`. Per-host retention uses a sweeper. | | Define UI information architecture | Codex | done | host detail UI, Administration settings | Schema/settings contract | Host Docker status, Containers tab, settings locations, and empty states are documented. | TBD | Contract documented below in `UI Information Architecture`. UI distinguishes not installed from permission denied. | @@ -206,7 +206,7 @@ Purpose: make the feature operationally useful beyond basic charts. - Agent Docker detection tests should cover not installed, permission denied, Docker API error, and installed states. - Agent sampler tests should cover CPU/memory calculations, bounded buffering, dropped sample counts, and flush behavior. -- Ingest tests should cover old agents, malformed payloads, excessive payloads, idempotent retries, and tenant/host scoping. +- Ingest tests should cover old agents, malformed payloads, excessive payloads, idempotent retries, and instance/host scoping. - Retention tests should cover global default, host override, clearing override, and mixed-host deletion behavior. - UI tests should cover Docker status empty states, container list rendering, chart rendering, and settings updates.